Skip to content

Commit 4f68026

Browse files
committed
feat: implement new routes convention
1 parent 8d4d7f8 commit 4f68026

File tree

15 files changed

+344
-147
lines changed

15 files changed

+344
-147
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@squarecloud/api": minor
3+
---
4+
5+
Implement new API routes convention

src/lib/routes.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { APIEndpoint } from "@/types";
2+
3+
export type Route<T extends APIEndpoint> = string & { __route: T };
4+
export const Route = <T extends APIEndpoint>(route: string) => {
5+
return route as Route<T>;
6+
};
7+
8+
export const Routes = {
9+
user() {
10+
return Route<"user">("/v2/users/me");
11+
},
12+
service: {
13+
status() {
14+
return Route<"service/status">("/v2/service/status");
15+
},
16+
},
17+
apps: {
18+
upload() {
19+
return Route<"apps/upload">("/v2/apps");
20+
},
21+
statusAll() {
22+
return Route<"apps/status-all">("/v2/apps/status");
23+
},
24+
info(appId: string) {
25+
return Route<"apps/info">(`/v2/apps/${appId}`);
26+
},
27+
status(appId: string) {
28+
return Route<"apps/status">(`/v2/apps/${appId}/status`);
29+
},
30+
logs(appId: string) {
31+
return Route<"apps/logs">(`/v2/apps/${appId}/logs`);
32+
},
33+
backups(appId: string) {
34+
return Route<"apps/backups">(`/v2/apps/${appId}/backups`);
35+
},
36+
generateBackup(appId: string) {
37+
return Route<"apps/generate-backup">(`/v2/apps/${appId}/backups`);
38+
},
39+
start(appId: string) {
40+
return Route<"apps/start">(`/v2/apps/${appId}/start`);
41+
},
42+
restart(appId: string) {
43+
return Route<"apps/restart">(`/v2/apps/${appId}/restart`);
44+
},
45+
stop(appId: string) {
46+
return Route<"apps/stop">(`/v2/apps/${appId}/stop`);
47+
},
48+
delete(appId: string) {
49+
return Route<"apps/delete">(`/v2/apps/${appId}`);
50+
},
51+
commit(appId: string) {
52+
return Route<"apps/commit">(`/v2/apps/${appId}/commit`);
53+
},
54+
files: {
55+
read(appId: string) {
56+
return Route<"apps/files/read">(`/v2/apps/${appId}/files/content`);
57+
},
58+
list(appId: string) {
59+
return Route<"apps/files/list">(`/v2/apps/${appId}/files`);
60+
},
61+
upsert(appId: string) {
62+
return Route<"apps/files/upsert">(`/v2/apps/${appId}/files`);
63+
},
64+
move(appId: string) {
65+
return Route<"apps/files/move">(`/v2/apps/${appId}/files`);
66+
},
67+
delete(appId: string) {
68+
return Route<"apps/files/delete">(`/v2/apps/${appId}/files`);
69+
},
70+
},
71+
deployments: {
72+
list(appId: string) {
73+
return Route<"apps/deployments/list">(`/v2/apps/${appId}/deployments`);
74+
},
75+
current(appId: string) {
76+
return Route<"apps/deployments/current">(
77+
`/v2/apps/${appId}/deployments/current`,
78+
);
79+
},
80+
webhook(appId: string) {
81+
return Route<"apps/deployments/webhook">(
82+
`/v2/apps/${appId}/deploy/webhook`,
83+
);
84+
},
85+
},
86+
network: {
87+
dns(appId: string) {
88+
return Route<"apps/network/dns">(`/v2/apps/${appId}/network/dns`);
89+
},
90+
analytics(appId: string) {
91+
return Route<"apps/network/analytics">(
92+
`/v2/apps/${appId}/network/analytics`,
93+
);
94+
},
95+
custom(appId: string) {
96+
return Route<"apps/network/custom">(`/v2/apps/${appId}/network/custom`);
97+
},
98+
},
99+
},
100+
};

src/managers/api.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,52 @@
1-
import type {
2-
APIPayload,
3-
APIUserInfo,
4-
APIVersion,
5-
} from "@squarecloud/api-types/v2";
6-
import { type APIApplicationEndpoints, SquareCloudAPIError } from "..";
1+
import type { Route } from "@/lib/routes";
2+
import type { APIPayload, APIVersion } from "@squarecloud/api-types/v2";
3+
import {
4+
type APIEndpoint,
5+
type APIRequestOptions,
6+
type APIResponse,
7+
SquareCloudAPIError,
8+
} from "..";
79

810
export class APIManager {
911
public readonly baseUrl = "https://api.squarecloud.app";
1012
public readonly version: APIVersion<1 | 2> = "v2";
1113

1214
constructor(readonly apiKey: string) {}
1315

14-
user(userId?: string): Promise<APIPayload<APIUserInfo>> {
15-
return this.fetch(`user${userId ? `/${userId}` : ""}`);
16-
}
16+
async request<T extends APIEndpoint>(
17+
path: Route<T>,
18+
options?: APIRequestOptions<T>,
19+
): Promise<APIResponse<T>> {
20+
const init = options || ({} as RequestInit);
21+
22+
init.method = init.method || "GET";
23+
init.headers = {
24+
...(init.headers || {}),
25+
Authorization: this.apiKey,
26+
};
1727

18-
application<T extends keyof APIApplicationEndpoints | (string & {})>(
19-
path: T,
20-
appId?: string,
21-
params?: Record<string, string>,
22-
options?: RequestInit | "GET" | "POST" | "DELETE",
23-
): Promise<
24-
APIPayload<
25-
T extends keyof APIApplicationEndpoints
26-
? APIApplicationEndpoints[T]
27-
: never
28-
>
29-
> {
30-
if (typeof options === "string") {
31-
options = {
32-
method: options,
33-
};
28+
const url = new URL(path, `${this.baseUrl}/${this.version}`);
29+
30+
if ("query" in init && init.query) {
31+
const query = new URLSearchParams(init.query as Record<string, string>);
32+
url.search = query.toString();
33+
init.query = undefined;
3434
}
3535

36-
const url = `apps${appId ? `/${appId}` : ""}${path ? `/${path}` : ""}${
37-
params ? `?${new URLSearchParams(params)}` : ""
38-
}`;
36+
if ("body" in init && init.body && !(init.body instanceof Buffer)) {
37+
init.body = JSON.stringify(init.body);
38+
}
3939

40-
return this.fetch(url, options);
40+
const response = await fetch(url, init).catch((err) => {
41+
throw new SquareCloudAPIError(err.code, err.message);
42+
});
43+
const data = await response.json();
44+
45+
if (!data || data.status === "error" || !response.ok) {
46+
throw new SquareCloudAPIError(data?.code || "COMMON_ERROR");
47+
}
48+
49+
return data;
4150
}
4251

4352
async fetch<T>(

src/managers/application/backup.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { type Application, SquareCloudAPIError } from "@/index";
2+
import { Routes } from "@/lib/routes";
3+
import type { RESTPostAPIApplicationBackupResult } from "@squarecloud/api-types/v2";
24

35
export class ApplicationBackupManager {
46
constructor(public readonly application: Application) {}
57

68
/** @returns The generated backup URL */
7-
async url(): Promise<string> {
8-
const data = await this.application.client.api.application(
9-
"backup",
10-
this.application.id,
9+
async create(): Promise<RESTPostAPIApplicationBackupResult> {
10+
const data = await this.application.client.api.request(
11+
Routes.apps.generateBackup(this.application.id),
12+
{ method: "POST" },
1113
);
1214

13-
const backup = data.response.downloadURL;
15+
const backup = data.response;
1416

1517
this.application.client.emit(
1618
"backupUpdate",
@@ -25,14 +27,9 @@ export class ApplicationBackupManager {
2527

2628
/** @returns The generated backup buffer */
2729
async download(): Promise<Buffer> {
28-
const url = await this.url();
30+
const backup = await this.create();
2931

30-
const registryUrl = url.replace(
31-
"https://squarecloud.app/dashboard/backup/",
32-
"https://registry.squarecloud.app/v1/backup/download/",
33-
);
34-
35-
const res = await fetch(registryUrl)
32+
const res = await fetch(backup.url)
3633
.then((res) => res.arrayBuffer())
3734
.catch(() => undefined);
3835

src/managers/application/cache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { ApplicationStatus } from "@/structures";
2+
import type { RESTPostAPIApplicationBackupResult } from "@squarecloud/api-types/v2";
23

34
export type ApplicationCacheKey = "status" | "backup" | "logs";
45

56
export class ApplicationCacheManager {
67
readonly status?: ApplicationStatus;
7-
readonly backup?: string;
8+
readonly backup?: RESTPostAPIApplicationBackupResult;
89
readonly logs?: string;
910

1011
set<T extends ApplicationCacheKey>(

src/managers/application/deploys.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assertString } from "@/assertions/literal";
22
import type { Application } from "@/index";
3+
import { Routes } from "@/lib/routes";
34

45
export class ApplicationDeploysManager {
56
constructor(public readonly application: Application) {}
@@ -12,14 +13,9 @@ export class ApplicationDeploysManager {
1213
async getGithubWebhook(accessToken: string) {
1314
assertString(accessToken);
1415

15-
const data = await this.application.client.api.application(
16-
"deploy/git-webhook",
17-
this.application.id,
18-
undefined,
19-
{
20-
method: "POST",
21-
body: JSON.stringify({ access_token: accessToken }),
22-
},
16+
const data = await this.application.client.api.request(
17+
Routes.apps.deployments.webhook(this.application.id),
18+
{ method: "POST", body: { access_token: accessToken } },
2319
);
2420

2521
return data.response.webhook;
@@ -29,9 +25,8 @@ export class ApplicationDeploysManager {
2925
* Gets the last 10 deployments of an application from the last 24 hours
3026
*/
3127
async list() {
32-
const data = await this.application.client.api.application(
33-
"deploys/list",
34-
this.application.id,
28+
const data = await this.application.client.api.request(
29+
Routes.apps.deployments.list(this.application.id),
3530
);
3631

3732
return data?.response;

src/managers/application/files.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { join } from "path";
22
import { assertPathLike, assertString } from "@/assertions/literal";
33
import type { Application } from "@/index";
4+
import { Routes } from "@/lib/routes";
45
import { readFile } from "fs/promises";
56

67
export class ApplicationFilesManager {
@@ -14,10 +15,9 @@ export class ApplicationFilesManager {
1415
async list(path = "/") {
1516
assertString(path, "LIST_FILES_PATH");
1617

17-
const { response } = await this.application.client.api.application(
18-
"files/list",
19-
this.application.id,
20-
{ path },
18+
const { response } = await this.application.client.api.request(
19+
Routes.apps.files.list(this.application.id),
20+
{ query: { path } },
2121
);
2222

2323
return response;
@@ -31,10 +31,9 @@ export class ApplicationFilesManager {
3131
async read(path: string) {
3232
assertString(path, "READ_FILE_PATH");
3333

34-
const { response } = await this.application.client.api.application(
35-
"files/read",
36-
this.application.id,
37-
{ path },
34+
const { response } = await this.application.client.api.request(
35+
Routes.apps.files.read(this.application.id),
36+
{ query: { path } },
3837
);
3938

4039
if (!response) {
@@ -59,16 +58,11 @@ export class ApplicationFilesManager {
5958
}
6059
path = join(path, fileName).replaceAll("\\", "/");
6160

62-
const { status } = await this.application.client.api.application(
63-
"files/create",
64-
this.application.id,
65-
undefined,
61+
const { status } = await this.application.client.api.request(
62+
Routes.apps.files.upsert(this.application.id),
6663
{
67-
method: "POST",
68-
body: JSON.stringify({
69-
buffer: file.toJSON(),
70-
path,
71-
}),
64+
method: "PUT",
65+
body: { content: file.toString("utf8"), path },
7266
},
7367
);
7468

@@ -83,11 +77,9 @@ export class ApplicationFilesManager {
8377
async delete(path: string) {
8478
assertString(path, "DELETE_FILE_PATH");
8579

86-
const { status } = await this.application.client.api.application(
87-
"files/delete",
88-
this.application.id,
89-
{ path },
90-
"DELETE",
80+
const { status } = await this.application.client.api.request(
81+
Routes.apps.files.delete(this.application.id),
82+
{ method: "DELETE", query: { path } },
9183
);
9284

9385
return status === "success";

0 commit comments

Comments
 (0)