Skip to content

Commit

Permalink
✨🎉 Overhaul codebase & implement account selection
Browse files Browse the repository at this point in the history
  • Loading branch information
Creative-Difficulty committed Aug 24, 2023
1 parent 84c1bc8 commit 7fce4eb
Show file tree
Hide file tree
Showing 14 changed files with 746 additions and 9 deletions.
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"bits-ui": "^0.0.32",
"clsx": "^2.0.0",
"lucide-svelte": "^0.268.0",
"svelte-french-toast": "^1.2.0",
"tailwind-merge": "^1.14.0",
"tailwind-variants": "^0.1.13",
"tailwindcss-animate": "^1.0.6"
Expand Down
52 changes: 52 additions & 0 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { redirect, type Handle } from '@sveltejs/kit';
import type { CapitalComUserAccounts } from "$lib/types"
import { UserCST, UserXSecurityToken } from "$lib/stores";

let userCST: string;
UserCST.subscribe((value: string) => {
userCST = value;
});

let userXSecurityToken: string;
UserXSecurityToken.subscribe((value: string) => {
userXSecurityToken = value;
});

export const handle: Handle = (async ({ event, resolve }) => {
if (event.url.pathname.startsWith("/dashboard") || event.url.pathname.startsWith("/api") || event.url.pathname === "/") {
const capitalComCST = event.cookies.get("CAPITALCOM-CST");
const capitalComSecurityToken = event.cookies.get("CAPITALCOM-X-SECURITY-TOKEN");

if(capitalComCST === undefined || capitalComSecurityToken === undefined) {
throw redirect(302, "/login");
}
const response: Response = await fetch("https://api-capital.backend-capital.com/api/v1/accounts", {
method: "GET",
headers: {
"X-SECURITY-TOKEN": capitalComSecurityToken!,
"CST" : capitalComCST!,
"Content-Type" : "application/json"
},
redirect: "follow"
});

let parsedResponse: CapitalComUserAccounts = await response.json();

if(parsedResponse.errorCode !== undefined) {
throw redirect(302, "/login");
}

if(parsedResponse.errorCode === undefined && capitalComCST !== userCST || parsedResponse.errorCode === undefined && capitalComSecurityToken !== userXSecurityToken) {
UserCST.set(capitalComCST);
UserXSecurityToken.set(capitalComSecurityToken);
}

if(event.url.pathname === "/") {
throw redirect(303, "/dashboard");
}
}

const response = await resolve(event);
return response;

});
4 changes: 4 additions & 0 deletions src/lib/stores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { writable, type Writable } from "svelte/store";

export const UserCST: Writable<string> = writable("");
export const UserXSecurityToken: Writable<string> = writable("");
122 changes: 122 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
export interface CapitalComCreateSessionResponse {
accountType?: string;
accountInfo?: AccountInfo;
currencyIsoCode?: string;
currencySymbol?: string;
currentAccountId?: string;
streamingHost?: string;
accounts?: (Account)[];
clientId?: string;
timezoneOffset?: number;
hasActiveDemoAccounts?: boolean;
hasActiveLiveAccounts?: boolean;
trailingStopsEnabled?: boolean;
errorCode?: string;
}

export interface AccountInfo {
balance: number;
deposit: number;
profitLoss: number;
available: number;
}

export interface Account {
accountId: string;
accountName: string;
preferred: boolean;
accountType: string;
}

export interface CapitalComTradeHistoryResponse {
activities?: Activity[];
errorCode?: string;
}

export interface Activity {
date: string
dateUTC: string
epic: string
dealId: string
source: string
type: string
status: string
}


export interface CapitalComTradeDetailsResponse {
position?: Position;
market?: Market;
errorCode?: string;
}

export interface Position {
contractSize: number;
createdDate: string;
createdDateUTC: string;
dealId: string;
dealReference: string;
workingOrderId: string;
size: number;
leverage: number;
upl: number;
direction: "BUY" | "SELL";
level: number;
currency: string;
guaranteedStop: boolean;
}

export interface Market {
instrumentName: string;
expiry: string;
marketStatus: string;
epic: string;
instrumentType: string;
lotSize: number;
high: number;
low: number;
percentageChange: number;
netChange: number;
bid: number;
offer: number;
updateTime: string;
updateTimeUTC: string;
delayTime: number;
streamingPricesAvailable: boolean;
scalingFactor: number;
}

export interface CapitalComPingResponse {
status?: "ok" | string,
errorCode?: string
}

export interface CapitalComUserAccounts {
accounts?: Account[];
errorCode?: string
}

export interface Account {
accountId: string
accountName: string
status: string
accountType: string
preferred: boolean
balance: Balance
currency: string
}

export interface Balance {
balance: number
deposit: number
profitLoss: number
available: number
}

export interface SwitchAccountsResponse {
trailingStopsEnabled?: boolean
dealingEnabled?: boolean
hasActiveDemoAccounts?: boolean
hasActiveLiveAccounts?: boolean
errorCode?: string
}
31 changes: 31 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import { page } from "$app/stores";
import type { CapitalComPingResponse } from "$lib/types"
import { UserCST, UserXSecurityToken } from "$lib/stores";
import "../app.postcss";
import { redirect } from "@sveltejs/kit";
import { Toaster } from "svelte-french-toast";
if($page.route.id?.startsWith("/dashboard")) {
setInterval(async () => {
const response: Response = await fetch("https://api-capital.backend-capital.com/api/v1/ping", {
method: "GET",
headers: {
"X-SECURITY-TOKEN": $UserXSecurityToken.toString(),
"CST" : $UserCST.toString(),
"Content-Type" : "application/json"
},
redirect: "follow"
});
let parsedResponse: CapitalComPingResponse = await response.json();
if(parsedResponse.errorCode !== undefined) {
throw redirect(302, "/login");
}
}, 580000)
}
</script>

<Toaster/>
<slot />
58 changes: 58 additions & 0 deletions src/routes/api/getrecenttrades/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { json, redirect, type RequestHandler } from '@sveltejs/kit';
import type { CapitalComTradeDetailsResponse, CapitalComTradeHistoryResponse } from "$lib/types";
import { UserCST, UserXSecurityToken } from "$lib/stores";

let userCST: string;
UserCST.subscribe((value: string) => {
userCST = value;
});

let userXSecurityToken: string;
UserXSecurityToken.subscribe((value: string) => {
userXSecurityToken = value;
});


export const GET = (async ({ cookies }) => {
const capitalComCST = cookies.get("CAPITALCOM-CST");
const capitalComSecurityToken = cookies.get("CAPITALCOM-X-SECURITY-TOKEN");
if(userCST !== capitalComCST || userXSecurityToken !== capitalComSecurityToken) { throw redirect(302, "/login") }
const response: Response = await fetch("https://api-capital.backend-capital.com/api/v1/history/activity?type=POSITION&lastPeriod=86400", {
method: "GET",
headers: {
"X-SECURITY-TOKEN": userXSecurityToken,
"CST": userCST,
"Content-Type" : "application/json"
}
});

const parsedResponse: CapitalComTradeHistoryResponse = await response.json();
if(parsedResponse.errorCode !== undefined) { return json([{ error: parsedResponse.errorCode }]); }
let tradeArrayToReturn: Array<{ title?: string; description?: string; error?: string }> = [];

await Promise.all(parsedResponse.activities!.map(async trade => {
if(trade.source === "USER" && trade.type === "POSITION") {
const tradeDetailsResponse: Response = await fetch(`https://api-capital.backend-capital.com/api/v1/positions/${trade.dealId}`, {
method: "GET",
headers: {
"X-SECURITY-TOKEN": userXSecurityToken,
"CST": userCST,
"Content-Type" : "application/json"
}
});

const parsedTradeDetailsResponse: CapitalComTradeDetailsResponse = await tradeDetailsResponse.json();
console.log(parsedTradeDetailsResponse);
if(parsedTradeDetailsResponse.errorCode === undefined) {
tradeArrayToReturn.push({
title: `${parsedTradeDetailsResponse.position!.direction! === "BUY" ? "Bought" : "Sold"} ${parsedTradeDetailsResponse.position!.size!} ${parsedTradeDetailsResponse!.market?.instrumentType.toLowerCase()} of ${parsedTradeDetailsResponse.market!.instrumentName}`,
description: `${new Date(new Date().getTime() - new Date(parsedTradeDetailsResponse.position!.createdDate).getTime()).getMinutes()} ${(new Date(new Date().getTime() - new Date(parsedTradeDetailsResponse.position!.createdDate).getTime()).getMinutes()) === 1 ? "minute" : "minutes"} ago`
})
} else {
console.log(`Error when getting details of one trade: ${parsedTradeDetailsResponse.errorCode}`);
}
}
}));
console.log(tradeArrayToReturn);
return json(tradeArrayToReturn, { status: 200 });
}) satisfies RequestHandler
61 changes: 61 additions & 0 deletions src/routes/api/selectcapitalcomaccount/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { json, redirect, type RequestHandler } from '@sveltejs/kit';
import type { CapitalComUserAccounts, SwitchAccountsResponse } from "$lib/types";
import { UserCST, UserXSecurityToken } from "$lib/stores";

let userCST: string;
UserCST.subscribe((value: string) => {
userCST = value;
});

let userXSecurityToken: string;
UserXSecurityToken.subscribe((value: string) => {
userXSecurityToken = value;
});


export const POST: RequestHandler = (async ({ cookies, request }) => {
const capitalComCST = cookies.get("CAPITALCOM-CST");
const capitalComSecurityToken = cookies.get("CAPITALCOM-X-SECURITY-TOKEN");
if(userCST !== capitalComCST || userXSecurityToken !== capitalComSecurityToken) { throw redirect(302, "/login") }

const userAccountsResponse: Response = await fetch("https://api-capital.backend-capital.com/api/v1/accounts", {
method: "GET",
headers: {
"X-SECURITY-TOKEN": userXSecurityToken,
"CST": userCST,
"Content-Type" : "application/json"
},
redirect: "follow"
});

let parsedUserAccountsResponse: CapitalComUserAccounts = await userAccountsResponse.json();
if(parsedUserAccountsResponse.errorCode !== undefined) { console.log(`Error while getting all acounts for user: ${parsedUserAccountsResponse.errorCode}`); return json({ error: `Error while getting all acounts for user: ${parsedUserAccountsResponse.errorCode}` }, { status: 500 }); }
if(parsedUserAccountsResponse.accounts?.length === 0) { console.log(`User has no accounts!: ${parsedUserAccountsResponse}`); return json({ error: "The selected account does not have any trading accounts." }, { status: 500 }); }

let submittedAccountName = JSON.parse((await request.text())).selectedAccount;
if(parsedUserAccountsResponse.accounts!.find(account => account.accountName === submittedAccountName) !== undefined) {
const parsedSwitchAccountResponse: SwitchAccountsResponse = await (await fetch("https://api-capital.backend-capital.com/api/v1/session", {
method: "PUT",
headers: {
"X-SECURITY-TOKEN": userXSecurityToken,
"CST": userCST,
"Content-Type" : "application/json"
},
body: JSON.stringify({
accountId: parsedUserAccountsResponse.accounts!.find(account => account.accountName === submittedAccountName)?.accountId
}),
redirect: "follow"
})).json();

if(parsedSwitchAccountResponse.errorCode !== undefined && parsedSwitchAccountResponse.errorCode !== "error.not-different.accountId") {
console.log(`Error while selecting account: ${parsedSwitchAccountResponse.errorCode!}`); return json({ error: `Error while switching account: ${parsedSwitchAccountResponse.errorCode!}` }, { status: 500 });
} else if(parsedSwitchAccountResponse.errorCode === "error.not-different.accountId") {
return json({ success: true, msg: `Already signed in to ${submittedAccountName}.` }, { status: 200 });
}

return json({ success: true }, { status: 200 });
} else {
return json({ error: `The selected trading account "${submittedAccountName}" does not exist.`.replace("\"", "") }, { status: 500 });
}

});
1 change: 1 addition & 0 deletions src/routes/dashboard/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ssr = false
Loading

0 comments on commit 7fce4eb

Please sign in to comment.