@darco2903/cdn-api 1.0.7-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/workflows/release.yml +116 -0
- package/LICENSE +674 -0
- package/README.md +25 -0
- package/TODO.md +0 -0
- package/package.json +49 -0
- package/prettier.config.js +24 -0
- package/src/client.ts +18 -0
- package/src/common.ts +4 -0
- package/src/consts.ts +8 -0
- package/src/contract/auth.ts +22 -0
- package/src/contract/endpoint.ts +53 -0
- package/src/contract/index.ts +27 -0
- package/src/contract/key.ts +19 -0
- package/src/contract/list.ts +30 -0
- package/src/contract/record.ts +82 -0
- package/src/contract/service.ts +44 -0
- package/src/contract/stats.ts +36 -0
- package/src/contract/upload.ts +82 -0
- package/src/index.ts +2 -0
- package/src/server.ts +89 -0
- package/src/socket/index.ts +2 -0
- package/src/socket/interface/index.ts +12 -0
- package/src/socket/interface/template.ts +7 -0
- package/src/socket/types.ts +7 -0
- package/src/types/creds.ts +7 -0
- package/src/types/endpoint.ts +19 -0
- package/src/types/index.ts +6 -0
- package/src/types/jwt.ts +78 -0
- package/src/types/record.ts +35 -0
- package/src/types/stats.ts +9 -0
- package/src/types/upload.ts +18 -0
- package/src/types.ts +38 -0
- package/src/uploader.ts +99 -0
- package/tests/keys/private.pem +5 -0
- package/tests/keys/public.pem +4 -0
- package/tests/keys.ts +11 -0
- package/tests/tsconfig.json +12 -0
- package/tests/vitest/jwt.test.ts +38 -0
- package/tools/generate-openapi.ts +25 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# CDN API
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
This API is a TypeScript client for the Darco2903 CDN service.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install cdn-api-<version>.tgz
|
|
11
|
+
npm install @ts-rest/core zod@3.22.3
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Create an instance of the API
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { initClient } from "@ts-rest/core";
|
|
20
|
+
import { contract } from "cdn-api";
|
|
21
|
+
|
|
22
|
+
const api = initClient(contract, {
|
|
23
|
+
baseUrl: "https://api.example.com",
|
|
24
|
+
});
|
|
25
|
+
```
|
package/TODO.md
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@darco2903/cdn-api",
|
|
3
|
+
"version": "1.0.7-beta.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"author": "",
|
|
9
|
+
"license": "ISC",
|
|
10
|
+
"repository": {
|
|
11
|
+
"url": "https://github.com/Darco2903/cdn-api.git"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./client": "./dist/client.js",
|
|
19
|
+
"./server": "./dist/server.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@darco2903/auth-api": "2.0.3-pre.0",
|
|
23
|
+
"@ts-rest/core": "^3.52.1",
|
|
24
|
+
"@ts-rest/open-api": "^3.52.1",
|
|
25
|
+
"axios": "^1.12.2",
|
|
26
|
+
"jsonwebtoken": "^9.0.3",
|
|
27
|
+
"neverthrow": "^8.2.0",
|
|
28
|
+
"socket.io-client": "^4.8.1",
|
|
29
|
+
"zod": "^3.25.76"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@darco2903/secondthought": "^1.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
36
|
+
"@types/node": "^25.4.0",
|
|
37
|
+
"prettier": "^3.8.1",
|
|
38
|
+
"rimraf": "^6.1.3",
|
|
39
|
+
"tsx": "^4.21.0",
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vitest": "^4.0.18"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"test": "vitest",
|
|
46
|
+
"clean": "rimraf -g dist darco2903-cdn-api-*.tgz openapi.json",
|
|
47
|
+
"generate:openapi": "tsx tools/generate-openapi.ts"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @see https://prettier.io/docs/configuration
|
|
3
|
+
* @type {import("prettier").Config}
|
|
4
|
+
*/
|
|
5
|
+
const config = {
|
|
6
|
+
trailingComma: "es5",
|
|
7
|
+
tabWidth: 4,
|
|
8
|
+
semi: true,
|
|
9
|
+
singleQuote: false,
|
|
10
|
+
bracketSpacing: true,
|
|
11
|
+
printWidth: 80,
|
|
12
|
+
arrowParens: "always",
|
|
13
|
+
endOfLine: "lf",
|
|
14
|
+
useTabs: false,
|
|
15
|
+
proseWrap: "preserve",
|
|
16
|
+
quoteProps: "as-needed",
|
|
17
|
+
htmlWhitespaceSensitivity: "css",
|
|
18
|
+
bracketSameLine: false,
|
|
19
|
+
experimentalOperatorPosition: "start",
|
|
20
|
+
experimentalTernaries: true,
|
|
21
|
+
objectWrap: "preserve",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default config;
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
import io from "socket.io-client";
|
|
3
|
+
import { initClient } from "@ts-rest/core";
|
|
4
|
+
import contract from "./contract/index.js";
|
|
5
|
+
import type { CdnClientSocket } from "./socket/index.js";
|
|
6
|
+
|
|
7
|
+
export function createClient(origin: string) {
|
|
8
|
+
return initClient(contract, {
|
|
9
|
+
baseUrl: origin,
|
|
10
|
+
credentials: "include",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSocket(origin: string): CdnClientSocket {
|
|
15
|
+
return io(origin, {
|
|
16
|
+
autoConnect: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
package/src/common.ts
ADDED
package/src/consts.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { initContract } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { authHeaderSchema } from "@darco2903/auth-api/client";
|
|
4
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
5
|
+
|
|
6
|
+
const c = initContract();
|
|
7
|
+
|
|
8
|
+
export default c.router({
|
|
9
|
+
access: {
|
|
10
|
+
method: "GET",
|
|
11
|
+
path: "/auth/access",
|
|
12
|
+
headers: authHeaderSchema,
|
|
13
|
+
responses: {
|
|
14
|
+
200: apiSuccess(
|
|
15
|
+
z.object({
|
|
16
|
+
access: z.boolean(),
|
|
17
|
+
})
|
|
18
|
+
),
|
|
19
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { initContract, ZodErrorSchema } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { authHeaderSchema } from "@darco2903/auth-api/client";
|
|
4
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
5
|
+
import { endpointPathSchema } from "../types/index.js";
|
|
6
|
+
|
|
7
|
+
const c = initContract();
|
|
8
|
+
|
|
9
|
+
export default c.router({
|
|
10
|
+
create: {
|
|
11
|
+
method: "POST",
|
|
12
|
+
path: "/endpoint",
|
|
13
|
+
headers: authHeaderSchema,
|
|
14
|
+
body: z.object({
|
|
15
|
+
storage_id: z.string().nonempty(),
|
|
16
|
+
endpoint: endpointPathSchema,
|
|
17
|
+
}),
|
|
18
|
+
responses: {
|
|
19
|
+
200: apiSuccess(z.null()),
|
|
20
|
+
400: ZodErrorSchema,
|
|
21
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
22
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
23
|
+
404: apiError(
|
|
24
|
+
z.literal("NOT_FOUND"),
|
|
25
|
+
z.literal("Record not found")
|
|
26
|
+
),
|
|
27
|
+
409: apiError(
|
|
28
|
+
z.literal("CONFLICT"),
|
|
29
|
+
z.literal("Endpoint already exists")
|
|
30
|
+
),
|
|
31
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
delete: {
|
|
35
|
+
method: "DELETE",
|
|
36
|
+
path: "/endpoint",
|
|
37
|
+
headers: authHeaderSchema,
|
|
38
|
+
body: z.object({
|
|
39
|
+
endpoint: endpointPathSchema,
|
|
40
|
+
}),
|
|
41
|
+
responses: {
|
|
42
|
+
200: apiSuccess(z.null()),
|
|
43
|
+
400: ZodErrorSchema,
|
|
44
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
45
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
46
|
+
404: apiError(
|
|
47
|
+
z.literal("NOT_FOUND"),
|
|
48
|
+
z.literal("Endpoint not found")
|
|
49
|
+
),
|
|
50
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { initContract } from "@ts-rest/core";
|
|
2
|
+
import auth from "./auth.js";
|
|
3
|
+
import endpoint from "./endpoint.js";
|
|
4
|
+
import key from "./key.js";
|
|
5
|
+
import list from "./list.js";
|
|
6
|
+
import record from "./record.js";
|
|
7
|
+
import service from "./service.js";
|
|
8
|
+
import stats from "./stats.js";
|
|
9
|
+
import upload from "./upload.js";
|
|
10
|
+
|
|
11
|
+
const c = initContract();
|
|
12
|
+
|
|
13
|
+
export default c.router(
|
|
14
|
+
{
|
|
15
|
+
auth,
|
|
16
|
+
endpoint,
|
|
17
|
+
...key,
|
|
18
|
+
list,
|
|
19
|
+
record,
|
|
20
|
+
service,
|
|
21
|
+
stats,
|
|
22
|
+
upload,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pathPrefix: "/api/v2",
|
|
26
|
+
}
|
|
27
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { initContract } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { apiSuccess } from "../types.js";
|
|
4
|
+
|
|
5
|
+
const c = initContract();
|
|
6
|
+
|
|
7
|
+
export default c.router({
|
|
8
|
+
publicKey: {
|
|
9
|
+
method: "GET",
|
|
10
|
+
path: "/public-key",
|
|
11
|
+
responses: {
|
|
12
|
+
200: apiSuccess(
|
|
13
|
+
z.object({
|
|
14
|
+
publicKey: z.string(),
|
|
15
|
+
})
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { initContract } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { authHeaderSchema } from "@darco2903/auth-api/client";
|
|
4
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
5
|
+
import { recordSchema, recordPublicSchema } from "../types/index.js";
|
|
6
|
+
|
|
7
|
+
const c = initContract();
|
|
8
|
+
|
|
9
|
+
export default c.router({
|
|
10
|
+
public: {
|
|
11
|
+
method: "GET",
|
|
12
|
+
path: "/list",
|
|
13
|
+
headers: authHeaderSchema,
|
|
14
|
+
responses: {
|
|
15
|
+
200: apiSuccess(z.array(recordPublicSchema)),
|
|
16
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
admin: {
|
|
20
|
+
method: "GET",
|
|
21
|
+
path: "/list/admin",
|
|
22
|
+
headers: authHeaderSchema,
|
|
23
|
+
responses: {
|
|
24
|
+
200: apiSuccess(z.array(recordSchema)),
|
|
25
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
26
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
27
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { initContract, ZodErrorSchema } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { authHeaderSchema } from "@darco2903/auth-api/client";
|
|
4
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
5
|
+
import { recordSchema } from "../types/index.js";
|
|
6
|
+
|
|
7
|
+
const c = initContract();
|
|
8
|
+
|
|
9
|
+
export default c.router({
|
|
10
|
+
get: {
|
|
11
|
+
method: "GET",
|
|
12
|
+
path: "/record/:storage_id",
|
|
13
|
+
headers: authHeaderSchema,
|
|
14
|
+
responses: {
|
|
15
|
+
200: apiSuccess(recordSchema),
|
|
16
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
17
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
18
|
+
404: apiError(
|
|
19
|
+
z.literal("NOT_FOUND"),
|
|
20
|
+
z.literal("Record not found")
|
|
21
|
+
),
|
|
22
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
update: {
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: "/record/:storage_id",
|
|
28
|
+
headers: authHeaderSchema,
|
|
29
|
+
body: z.union([
|
|
30
|
+
z.object({
|
|
31
|
+
filename: z.string().max(128),
|
|
32
|
+
role: z.number().gte(0).optional(),
|
|
33
|
+
active: z.boolean().optional(),
|
|
34
|
+
visible: z.boolean().optional(),
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
filename: z.string().max(128).optional(),
|
|
38
|
+
role: z.number().gte(0),
|
|
39
|
+
active: z.boolean().optional(),
|
|
40
|
+
visible: z.boolean().optional(),
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
filename: z.string().max(128).optional(),
|
|
44
|
+
role: z.number().gte(0).optional(),
|
|
45
|
+
active: z.boolean(),
|
|
46
|
+
visible: z.boolean().optional(),
|
|
47
|
+
}),
|
|
48
|
+
z.object({
|
|
49
|
+
filename: z.string().max(128).optional(),
|
|
50
|
+
role: z.number().gte(0).optional(),
|
|
51
|
+
active: z.boolean().optional(),
|
|
52
|
+
visible: z.boolean(),
|
|
53
|
+
}),
|
|
54
|
+
]),
|
|
55
|
+
responses: {
|
|
56
|
+
200: apiSuccess(z.null()),
|
|
57
|
+
400: ZodErrorSchema,
|
|
58
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
59
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
60
|
+
404: apiError(
|
|
61
|
+
z.literal("NOT_FOUND"),
|
|
62
|
+
z.literal("Record not found")
|
|
63
|
+
),
|
|
64
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
delete: {
|
|
68
|
+
method: "DELETE",
|
|
69
|
+
path: "/record/:storage_id",
|
|
70
|
+
headers: authHeaderSchema,
|
|
71
|
+
responses: {
|
|
72
|
+
200: apiSuccess(z.null()),
|
|
73
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
74
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
75
|
+
404: apiError(
|
|
76
|
+
z.literal("NOT_FOUND"),
|
|
77
|
+
z.literal("Record not found")
|
|
78
|
+
),
|
|
79
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { initContract, ZodErrorSchema } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
4
|
+
import { cdnHeaderSchema } from "../types/index.js";
|
|
5
|
+
|
|
6
|
+
const c = initContract();
|
|
7
|
+
|
|
8
|
+
export default c.router({
|
|
9
|
+
update: {
|
|
10
|
+
method: "POST",
|
|
11
|
+
path: "/service/asset",
|
|
12
|
+
headers: cdnHeaderSchema,
|
|
13
|
+
contentType: "multipart/form-data",
|
|
14
|
+
body: z.object({}),
|
|
15
|
+
responses: {
|
|
16
|
+
200: apiSuccess(z.null()),
|
|
17
|
+
400: apiError(z.literal("BAD_REQUEST"), z.string()),
|
|
18
|
+
401: apiError(
|
|
19
|
+
z.literal("UNAUTHORIZED"),
|
|
20
|
+
z.literal("Invalid CDN token")
|
|
21
|
+
),
|
|
22
|
+
429: apiError(z.literal("TOO_MANY_REQUESTS"), z.string()),
|
|
23
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
delete: {
|
|
27
|
+
method: "DELETE",
|
|
28
|
+
path: "/service/asset",
|
|
29
|
+
headers: cdnHeaderSchema,
|
|
30
|
+
responses: {
|
|
31
|
+
200: apiSuccess(z.null()),
|
|
32
|
+
400: ZodErrorSchema,
|
|
33
|
+
401: apiError(
|
|
34
|
+
z.literal("UNAUTHORIZED"),
|
|
35
|
+
z.literal("Invalid CDN token")
|
|
36
|
+
),
|
|
37
|
+
404: apiError(
|
|
38
|
+
z.literal("NOT_FOUND"),
|
|
39
|
+
z.literal("Service asset not found")
|
|
40
|
+
),
|
|
41
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { initContract } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { apiError, apiSuccess } from "../types.js";
|
|
4
|
+
import { statsGlobalSchema } from "../types/index.js";
|
|
5
|
+
|
|
6
|
+
const c = initContract();
|
|
7
|
+
|
|
8
|
+
export default c.router({
|
|
9
|
+
global: {
|
|
10
|
+
method: "GET",
|
|
11
|
+
path: "/stats",
|
|
12
|
+
responses: {
|
|
13
|
+
200: apiSuccess(statsGlobalSchema),
|
|
14
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
// globalAdmin: {
|
|
18
|
+
// method: "GET",
|
|
19
|
+
// path: "/stats/admin",
|
|
20
|
+
// responses: {
|
|
21
|
+
// 200: apiSuccess(statsGlobalSchema),
|
|
22
|
+
// 401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
23
|
+
// 403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
24
|
+
// 500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
25
|
+
// },
|
|
26
|
+
// },
|
|
27
|
+
// user: {
|
|
28
|
+
// method: "GET",
|
|
29
|
+
// path: "/stats/:public_id",
|
|
30
|
+
// headers: authHeaderSchema,
|
|
31
|
+
// responses: {
|
|
32
|
+
// 200: apiSuccess(statsGlobalSchema),
|
|
33
|
+
// 500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
34
|
+
// },
|
|
35
|
+
// },
|
|
36
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { initContract, ZodErrorSchema } from "@ts-rest/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { authHeaderSchema } from "@darco2903/auth-api/client";
|
|
4
|
+
import { apiError, apiSuccess, jsonStringAs } from "../types.js";
|
|
5
|
+
import { uploadDataSchema, uploadInitSchema } from "../types/index.js";
|
|
6
|
+
|
|
7
|
+
const c = initContract();
|
|
8
|
+
|
|
9
|
+
export default c.router({
|
|
10
|
+
upload: {
|
|
11
|
+
method: "POST",
|
|
12
|
+
path: "/upload",
|
|
13
|
+
headers: authHeaderSchema,
|
|
14
|
+
contentType: "multipart/form-data",
|
|
15
|
+
body: z.object({
|
|
16
|
+
file: z.any(),
|
|
17
|
+
data: jsonStringAs(uploadDataSchema),
|
|
18
|
+
}),
|
|
19
|
+
responses: {
|
|
20
|
+
200: apiSuccess(z.null()),
|
|
21
|
+
400: z.union([
|
|
22
|
+
ZodErrorSchema,
|
|
23
|
+
apiError(z.literal("BAD_REQUEST"), z.string()),
|
|
24
|
+
]),
|
|
25
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
26
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
27
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
uploadInit: {
|
|
31
|
+
method: "POST",
|
|
32
|
+
path: "/upload/init",
|
|
33
|
+
body: uploadInitSchema,
|
|
34
|
+
responses: {
|
|
35
|
+
200: apiSuccess(
|
|
36
|
+
z.object({
|
|
37
|
+
uploadId: z.string(),
|
|
38
|
+
})
|
|
39
|
+
),
|
|
40
|
+
400: z.union([
|
|
41
|
+
ZodErrorSchema,
|
|
42
|
+
apiError(z.literal("BAD_REQUEST"), z.string()),
|
|
43
|
+
]),
|
|
44
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
45
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
46
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
uploadPart: {
|
|
50
|
+
method: "POST",
|
|
51
|
+
path: "/upload/part/:upload_id/:part",
|
|
52
|
+
contentType: "multipart/form-data",
|
|
53
|
+
body: z.object({
|
|
54
|
+
file: z.any(),
|
|
55
|
+
}),
|
|
56
|
+
responses: {
|
|
57
|
+
200: apiSuccess(z.null()),
|
|
58
|
+
400: z.union([
|
|
59
|
+
ZodErrorSchema,
|
|
60
|
+
apiError(z.literal("BAD_REQUEST"), z.string()),
|
|
61
|
+
]),
|
|
62
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
63
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
64
|
+
404: apiError(z.literal("NOT_FOUND"), z.string()),
|
|
65
|
+
409: apiError(z.literal("CONFLICT"), z.string()),
|
|
66
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
uploadEnd: {
|
|
70
|
+
method: "POST",
|
|
71
|
+
path: "/upload/end/:upload_id",
|
|
72
|
+
body: z.undefined(),
|
|
73
|
+
responses: {
|
|
74
|
+
200: apiSuccess(z.null()),
|
|
75
|
+
400: ZodErrorSchema,
|
|
76
|
+
401: apiError(z.literal("UNAUTHORIZED"), z.literal("Unauthorized")),
|
|
77
|
+
403: apiError(z.literal("FORBIDDEN"), z.literal("Forbidden")),
|
|
78
|
+
404: apiError(z.literal("NOT_FOUND"), z.string()),
|
|
79
|
+
500: apiError(z.literal("INTERNAL_SERVER_ERROR"), z.string()),
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
package/src/index.ts
ADDED
package/src/server.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { ResultAsync } from "neverthrow";
|
|
4
|
+
import type { Time } from "@darco2903/secondthought";
|
|
5
|
+
import {
|
|
6
|
+
cdnAssetTokenDataDecodedSchema,
|
|
7
|
+
type JWTVerifyError,
|
|
8
|
+
type CdnFeedbackTokenData,
|
|
9
|
+
type CdnAssetTokenDataDecoded,
|
|
10
|
+
type JWTSignError,
|
|
11
|
+
} from "./common.js";
|
|
12
|
+
import { JWT_ALGORITHM } from "./consts.js";
|
|
13
|
+
|
|
14
|
+
export function JWTVerify(
|
|
15
|
+
token: string,
|
|
16
|
+
pubKey: string
|
|
17
|
+
): ResultAsync<CdnAssetTokenDataDecoded, JWTVerifyError> {
|
|
18
|
+
return ResultAsync.fromPromise(
|
|
19
|
+
new Promise((resolve, reject) => {
|
|
20
|
+
jwt.verify(
|
|
21
|
+
token,
|
|
22
|
+
pubKey,
|
|
23
|
+
{ algorithms: [JWT_ALGORITHM] },
|
|
24
|
+
(e, decoded) => {
|
|
25
|
+
if (e) {
|
|
26
|
+
reject({
|
|
27
|
+
name: e.name as JWTVerifyError["name"],
|
|
28
|
+
message: e.message,
|
|
29
|
+
} satisfies JWTVerifyError);
|
|
30
|
+
} else if (decoded === undefined) {
|
|
31
|
+
reject({
|
|
32
|
+
name: "InvalidToken",
|
|
33
|
+
message: "Token is undefined",
|
|
34
|
+
} satisfies JWTVerifyError);
|
|
35
|
+
} else {
|
|
36
|
+
const res =
|
|
37
|
+
cdnAssetTokenDataDecodedSchema.safeParse(decoded);
|
|
38
|
+
if (res.success) {
|
|
39
|
+
resolve(res.data);
|
|
40
|
+
} else {
|
|
41
|
+
reject({
|
|
42
|
+
name: "InvalidTokenData",
|
|
43
|
+
message: "Invalid token data",
|
|
44
|
+
} satisfies JWTVerifyError);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}),
|
|
50
|
+
(e) => e as JWTVerifyError
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sign a JWT token with the given payload and private key, with the specified expiration time.
|
|
56
|
+
* @param expiresIn Expiration time in seconds or a Time object.
|
|
57
|
+
*/
|
|
58
|
+
export function JWTSign(
|
|
59
|
+
payload: CdnFeedbackTokenData,
|
|
60
|
+
privKey: string,
|
|
61
|
+
expiresIn: number | Time
|
|
62
|
+
): ResultAsync<string, JWTSignError> {
|
|
63
|
+
const expiresInSec =
|
|
64
|
+
typeof expiresIn === "number" ? expiresIn : expiresIn.toSecond().time;
|
|
65
|
+
|
|
66
|
+
return ResultAsync.fromPromise(
|
|
67
|
+
new Promise((resolve, reject) => {
|
|
68
|
+
jwt.sign(
|
|
69
|
+
payload,
|
|
70
|
+
privKey,
|
|
71
|
+
{
|
|
72
|
+
algorithm: JWT_ALGORITHM,
|
|
73
|
+
expiresIn: expiresInSec,
|
|
74
|
+
},
|
|
75
|
+
(e, token) => {
|
|
76
|
+
if (e || token === undefined) {
|
|
77
|
+
reject({
|
|
78
|
+
name: "JsonWebTokenError",
|
|
79
|
+
message: e?.message ?? "Failed to sign token",
|
|
80
|
+
} satisfies JWTSignError);
|
|
81
|
+
} else {
|
|
82
|
+
resolve(token);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}),
|
|
87
|
+
(e) => e as JWTSignError
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SocketClientToServerTemplate,
|
|
3
|
+
SocketServerToClientTemplate,
|
|
4
|
+
} from "./template.js";
|
|
5
|
+
|
|
6
|
+
export interface CdnClientToServerEvents
|
|
7
|
+
//
|
|
8
|
+
extends SocketClientToServerTemplate {}
|
|
9
|
+
|
|
10
|
+
export interface CdnServerToClientEvents
|
|
11
|
+
//
|
|
12
|
+
extends SocketServerToClientTemplate {}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Socket as ClientSocket } from "socket.io-client";
|
|
2
|
+
import {
|
|
3
|
+
CdnClientToServerEvents as ClientToServer,
|
|
4
|
+
CdnServerToClientEvents as ServerToClient,
|
|
5
|
+
} from "./interface/index.js";
|
|
6
|
+
|
|
7
|
+
export type CdnClientSocket = ClientSocket<ServerToClient, ClientToServer>;
|