@abyss-project/link 0.0.0-stage → 1.1.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/.eslintrc.js +32 -0
- package/.prettierrc +5 -0
- package/README.md +21 -2
- package/dist/api/abyss.admin.api.d.ts +4 -0
- package/dist/api/abyss.admin.api.js +17 -0
- package/dist/api/abyss.admin.api.js.map +1 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +21 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/link.admin.api.d.ts +14 -0
- package/dist/api/link.admin.api.js +64 -0
- package/dist/api/link.admin.api.js.map +1 -0
- package/dist/api/link.api.d.ts +15 -0
- package/dist/api/link.api.js +69 -0
- package/dist/api/link.api.js.map +1 -0
- package/dist/api/link.public.api.d.ts +2 -0
- package/dist/api/link.public.api.js +9 -0
- package/dist/api/link.public.api.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +18 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/link.constants.d.ts +25 -0
- package/dist/constants/link.constants.js +48 -0
- package/dist/constants/link.constants.js.map +1 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -0
- package/dist/types/enum/api-error.enum.d.ts +24 -0
- package/dist/types/enum/api-error.enum.js +30 -0
- package/dist/types/enum/api-error.enum.js.map +1 -0
- package/dist/types/enum/index.d.ts +4 -0
- package/dist/types/enum/index.js +21 -0
- package/dist/types/enum/index.js.map +1 -0
- package/dist/types/enum/link-share.enum.d.ts +11 -0
- package/dist/types/enum/link-share.enum.js +17 -0
- package/dist/types/enum/link-share.enum.js.map +1 -0
- package/dist/types/enum/link-stats.enum.d.ts +5 -0
- package/dist/types/enum/link-stats.enum.js +10 -0
- package/dist/types/enum/link-stats.enum.js.map +1 -0
- package/dist/types/enum/link.enum.d.ts +20 -0
- package/dist/types/enum/link.enum.js +27 -0
- package/dist/types/enum/link.enum.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interface/api/index.d.ts +5 -0
- package/dist/types/interface/api/index.js +22 -0
- package/dist/types/interface/api/index.js.map +1 -0
- package/dist/types/interface/api/requests/abyss.admin.request.d.ts +14 -0
- package/dist/types/interface/api/requests/abyss.admin.request.js +3 -0
- package/dist/types/interface/api/requests/abyss.admin.request.js.map +1 -0
- package/dist/types/interface/api/requests/index.d.ts +4 -0
- package/dist/types/interface/api/requests/index.js +21 -0
- package/dist/types/interface/api/requests/index.js.map +1 -0
- package/dist/types/interface/api/requests/link.admin.request.d.ts +69 -0
- package/dist/types/interface/api/requests/link.admin.request.js +3 -0
- package/dist/types/interface/api/requests/link.admin.request.js.map +1 -0
- package/dist/types/interface/api/requests/link.public.request.d.ts +13 -0
- package/dist/types/interface/api/requests/link.public.request.js +3 -0
- package/dist/types/interface/api/requests/link.public.request.js.map +1 -0
- package/dist/types/interface/api/requests/link.request.d.ts +74 -0
- package/dist/types/interface/api/requests/link.request.js +3 -0
- package/dist/types/interface/api/requests/link.request.js.map +1 -0
- package/dist/types/interface/api/responses/abyss.admin.response.d.ts +14 -0
- package/dist/types/interface/api/responses/abyss.admin.response.js +3 -0
- package/dist/types/interface/api/responses/abyss.admin.response.js.map +1 -0
- package/dist/types/interface/api/responses/index.d.ts +4 -0
- package/dist/types/interface/api/responses/index.js +21 -0
- package/dist/types/interface/api/responses/index.js.map +1 -0
- package/dist/types/interface/api/responses/link.admin.response.d.ts +43 -0
- package/dist/types/interface/api/responses/link.admin.response.js +3 -0
- package/dist/types/interface/api/responses/link.admin.response.js.map +1 -0
- package/dist/types/interface/api/responses/link.public.response.d.ts +5 -0
- package/dist/types/interface/api/responses/link.public.response.js +3 -0
- package/dist/types/interface/api/responses/link.public.response.js.map +1 -0
- package/dist/types/interface/api/responses/link.response.d.ts +43 -0
- package/dist/types/interface/api/responses/link.response.js +3 -0
- package/dist/types/interface/api/responses/link.response.js.map +1 -0
- package/dist/types/interface/api/type-message/api-error.d.ts +6 -0
- package/dist/types/interface/api/type-message/api-error.js +3 -0
- package/dist/types/interface/api/type-message/api-error.js.map +1 -0
- package/dist/types/interface/api/type-message/base-paginate.d.ts +10 -0
- package/dist/types/interface/api/type-message/base-paginate.js +3 -0
- package/dist/types/interface/api/type-message/base-paginate.js.map +1 -0
- package/dist/types/interface/api/type-message/response.d.ts +5 -0
- package/dist/types/interface/api/type-message/response.js +3 -0
- package/dist/types/interface/api/type-message/response.js.map +1 -0
- package/dist/types/interface/index.d.ts +2 -0
- package/dist/types/interface/index.js +19 -0
- package/dist/types/interface/index.js.map +1 -0
- package/dist/types/interface/models/index.d.ts +5 -0
- package/dist/types/interface/models/index.js +22 -0
- package/dist/types/interface/models/index.js.map +1 -0
- package/dist/types/interface/models/link-list.dto.d.ts +20 -0
- package/dist/types/interface/models/link-list.dto.js +3 -0
- package/dist/types/interface/models/link-list.dto.js.map +1 -0
- package/dist/types/interface/models/link.dto.d.ts +91 -0
- package/dist/types/interface/models/link.dto.js +3 -0
- package/dist/types/interface/models/link.dto.js.map +1 -0
- package/dist/types/interface/models/organization.dto.d.ts +11 -0
- package/dist/types/interface/models/organization.dto.js +3 -0
- package/dist/types/interface/models/organization.dto.js.map +1 -0
- package/dist/types/interface/models/project.dto.d.ts +12 -0
- package/dist/types/interface/models/project.dto.js +3 -0
- package/dist/types/interface/models/project.dto.js.map +1 -0
- package/dist/types/interface/models/user.dto.d.ts +11 -0
- package/dist/types/interface/models/user.dto.js +3 -0
- package/dist/types/interface/models/user.dto.js.map +1 -0
- package/dist/utils/enum.utils.d.ts +1 -0
- package/dist/utils/enum.utils.js +3 -0
- package/dist/utils/enum.utils.js.map +1 -0
- package/dist/utils/error.utils.d.ts +15 -0
- package/dist/utils/error.utils.js +25 -0
- package/dist/utils/error.utils.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/link-slug.utils.d.ts +1 -0
- package/dist/utils/link-slug.utils.js +11 -0
- package/dist/utils/link-slug.utils.js.map +1 -0
- package/jest.config.cjs +9 -0
- package/package.json +62 -4
- package/src/api/abyss.admin.api.ts +39 -0
- package/src/api/index.ts +4 -0
- package/src/api/link.admin.api.ts +168 -0
- package/src/api/link.api.ts +158 -0
- package/src/api/link.public.api.ts +19 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/link.constants.ts +66 -0
- package/src/constants/tests/link-forbidden-slugs.spec.ts +103 -0
- package/src/index.ts +180 -0
- package/src/tests/link-public-api.spec.ts +37 -0
- package/src/tests/link-sdk-surface.spec.ts +191 -0
- package/src/tests/sdk-surface.spec.ts +63 -0
- package/src/types/enum/api-error.enum.ts +31 -0
- package/src/types/enum/index.ts +4 -0
- package/src/types/enum/link-share.enum.ts +17 -0
- package/src/types/enum/link-stats.enum.ts +6 -0
- package/src/types/enum/link.enum.ts +34 -0
- package/src/types/index.ts +2 -0
- package/src/types/interface/api/index.ts +5 -0
- package/src/types/interface/api/requests/abyss.admin.request.ts +17 -0
- package/src/types/interface/api/requests/index.ts +4 -0
- package/src/types/interface/api/requests/link.admin.request.ts +104 -0
- package/src/types/interface/api/requests/link.public.request.ts +18 -0
- package/src/types/interface/api/requests/link.request.ts +100 -0
- package/src/types/interface/api/responses/abyss.admin.response.ts +17 -0
- package/src/types/interface/api/responses/index.ts +4 -0
- package/src/types/interface/api/responses/link.admin.response.ts +56 -0
- package/src/types/interface/api/responses/link.public.response.ts +7 -0
- package/src/types/interface/api/responses/link.response.ts +55 -0
- package/src/types/interface/api/type-message/api-error.ts +7 -0
- package/src/types/interface/api/type-message/base-paginate.ts +11 -0
- package/src/types/interface/api/type-message/response.ts +6 -0
- package/src/types/interface/index.ts +2 -0
- package/src/types/interface/models/index.ts +5 -0
- package/src/types/interface/models/link-list.dto.ts +28 -0
- package/src/types/interface/models/link.dto.ts +117 -0
- package/src/types/interface/models/organization.dto.ts +21 -0
- package/src/types/interface/models/project.dto.ts +16 -0
- package/src/types/interface/models/user.dto.ts +19 -0
- package/src/types/tests/link-enums.spec.ts +48 -0
- package/src/utils/enum.utils.ts +1 -0
- package/src/utils/error.utils.ts +30 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/link-slug.utils.ts +10 -0
- package/tsconfig.check.json +15 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./error.utils"), exports);
|
|
18
|
+
__exportStar(require("./enum.utils"), exports);
|
|
19
|
+
__exportStar(require("./link-slug.utils"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,+CAA6B;AAC7B,oDAAkC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isForbiddenLinkSlug: (slug: string) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isForbiddenLinkSlug = void 0;
|
|
4
|
+
const link_constants_1 = require("../constants/link.constants");
|
|
5
|
+
const isForbiddenLinkSlug = (slug) => {
|
|
6
|
+
const key = slug.toLowerCase();
|
|
7
|
+
return (link_constants_1.LINK_FORBIDDEN_SLUGS.includes(key) ||
|
|
8
|
+
link_constants_1.LINK_FORBIDDEN_SLUG_PREFIXES.some((prefix) => key.startsWith(prefix)));
|
|
9
|
+
};
|
|
10
|
+
exports.isForbiddenLinkSlug = isForbiddenLinkSlug;
|
|
11
|
+
//# sourceMappingURL=link-slug.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-slug.utils.js","sourceRoot":"","sources":["../../src/utils/link-slug.utils.ts"],"names":[],"mappings":";;;AAAA,gEAAiG;AAG1F,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAW,EAAE;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC/B,OAAO,CACL,qCAAoB,CAAC,QAAQ,CAAC,GAAG,CAAC;QAClC,6CAA4B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACtE,CAAC;AACJ,CAAC,CAAC;AANW,QAAA,mBAAmB,uBAM9B"}
|
package/jest.config.cjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyss-project/link",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Core package to interact with AbyssLink",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"lint": "eslint \"{src,apps,libs,test,core}/**/*.ts\" --fix",
|
|
13
|
+
"lint:check": "eslint \"{src,apps,libs,test,core}/**/*.ts\" --max-warnings 0",
|
|
14
|
+
"tsc": "tsc",
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"prepublishOnly": "tsc && node ../../scripts/check-sdk-surface.cjs",
|
|
18
|
+
"dev": "tsc --watch"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://gitlab.com/abyss-group/abyss-link.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [],
|
|
25
|
+
"author": "scylla",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=19.8"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.6.0",
|
|
32
|
+
"axios-retry": "^4.5.0",
|
|
33
|
+
"date-fns": "^2.30.0",
|
|
34
|
+
"dayjs": "^1.10.7",
|
|
35
|
+
"jsonwebtoken": "^8.5.1",
|
|
36
|
+
"reflect-metadata": "^0.1.13",
|
|
37
|
+
"ts-node": "^10.4.0",
|
|
38
|
+
"ts-node-dev": "^1.1.1",
|
|
39
|
+
"uuid": "^8.3.2"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/express-serve-static-core": "^4.19.6",
|
|
43
|
+
"@types/jest": "^29.5.14",
|
|
44
|
+
"@types/jsonwebtoken": "^8.5.0",
|
|
45
|
+
"@types/node": "^20.11.0",
|
|
46
|
+
"@types/uuid": "^8.3.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "8.71.0",
|
|
48
|
+
"@typescript-eslint/parser": "8.71.0",
|
|
49
|
+
"eslint": "^8.57.1",
|
|
50
|
+
"eslint-config-airbnb-base": "^14.2.1",
|
|
51
|
+
"eslint-config-prettier": "^8.3.0",
|
|
52
|
+
"eslint-config-standard": "^16.0.3",
|
|
53
|
+
"eslint-plugin-filenames-simple": "^0.7.0",
|
|
54
|
+
"eslint-plugin-import": "^2.23.4",
|
|
55
|
+
"eslint-plugin-node": "^11.1.0",
|
|
56
|
+
"eslint-plugin-prettier": "^3.4.0",
|
|
57
|
+
"eslint-plugin-promise": "^5.1.0",
|
|
58
|
+
"jest": "^29.7.0",
|
|
59
|
+
"prettier": "2.3.1",
|
|
60
|
+
"ts-jest": "^29.4.15",
|
|
61
|
+
"tsc-watch": "^4.6.2",
|
|
62
|
+
"typescript": "5.9.3"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbyssLinkCore,
|
|
3
|
+
IActivateUserServiceAbyssAdminBody,
|
|
4
|
+
IActivateUserServiceAbyssAdminResponse,
|
|
5
|
+
ISyncOrganizationAbyssAdminBody,
|
|
6
|
+
ISyncOrganizationAbyssAdminResponse,
|
|
7
|
+
ISyncProjectAbyssAdminBody,
|
|
8
|
+
ISyncProjectAbyssAdminResponse,
|
|
9
|
+
} from '..';
|
|
10
|
+
|
|
11
|
+
export const activateUserServiceAbyssAdmin = async (
|
|
12
|
+
body: IActivateUserServiceAbyssAdminBody,
|
|
13
|
+
): Promise<IActivateUserServiceAbyssAdminResponse> => {
|
|
14
|
+
return (
|
|
15
|
+
await AbyssLinkCore.axios.post<IActivateUserServiceAbyssAdminResponse>(
|
|
16
|
+
`abyss/admin/user/activate-service`,
|
|
17
|
+
body,
|
|
18
|
+
)
|
|
19
|
+
).data;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const syncOrganizationAbyssAdmin = async (
|
|
23
|
+
body: ISyncOrganizationAbyssAdminBody,
|
|
24
|
+
): Promise<ISyncOrganizationAbyssAdminResponse> => {
|
|
25
|
+
return (
|
|
26
|
+
await AbyssLinkCore.axios.post<ISyncOrganizationAbyssAdminResponse>(
|
|
27
|
+
`abyss/admin/organization/sync`,
|
|
28
|
+
body,
|
|
29
|
+
)
|
|
30
|
+
).data;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const syncProjectAbyssAdmin = async (
|
|
34
|
+
body: ISyncProjectAbyssAdminBody,
|
|
35
|
+
): Promise<ISyncProjectAbyssAdminResponse> => {
|
|
36
|
+
return (
|
|
37
|
+
await AbyssLinkCore.axios.post<ISyncProjectAbyssAdminResponse>(`abyss/admin/project/sync`, body)
|
|
38
|
+
).data;
|
|
39
|
+
};
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbyssLinkCore,
|
|
3
|
+
ICreateLinkAdminBody,
|
|
4
|
+
ICreateLinkAdminResponse,
|
|
5
|
+
IDeleteCampaignTokenLinkAdminParams,
|
|
6
|
+
IDeleteCampaignTokenLinkAdminResponse,
|
|
7
|
+
IDeleteLinkAdminParams,
|
|
8
|
+
IDeleteLinkAdminResponse,
|
|
9
|
+
IGetByOriginLinkAdminQuery,
|
|
10
|
+
IGetByOriginLinkAdminResponse,
|
|
11
|
+
IGetCampaignStatsLinkAdminParams,
|
|
12
|
+
IGetCampaignStatsLinkAdminQuery,
|
|
13
|
+
IGetCampaignStatsLinkAdminResponse,
|
|
14
|
+
IGetLinkAdminParams,
|
|
15
|
+
IGetLinkAdminResponse,
|
|
16
|
+
IGetLinkStatsQuery,
|
|
17
|
+
IGetStatsByOriginLinkAdminResponse,
|
|
18
|
+
IGetVisitsLinkAdminParams,
|
|
19
|
+
IGetVisitsLinkAdminQuery,
|
|
20
|
+
IGetVisitsLinkAdminResponse,
|
|
21
|
+
IListCampaignTokensLinkAdminQuery,
|
|
22
|
+
IListCampaignTokensLinkAdminResponse,
|
|
23
|
+
ILinkOrigin,
|
|
24
|
+
IPaginateLinkAdminQuery,
|
|
25
|
+
IPaginateLinkAdminResponse,
|
|
26
|
+
IRecordCampaignViewLinkAdminBody,
|
|
27
|
+
IRecordCampaignViewLinkAdminResponse,
|
|
28
|
+
ISetProtectionLinkAdminBody,
|
|
29
|
+
ISetProtectionLinkAdminParams,
|
|
30
|
+
ISetProtectionLinkAdminResponse,
|
|
31
|
+
IUpdateLinkAdminBody,
|
|
32
|
+
IUpdateLinkAdminParams,
|
|
33
|
+
IUpdateLinkAdminResponse,
|
|
34
|
+
} from '..';
|
|
35
|
+
|
|
36
|
+
export const paginateLinkAdmin = async (
|
|
37
|
+
query: IPaginateLinkAdminQuery,
|
|
38
|
+
): Promise<IPaginateLinkAdminResponse> => {
|
|
39
|
+
return (
|
|
40
|
+
await AbyssLinkCore.axios.get<IPaginateLinkAdminResponse>(`admin/link/paginate`, {
|
|
41
|
+
params: query,
|
|
42
|
+
})
|
|
43
|
+
).data;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const getLinkAdmin = async (params: IGetLinkAdminParams): Promise<IGetLinkAdminResponse> => {
|
|
47
|
+
return (await AbyssLinkCore.axios.get<IGetLinkAdminResponse>(`admin/link/${params.linkId}`)).data;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const getVisitsLinkAdmin = async (
|
|
51
|
+
params: IGetVisitsLinkAdminParams,
|
|
52
|
+
query: IGetVisitsLinkAdminQuery = {},
|
|
53
|
+
): Promise<IGetVisitsLinkAdminResponse> => {
|
|
54
|
+
return (
|
|
55
|
+
await AbyssLinkCore.axios.get<IGetVisitsLinkAdminResponse>(
|
|
56
|
+
`admin/link/${params.linkId}/visits`,
|
|
57
|
+
{ params: query },
|
|
58
|
+
)
|
|
59
|
+
).data;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const getCampaignStatsLinkAdmin = async (
|
|
63
|
+
params: IGetCampaignStatsLinkAdminParams,
|
|
64
|
+
query: IGetCampaignStatsLinkAdminQuery,
|
|
65
|
+
): Promise<IGetCampaignStatsLinkAdminResponse> => {
|
|
66
|
+
return (
|
|
67
|
+
await AbyssLinkCore.axios.get<IGetCampaignStatsLinkAdminResponse>(
|
|
68
|
+
`admin/link/${params.linkId}/campaign-stats`,
|
|
69
|
+
{ params: query },
|
|
70
|
+
)
|
|
71
|
+
).data;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const setProtectionLinkAdmin = async (
|
|
75
|
+
params: ISetProtectionLinkAdminParams,
|
|
76
|
+
body: ISetProtectionLinkAdminBody,
|
|
77
|
+
): Promise<ISetProtectionLinkAdminResponse> => {
|
|
78
|
+
return (
|
|
79
|
+
await AbyssLinkCore.axios.put<ISetProtectionLinkAdminResponse>(
|
|
80
|
+
`admin/link/${params.linkId}/protection`,
|
|
81
|
+
body,
|
|
82
|
+
)
|
|
83
|
+
).data;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const updateLinkAdmin = async (
|
|
87
|
+
params: IUpdateLinkAdminParams,
|
|
88
|
+
body: IUpdateLinkAdminBody,
|
|
89
|
+
): Promise<IUpdateLinkAdminResponse> => {
|
|
90
|
+
return (
|
|
91
|
+
await AbyssLinkCore.axios.put<IUpdateLinkAdminResponse>(`admin/link/${params.linkId}`, body)
|
|
92
|
+
).data;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const deleteLinkAdmin = async (
|
|
96
|
+
params: IDeleteLinkAdminParams,
|
|
97
|
+
): Promise<IDeleteLinkAdminResponse> => {
|
|
98
|
+
return (await AbyssLinkCore.axios.delete<IDeleteLinkAdminResponse>(`admin/link/${params.linkId}`))
|
|
99
|
+
.data;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A RESOURCE address or a campaign token for a product resource, free on Link's lines. A RESOURCE
|
|
104
|
+
* is one per origin: a second call returns the same link.
|
|
105
|
+
*/
|
|
106
|
+
export const createLinkAdmin = async (
|
|
107
|
+
body: ICreateLinkAdminBody,
|
|
108
|
+
): Promise<ICreateLinkAdminResponse> => {
|
|
109
|
+
return (await AbyssLinkCore.axios.post<ICreateLinkAdminResponse>(`admin/link`, body)).data;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** The RESOURCE address of a product resource; 404 when the product minted none. */
|
|
113
|
+
export const getByOriginLinkAdmin = async (
|
|
114
|
+
origin: IGetByOriginLinkAdminQuery,
|
|
115
|
+
): Promise<IGetByOriginLinkAdminResponse> => {
|
|
116
|
+
return (
|
|
117
|
+
await AbyssLinkCore.axios.get<IGetByOriginLinkAdminResponse>(`admin/link/origin`, {
|
|
118
|
+
params: origin,
|
|
119
|
+
})
|
|
120
|
+
).data;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** The statistics of a product resource's RESOURCE address over `range` (30 days by default). */
|
|
124
|
+
export const getStatsByOriginLinkAdmin = async (
|
|
125
|
+
origin: ILinkOrigin,
|
|
126
|
+
range: IGetLinkStatsQuery = {},
|
|
127
|
+
): Promise<IGetStatsByOriginLinkAdminResponse> => {
|
|
128
|
+
return (
|
|
129
|
+
await AbyssLinkCore.axios.get<IGetStatsByOriginLinkAdminResponse>(`admin/link/origin/stats`, {
|
|
130
|
+
params: { ...origin, ...range },
|
|
131
|
+
})
|
|
132
|
+
).data;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Records a page view attributed to a campaign token; 404 for an unknown token. */
|
|
136
|
+
export const recordCampaignViewLinkAdmin = async (
|
|
137
|
+
body: IRecordCampaignViewLinkAdminBody,
|
|
138
|
+
): Promise<IRecordCampaignViewLinkAdminResponse> => {
|
|
139
|
+
return (
|
|
140
|
+
await AbyssLinkCore.axios.post<IRecordCampaignViewLinkAdminResponse>(
|
|
141
|
+
`admin/link/campaign-view`,
|
|
142
|
+
body,
|
|
143
|
+
)
|
|
144
|
+
).data;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** The campaign tokens of a product resource, oldest first. */
|
|
148
|
+
export const listCampaignTokensLinkAdmin = async (
|
|
149
|
+
origin: IListCampaignTokensLinkAdminQuery,
|
|
150
|
+
): Promise<IListCampaignTokensLinkAdminResponse> => {
|
|
151
|
+
return (
|
|
152
|
+
await AbyssLinkCore.axios.get<IListCampaignTokensLinkAdminResponse>(
|
|
153
|
+
`admin/link/campaign-token`,
|
|
154
|
+
{ params: origin },
|
|
155
|
+
)
|
|
156
|
+
).data;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** Deletes a campaign token; 404 for any other link. */
|
|
160
|
+
export const deleteCampaignTokenLinkAdmin = async (
|
|
161
|
+
params: IDeleteCampaignTokenLinkAdminParams,
|
|
162
|
+
): Promise<IDeleteCampaignTokenLinkAdminResponse> => {
|
|
163
|
+
return (
|
|
164
|
+
await AbyssLinkCore.axios.delete<IDeleteCampaignTokenLinkAdminResponse>(
|
|
165
|
+
`admin/link/campaign-token/${params.linkId}`,
|
|
166
|
+
)
|
|
167
|
+
).data;
|
|
168
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbyssLinkCore,
|
|
3
|
+
ICountOrganizationLinkParams,
|
|
4
|
+
ICountOrganizationLinkQuery,
|
|
5
|
+
ICountOrganizationLinkResponse,
|
|
6
|
+
ICreateForOrganizationLinkBody,
|
|
7
|
+
ICreateForOrganizationLinkParams,
|
|
8
|
+
ICreateLinkBody,
|
|
9
|
+
ICreateLinkResponse,
|
|
10
|
+
IDeleteLinkParams,
|
|
11
|
+
IDeleteLinkResponse,
|
|
12
|
+
IGetByOriginLinkQuery,
|
|
13
|
+
IGetByOriginLinkResponse,
|
|
14
|
+
IGetLinkParams,
|
|
15
|
+
IGetLinkResponse,
|
|
16
|
+
IGetLinkStatsParams,
|
|
17
|
+
IGetLinkStatsQuery,
|
|
18
|
+
IGetLinkStatsResponse,
|
|
19
|
+
IGetLinkVisitsParams,
|
|
20
|
+
IGetLinkVisitsQuery,
|
|
21
|
+
IGetLinkVisitsResponse,
|
|
22
|
+
IGetStatsByOriginLinkQuery,
|
|
23
|
+
IGetStatsByOriginLinkResponse,
|
|
24
|
+
IPaginateLinkQuery,
|
|
25
|
+
IPaginateLinkResponse,
|
|
26
|
+
IPaginateOrganizationLinkParams,
|
|
27
|
+
IPaginateOrganizationLinkQuery,
|
|
28
|
+
IPaginateProjectLinkParams,
|
|
29
|
+
ITransferLinkBody,
|
|
30
|
+
ITransferLinkParams,
|
|
31
|
+
ITransferLinkResponse,
|
|
32
|
+
IUpdateLinkBody,
|
|
33
|
+
IUpdateLinkParams,
|
|
34
|
+
IUpdateLinkResponse,
|
|
35
|
+
} from '..';
|
|
36
|
+
|
|
37
|
+
/** The caller's own SHORTENED links. */
|
|
38
|
+
export const paginateLink = async (query: IPaginateLinkQuery): Promise<IPaginateLinkResponse> => {
|
|
39
|
+
return (await AbyssLinkCore.axios.get<IPaginateLinkResponse>(`link/paginate`, { params: query }))
|
|
40
|
+
.data;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const paginateProjectLink = async (
|
|
44
|
+
params: IPaginateProjectLinkParams,
|
|
45
|
+
query: IPaginateLinkQuery,
|
|
46
|
+
): Promise<IPaginateLinkResponse> => {
|
|
47
|
+
return (
|
|
48
|
+
await AbyssLinkCore.axios.get<IPaginateLinkResponse>(
|
|
49
|
+
`link/project/${params.projectId}/paginate`,
|
|
50
|
+
{ params: query },
|
|
51
|
+
)
|
|
52
|
+
).data;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const paginateOrganizationLink = async (
|
|
56
|
+
params: IPaginateOrganizationLinkParams,
|
|
57
|
+
query: IPaginateOrganizationLinkQuery,
|
|
58
|
+
): Promise<IPaginateLinkResponse> => {
|
|
59
|
+
return (
|
|
60
|
+
await AbyssLinkCore.axios.get<IPaginateLinkResponse>(
|
|
61
|
+
`link/organization/${params.organizationId}/paginate`,
|
|
62
|
+
{ params: query },
|
|
63
|
+
)
|
|
64
|
+
).data;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const countOrganizationLink = async (
|
|
68
|
+
params: ICountOrganizationLinkParams,
|
|
69
|
+
query: ICountOrganizationLinkQuery = {},
|
|
70
|
+
): Promise<ICountOrganizationLinkResponse> => {
|
|
71
|
+
return (
|
|
72
|
+
await AbyssLinkCore.axios.get<ICountOrganizationLinkResponse>(
|
|
73
|
+
`link/organization/${params.organizationId}/count`,
|
|
74
|
+
{ params: query },
|
|
75
|
+
)
|
|
76
|
+
).data;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const getLink = async (params: IGetLinkParams): Promise<IGetLinkResponse> => {
|
|
80
|
+
return (await AbyssLinkCore.axios.get<IGetLinkResponse>(`link/${params.linkId}`)).data;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const createLink = async (body: ICreateLinkBody): Promise<ICreateLinkResponse> => {
|
|
84
|
+
return (await AbyssLinkCore.axios.post<ICreateLinkResponse>(`link`, body)).data;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const createForOrganizationLink = async (
|
|
88
|
+
params: ICreateForOrganizationLinkParams,
|
|
89
|
+
body: ICreateForOrganizationLinkBody,
|
|
90
|
+
): Promise<ICreateLinkResponse> => {
|
|
91
|
+
return (
|
|
92
|
+
await AbyssLinkCore.axios.post<ICreateLinkResponse>(
|
|
93
|
+
`link/organization/${params.organizationId}`,
|
|
94
|
+
body,
|
|
95
|
+
)
|
|
96
|
+
).data;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const updateLink = async (
|
|
100
|
+
params: IUpdateLinkParams,
|
|
101
|
+
body: IUpdateLinkBody,
|
|
102
|
+
): Promise<IUpdateLinkResponse> => {
|
|
103
|
+
return (await AbyssLinkCore.axios.put<IUpdateLinkResponse>(`link/${params.linkId}`, body)).data;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const deleteLink = async (params: IDeleteLinkParams): Promise<IDeleteLinkResponse> => {
|
|
107
|
+
return (await AbyssLinkCore.axios.delete<IDeleteLinkResponse>(`link/${params.linkId}`)).data;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const transferLink = async (
|
|
111
|
+
params: ITransferLinkParams,
|
|
112
|
+
body: ITransferLinkBody,
|
|
113
|
+
): Promise<ITransferLinkResponse> => {
|
|
114
|
+
return (
|
|
115
|
+
await AbyssLinkCore.axios.post<ITransferLinkResponse>(`link/${params.linkId}/transfer`, body)
|
|
116
|
+
).data;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const getStatsLink = async (
|
|
120
|
+
params: IGetLinkStatsParams,
|
|
121
|
+
query: IGetLinkStatsQuery = {},
|
|
122
|
+
): Promise<IGetLinkStatsResponse> => {
|
|
123
|
+
return (
|
|
124
|
+
await AbyssLinkCore.axios.get<IGetLinkStatsResponse>(`link/${params.linkId}/stats`, {
|
|
125
|
+
params: query,
|
|
126
|
+
})
|
|
127
|
+
).data;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const getVisitsLink = async (
|
|
131
|
+
params: IGetLinkVisitsParams,
|
|
132
|
+
query: IGetLinkVisitsQuery = {},
|
|
133
|
+
): Promise<IGetLinkVisitsResponse> => {
|
|
134
|
+
return (
|
|
135
|
+
await AbyssLinkCore.axios.get<IGetLinkVisitsResponse>(`link/${params.linkId}/visits`, {
|
|
136
|
+
params: query,
|
|
137
|
+
})
|
|
138
|
+
).data;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** The RESOURCE address of a product resource, from EDIT on the resource. */
|
|
142
|
+
export const getByOriginLink = async (
|
|
143
|
+
query: IGetByOriginLinkQuery,
|
|
144
|
+
): Promise<IGetByOriginLinkResponse> => {
|
|
145
|
+
return (await AbyssLinkCore.axios.get<IGetByOriginLinkResponse>(`link/origin`, { params: query }))
|
|
146
|
+
.data;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** The statistics of a product resource's RESOURCE address, from EDIT on the resource. */
|
|
150
|
+
export const getStatsByOriginLink = async (
|
|
151
|
+
query: IGetStatsByOriginLinkQuery,
|
|
152
|
+
): Promise<IGetStatsByOriginLinkResponse> => {
|
|
153
|
+
return (
|
|
154
|
+
await AbyssLinkCore.axios.get<IGetStatsByOriginLinkResponse>(`link/origin/stats`, {
|
|
155
|
+
params: query,
|
|
156
|
+
})
|
|
157
|
+
).data;
|
|
158
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbyssLinkCore,
|
|
3
|
+
IUnlockLinkPublicBody,
|
|
4
|
+
IUnlockLinkPublicParams,
|
|
5
|
+
IUnlockLinkPublicResponse,
|
|
6
|
+
} from '..';
|
|
7
|
+
|
|
8
|
+
/** Unlocks a shortened link protected by a password: the right password answers its destination. */
|
|
9
|
+
export const unlockLinkPublic = async ({
|
|
10
|
+
slug,
|
|
11
|
+
...body
|
|
12
|
+
}: IUnlockLinkPublicParams & IUnlockLinkPublicBody): Promise<IUnlockLinkPublicResponse> => {
|
|
13
|
+
return (
|
|
14
|
+
await AbyssLinkCore.axios.post<IUnlockLinkPublicResponse>(
|
|
15
|
+
`link/public/${encodeURIComponent(slug)}/unlock`,
|
|
16
|
+
body,
|
|
17
|
+
)
|
|
18
|
+
).data;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './link.constants';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { LinkResourceProduct } from '../types/enum/link.enum';
|
|
2
|
+
|
|
3
|
+
/** Products that own a RESOURCE address. Spotlight only receives campaign tokens, which have no Link address. */
|
|
4
|
+
export type LinkPrefixedProduct = Exclude<LinkResourceProduct, LinkResourceProduct.SPOTLIGHT>;
|
|
5
|
+
|
|
6
|
+
/** First segment of a RESOURCE address, `<host>/<prefix>/<code>` (spec §3.5). Compared in lowercase (R10). */
|
|
7
|
+
export const LINK_RESOURCE_PREFIXES: Readonly<Record<LinkPrefixedProduct, string>> = {
|
|
8
|
+
[LinkResourceProduct.BEAM]: 'b',
|
|
9
|
+
[LinkResourceProduct.ATLAS]: 'a',
|
|
10
|
+
[LinkResourceProduct.DRAW]: 'd',
|
|
11
|
+
[LinkResourceProduct.RETROSPECTIVE]: 'r',
|
|
12
|
+
[LinkResourceProduct.FORM]: 'f',
|
|
13
|
+
[LinkResourceProduct.EXTRACT]: 'e',
|
|
14
|
+
[LinkResourceProduct.CLOUD]: 'c',
|
|
15
|
+
[LinkResourceProduct.MEMORIES]: 'm',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** A SHORTENED slug starting with one of these (lowercase) is refused. */
|
|
19
|
+
export const LINK_FORBIDDEN_SLUG_PREFIXES: readonly string[] = ['_', 'abyss'];
|
|
20
|
+
|
|
21
|
+
/** SHORTENED slugs refused at manual creation, in lowercase: every resource prefix and the host's own paths. */
|
|
22
|
+
export const LINK_FORBIDDEN_SLUGS: readonly string[] = [
|
|
23
|
+
...Object.values(LINK_RESOURCE_PREFIXES),
|
|
24
|
+
'api',
|
|
25
|
+
'assets',
|
|
26
|
+
'static',
|
|
27
|
+
'health',
|
|
28
|
+
'admin',
|
|
29
|
+
'login',
|
|
30
|
+
'favicon.ico',
|
|
31
|
+
'robots.txt',
|
|
32
|
+
'.well-known',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const MIN_LINK_SLUG_LENGTH = 3;
|
|
36
|
+
export const MAX_LINK_SLUG_LENGTH = 50;
|
|
37
|
+
export const LINK_SLUG_PATTERN = /^[a-zA-Z0-9_-]{3,50}$/;
|
|
38
|
+
|
|
39
|
+
export const MIN_LINK_PASSWORD_LENGTH = 4;
|
|
40
|
+
export const MAX_LINK_PASSWORD_LENGTH = 128;
|
|
41
|
+
|
|
42
|
+
/** Base62 code of a RESOURCE address, compared exactly (R10). */
|
|
43
|
+
export const LINK_RESOURCE_CODE_LENGTH = 10;
|
|
44
|
+
/** Base62 campaign token. */
|
|
45
|
+
export const LINK_CAMPAIGN_TOKEN_LENGTH = 12;
|
|
46
|
+
/** Campaign tokens a product resource (a Spotlight project) holds at most (O5). */
|
|
47
|
+
export const LINK_CAMPAIGN_TOKENS_PER_PROJECT = 50;
|
|
48
|
+
/** The query parameter carrying a campaign token on the product's page: `?lnk=<token>`. */
|
|
49
|
+
export const CAMPAIGN_TOKEN_QUERY_PARAM = 'lnk';
|
|
50
|
+
|
|
51
|
+
/** Raw visits are kept this long (O2); daily aggregates live as long as the link. */
|
|
52
|
+
export const LINK_VISIT_RETENTION_DAYS = 90;
|
|
53
|
+
|
|
54
|
+
/** Schemes a link may target. */
|
|
55
|
+
export const LINK_ALLOWED_URL_SCHEMES = ['http', 'https'] as const;
|
|
56
|
+
/** Length of the slug drawn when a SHORTENED link is created without one. */
|
|
57
|
+
export const LINK_GENERATED_SLUG_LENGTH = 6;
|
|
58
|
+
export const MAX_LINK_TARGET_URL_LENGTH = 2048;
|
|
59
|
+
export const MAX_LINK_TITLE_LENGTH = 255;
|
|
60
|
+
export const MAX_LINK_DESCRIPTION_LENGTH = 1000;
|
|
61
|
+
export const MIN_LINK_MAX_CLICKS = 1;
|
|
62
|
+
|
|
63
|
+
export const MIN_LINK_PAGE = 1;
|
|
64
|
+
export const MIN_LINK_LIMIT = 1;
|
|
65
|
+
export const MAX_LINK_LIMIT = 100;
|
|
66
|
+
export const DEFAULT_LINK_LIMIT = 20;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isForbiddenLinkSlug,
|
|
3
|
+
LINK_FORBIDDEN_SLUG_PREFIXES,
|
|
4
|
+
LINK_FORBIDDEN_SLUGS,
|
|
5
|
+
LINK_RESOURCE_PREFIXES,
|
|
6
|
+
LINK_SLUG_PATTERN,
|
|
7
|
+
LinkResourceProduct,
|
|
8
|
+
MAX_LINK_SLUG_LENGTH,
|
|
9
|
+
MIN_LINK_SLUG_LENGTH,
|
|
10
|
+
} from '../..';
|
|
11
|
+
|
|
12
|
+
describe('LINK_RESOURCE_PREFIXES', () => {
|
|
13
|
+
it('maps the eight products of spec §3.5 to their prefix', () => {
|
|
14
|
+
expect(LINK_RESOURCE_PREFIXES).toEqual({
|
|
15
|
+
[LinkResourceProduct.BEAM]: 'b',
|
|
16
|
+
[LinkResourceProduct.ATLAS]: 'a',
|
|
17
|
+
[LinkResourceProduct.DRAW]: 'd',
|
|
18
|
+
[LinkResourceProduct.RETROSPECTIVE]: 'r',
|
|
19
|
+
[LinkResourceProduct.FORM]: 'f',
|
|
20
|
+
[LinkResourceProduct.EXTRACT]: 'e',
|
|
21
|
+
[LinkResourceProduct.CLOUD]: 'c',
|
|
22
|
+
[LinkResourceProduct.MEMORIES]: 'm',
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('gives Spotlight no prefix: a campaign token has no Link address', () => {
|
|
27
|
+
expect(Object.keys(LINK_RESOURCE_PREFIXES)).not.toContain(LinkResourceProduct.SPOTLIGHT);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('never gives two products the same prefix, and every prefix is lowercase', () => {
|
|
31
|
+
const prefixes = Object.values(LINK_RESOURCE_PREFIXES);
|
|
32
|
+
expect(new Set(prefixes).size).toBe(prefixes.length);
|
|
33
|
+
prefixes.forEach((prefix) => expect(prefix).toBe(prefix.toLowerCase()));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('LINK_FORBIDDEN_SLUGS', () => {
|
|
38
|
+
it.each(Object.values(LINK_RESOURCE_PREFIXES))('forbids the resource prefix %s', (prefix) => {
|
|
39
|
+
expect(LINK_FORBIDDEN_SLUGS).toContain(prefix);
|
|
40
|
+
expect(isForbiddenLinkSlug(prefix)).toBe(true);
|
|
41
|
+
expect(isForbiddenLinkSlug(prefix.toUpperCase())).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('holds the host paths of spec §3.5, in lowercase', () => {
|
|
45
|
+
expect(LINK_FORBIDDEN_SLUGS).toEqual(
|
|
46
|
+
expect.arrayContaining([
|
|
47
|
+
'api',
|
|
48
|
+
'assets',
|
|
49
|
+
'static',
|
|
50
|
+
'health',
|
|
51
|
+
'admin',
|
|
52
|
+
'login',
|
|
53
|
+
'favicon.ico',
|
|
54
|
+
'robots.txt',
|
|
55
|
+
'.well-known',
|
|
56
|
+
]),
|
|
57
|
+
);
|
|
58
|
+
LINK_FORBIDDEN_SLUGS.forEach((slug) => expect(slug).toBe(slug.toLowerCase()));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('forbids every slug starting with _ or abyss', () => {
|
|
62
|
+
expect(LINK_FORBIDDEN_SLUG_PREFIXES).toEqual(['_', 'abyss']);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('isForbiddenLinkSlug', () => {
|
|
67
|
+
it.each([
|
|
68
|
+
'_x',
|
|
69
|
+
'_',
|
|
70
|
+
'API',
|
|
71
|
+
'Api',
|
|
72
|
+
'Abyss-team',
|
|
73
|
+
'ABYSS',
|
|
74
|
+
'abyssal',
|
|
75
|
+
'.well-known',
|
|
76
|
+
'Favicon.ico',
|
|
77
|
+
'LOGIN',
|
|
78
|
+
])('forbids %s', (slug) => {
|
|
79
|
+
expect(isForbiddenLinkSlug(slug)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it.each(['abc', 'my-link', 'apis', 'x_abyss', 'B2B', 'admins'])('allows %s', (slug) => {
|
|
83
|
+
expect(isForbiddenLinkSlug(slug)).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('LINK_SLUG_PATTERN', () => {
|
|
88
|
+
it('accepts 3 to 50 characters of [a-zA-Z0-9_-]', () => {
|
|
89
|
+
expect(MIN_LINK_SLUG_LENGTH).toBe(3);
|
|
90
|
+
expect(MAX_LINK_SLUG_LENGTH).toBe(50);
|
|
91
|
+
expect(LINK_SLUG_PATTERN.test('a'.repeat(MIN_LINK_SLUG_LENGTH))).toBe(true);
|
|
92
|
+
expect(LINK_SLUG_PATTERN.test('a'.repeat(MAX_LINK_SLUG_LENGTH))).toBe(true);
|
|
93
|
+
expect(LINK_SLUG_PATTERN.test('My_Link-42')).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('refuses a slug too short, too long or with another character', () => {
|
|
97
|
+
expect(LINK_SLUG_PATTERN.test('a'.repeat(MIN_LINK_SLUG_LENGTH - 1))).toBe(false);
|
|
98
|
+
expect(LINK_SLUG_PATTERN.test('a'.repeat(MAX_LINK_SLUG_LENGTH + 1))).toBe(false);
|
|
99
|
+
expect(LINK_SLUG_PATTERN.test('my.link')).toBe(false);
|
|
100
|
+
expect(LINK_SLUG_PATTERN.test('my/link')).toBe(false);
|
|
101
|
+
expect(LINK_SLUG_PATTERN.test('lien-été')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|