@canmingir/link-express 1.6.11 → 1.7.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.
Files changed (56) hide show
  1. package/.eslintrc.ts +29 -0
  2. package/index.ts +2 -0
  3. package/package.json +52 -12
  4. package/{prepare.js → prepare.ts} +1 -1
  5. package/src/{authorization.js → authorization.ts} +31 -9
  6. package/src/config.ts +112 -0
  7. package/src/dynamodb.ts +24 -0
  8. package/src/{error.js → error.ts} +30 -11
  9. package/src/express.ts +66 -0
  10. package/src/lib/{settings.js → settings.ts} +16 -4
  11. package/src/lib/test.ts +68 -0
  12. package/src/{logger.js → logger.ts} +11 -9
  13. package/src/metrics/{dbMetrics.js → dbMetrics.ts} +37 -14
  14. package/src/models/Organization.model.ts +27 -0
  15. package/src/models/Permission.model.ts +48 -0
  16. package/src/models/Project.model.ts +50 -0
  17. package/src/models/Settings.model.ts +31 -0
  18. package/src/models/{index.js → index.ts} +8 -8
  19. package/src/platform.ts +55 -0
  20. package/src/postgres.ts +309 -0
  21. package/src/routes/index.ts +8 -0
  22. package/src/routes/metrics.ts +13 -0
  23. package/src/routes/oauth.ts +267 -0
  24. package/src/routes/{organizations.js → organizations.ts} +10 -8
  25. package/src/routes/{permissions.js → permissions.ts} +8 -6
  26. package/src/routes/{projects.js → projects.ts} +22 -16
  27. package/src/routes/settings.ts +31 -0
  28. package/src/schemas/{Organization.js → Organization.ts} +2 -2
  29. package/src/schemas/{Permission.js → Permission.ts} +2 -2
  30. package/src/schemas/{Project.js → Project.ts} +2 -2
  31. package/src/schemas/index.ts +5 -0
  32. package/src/sequelize.ts +13 -0
  33. package/src/{test.js → test.ts} +11 -13
  34. package/src/types/Organization.ts +9 -0
  35. package/src/types/Permission.ts +13 -0
  36. package/src/types/Project.ts +14 -0
  37. package/src/types/index.ts +5 -0
  38. package/tsconfig.json +32 -0
  39. package/.eslintrc.js +0 -20
  40. package/index.js +0 -1
  41. package/src/config.js +0 -21
  42. package/src/dynamodb.js +0 -18
  43. package/src/express.js +0 -58
  44. package/src/lib/test.js +0 -69
  45. package/src/models/Organization.js +0 -17
  46. package/src/models/Permission.js +0 -33
  47. package/src/models/Project.js +0 -37
  48. package/src/models/Settings.js +0 -21
  49. package/src/openapi.js +0 -40
  50. package/src/platform.js +0 -56
  51. package/src/postgres.js +0 -308
  52. package/src/routes/index.js +0 -15
  53. package/src/routes/metrics.js +0 -12
  54. package/src/routes/oauth.js +0 -213
  55. package/src/routes/settings.js +0 -25
  56. package/src/schemas/index.js +0 -5
package/.eslintrc.ts ADDED
@@ -0,0 +1,29 @@
1
+ export default {
2
+ env: {
3
+ es6: true,
4
+ node: true,
5
+ mocha: true,
6
+ },
7
+ parser: "@typescript-eslint/parser",
8
+ parserOptions: {
9
+ ecmaVersion: 2022,
10
+ sourceType: "module",
11
+ project: "./tsconfig.json",
12
+ },
13
+ plugins: ["@typescript-eslint"],
14
+ extends: [
15
+ "eslint:recommended",
16
+ "plugin:@typescript-eslint/recommended",
17
+ "plugin:prettier/recommended",
18
+ ],
19
+ ignorePatterns: ["/node_modules", "/dist"],
20
+ rules: {
21
+ eqeqeq: ["error", "always"],
22
+ "no-console": "off",
23
+ "no-eval": "error",
24
+ "no-var": "error",
25
+ "prefer-arrow-callback": "error",
26
+ "@typescript-eslint/no-explicit-any": "error",
27
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
28
+ },
29
+ };
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ import * as platform from "./src/platform";
2
+ export default platform;
package/package.json CHANGED
@@ -1,22 +1,48 @@
1
1
  {
2
2
  "name": "@canmingir/link-express",
3
- "version": "1.6.11",
3
+ "version": "1.7.0",
4
4
  "description": "",
5
- "main": "index.js",
5
+ "main": "index.ts",
6
+ "types": "index.ts",
7
+ "typesVersions": {
8
+ "*": {
9
+ "models": [
10
+ "src/models/index.ts"
11
+ ],
12
+ "test": [
13
+ "src/test.ts"
14
+ ],
15
+ "authorization": [
16
+ "src/authorization.ts"
17
+ ],
18
+ "error": [
19
+ "src/error.ts"
20
+ ],
21
+ "types": [
22
+ "src/types/index.ts"
23
+ ],
24
+ "sequelize": [
25
+ "src/sequelize.ts"
26
+ ]
27
+ }
28
+ },
6
29
  "author": "NucTeam",
7
30
  "exports": {
8
- ".": "./index.js",
9
- "./models": "./src/models/index.js",
10
- "./test": "./src/test.js",
11
- "./authorization": "./src/authorization.js",
12
- "./error": "./src/error.js"
31
+ ".": "./index.ts",
32
+ "./models": "./src/models/index.ts",
33
+ "./test": "./src/test.ts",
34
+ "./authorization": "./src/authorization.ts",
35
+ "./error": "./src/error.ts",
36
+ "./types": "./src/types/index.ts",
37
+ "./sequelize": "./src/sequelize.ts"
13
38
  },
14
39
  "scripts": {
15
- "prepare": "node prepare.js",
40
+ "prepare": "tsx prepare.ts",
41
+ "build": "tsc",
16
42
  "dev": "node server --dev",
17
43
  "start": "node server",
18
44
  "test": "echo 'No tests'",
19
- "lint": "eslint . --ext .js"
45
+ "lint": "eslint . --ext .ts"
20
46
  },
21
47
  "dependencies": {
22
48
  "@aws-sdk/client-dynamodb": "^3.614.0",
@@ -37,12 +63,24 @@
37
63
  "pino": "^10.1.0",
38
64
  "pino-elasticsearch": "^8.1.0",
39
65
  "prom-client": "^15.1.3",
40
- "sequelize": "^6.37.3",
66
+ "sequelize": "^6.37.7",
67
+ "sequelize-typescript": "^2.1.6",
41
68
  "swagger-jsdoc": "^6.2.8",
42
- "swagger-ui-express": "^5.0.1",
43
69
  "uuid": "^10.0.0"
44
70
  },
45
71
  "devDependencies": {
72
+ "@types/cors": "^2.8.19",
73
+ "@types/express": "^5.0.5",
74
+ "@types/jsonwebtoken": "^9.0.10",
75
+ "@types/lodash": "^4.17.21",
76
+ "@types/morgan": "^1.9.10",
77
+ "@types/node": "^24.10.1",
78
+ "@types/pg": "^8.15.6",
79
+ "@types/swagger-jsdoc": "^6.0.4",
80
+ "@types/swagger-ui-express": "^4.1.8",
81
+ "@types/uuid": "^10.0.0",
82
+ "@typescript-eslint/eslint-plugin": "^8.48.0",
83
+ "@typescript-eslint/parser": "^8.48.0",
46
84
  "axios-mock-adapter": "^1.21.1",
47
85
  "eslint": "^8.57.1",
48
86
  "eslint-config-prettier": "^8.5.0",
@@ -50,6 +88,8 @@
50
88
  "mocha": "10.8.2",
51
89
  "prettier": "^2.6.2",
52
90
  "sqlite3": "^5.1.6",
53
- "supertest": "^7.0.0"
91
+ "supertest": "^7.0.0",
92
+ "tsx": "^4.20.6",
93
+ "typescript": "^5.9.3"
54
94
  }
55
95
  }
@@ -1,4 +1,4 @@
1
- const fs = require("fs");
1
+ import fs from "fs";
2
2
 
3
3
  const prePush = `#!/usr/bin/env node
4
4
  const { execSync } = require("child_process");
@@ -1,6 +1,22 @@
1
- const jwt = require("jsonwebtoken");
2
- const { AuthorizationError } = require("./error");
3
- function verify(req, res, next) {
1
+ import jwt from "jsonwebtoken";
2
+ import { Request, Response, NextFunction } from "express";
3
+ import { AuthorizationError } from "./error";
4
+
5
+ interface Session {
6
+ projectId: string;
7
+ userId: string;
8
+ roles: string[];
9
+ appId: string;
10
+ organizationId: string;
11
+ }
12
+
13
+ declare module "express-serve-static-core" {
14
+ interface Request {
15
+ session: Session;
16
+ }
17
+ }
18
+
19
+ function verify(req: Request, _res: Response, next: NextFunction): void {
4
20
  if (process.env.PROFILE === "TEST") {
5
21
  switch (process.env.PROJECT_ID) {
6
22
  case "0c756054-2d28-4f87-9b12-8023a79136a5":
@@ -69,8 +85,15 @@ function verify(req, res, next) {
69
85
  try {
70
86
  const { sub, aud, rls, aid, oid } = jwt.verify(
71
87
  token,
72
- process.env.JWT_SECRET
73
- );
88
+ process.env.JWT_SECRET as string
89
+ ) as {
90
+ sub: string;
91
+ aud: string;
92
+ rls: string[];
93
+ aid: string;
94
+ oid: string;
95
+ };
96
+
74
97
  req.session = {
75
98
  projectId: aud,
76
99
  userId: sub,
@@ -85,9 +108,8 @@ function verify(req, res, next) {
85
108
  next();
86
109
  }
87
110
 
88
- // eslint-disable-next-line no-unused-vars
89
- function authorize(role) {
90
- return (req, res, next) => {
111
+ function authorize(_role: string) {
112
+ return (req: Request, _res: Response, next: NextFunction): void => {
91
113
  const { roles } = req.session;
92
114
 
93
115
  // TODO Add expression check for role
@@ -99,4 +121,4 @@ function authorize(role) {
99
121
  };
100
122
  }
101
123
 
102
- module.exports = { verify, authorize };
124
+ export { verify, authorize };
package/src/config.ts ADDED
@@ -0,0 +1,112 @@
1
+ import _ from "lodash";
2
+
3
+ interface LoggerConfig {
4
+ elasticsearch: {
5
+ index: string;
6
+ consistency?: string;
7
+ node: string;
8
+ esVersion?: number;
9
+ flushBytes?: number;
10
+ };
11
+ }
12
+
13
+ interface ProjectConfig {
14
+ name: string;
15
+ version: string;
16
+ oauth?: {
17
+ jwt: {
18
+ identifier: string;
19
+ };
20
+ providers: Record<
21
+ string,
22
+ {
23
+ tokenUrl: string;
24
+ userUrl: string;
25
+ clientId: string;
26
+ redirectUri?: string;
27
+ userIdentifier: string;
28
+ userFields: {
29
+ name: string;
30
+ displayName: string;
31
+ avatarUrl: string;
32
+ email: string;
33
+ };
34
+ }
35
+ >;
36
+ };
37
+ }
38
+
39
+ interface LinkConfig {
40
+ [key: string]: unknown;
41
+ }
42
+
43
+ interface OpenapiConfig {
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ interface PostgresConfig {
48
+ uri: string;
49
+ debug?: boolean;
50
+ sync?: boolean;
51
+ }
52
+
53
+ interface DynamodbConfig {
54
+ region: string;
55
+ }
56
+
57
+ interface PushGatewayConfig {
58
+ url?: string;
59
+ jobName?: string;
60
+ instance?: string;
61
+ interval?: number;
62
+ }
63
+
64
+ interface MetricsConfig {
65
+ enabled: boolean;
66
+ url?: string;
67
+ pushGateway: PushGatewayConfig;
68
+ interval?: number;
69
+ }
70
+
71
+ interface Config {
72
+ link: LinkConfig;
73
+ project: ProjectConfig | null;
74
+ openapi: OpenapiConfig;
75
+ postgres: PostgresConfig | null;
76
+ dynamodb: DynamodbConfig | null;
77
+ pushGateway: PushGatewayConfig;
78
+ title?: string;
79
+ version?: string;
80
+ logger?: LoggerConfig;
81
+ metrics?: MetricsConfig;
82
+ }
83
+
84
+ let _config: Config = {
85
+ link: {},
86
+ project: null,
87
+ openapi: {},
88
+ postgres: null,
89
+ dynamodb: null,
90
+ pushGateway: {},
91
+ };
92
+
93
+ function init(config: Partial<Config> = {}): Config {
94
+ _config = _.merge(
95
+ {
96
+ link: {},
97
+ project: null,
98
+ openapi: {},
99
+ postgres: null,
100
+ dynamodb: null,
101
+ pushGateway: {},
102
+ },
103
+ config
104
+ );
105
+
106
+ return _config;
107
+ }
108
+
109
+ const getConfig = (): Config => _config;
110
+
111
+ export { getConfig, init, Config };
112
+ export default getConfig;
@@ -0,0 +1,24 @@
1
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
+ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
3
+ import { getConfig } from "./config";
4
+ import dotenv from "dotenv";
5
+
6
+ dotenv.config();
7
+
8
+ const { dynamodb } = getConfig();
9
+
10
+ if (!dynamodb) {
11
+ throw new Error("DynamoDB configuration is required");
12
+ }
13
+
14
+ const client = new DynamoDBClient({
15
+ region: dynamodb.region,
16
+ credentials: {
17
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
18
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
19
+ },
20
+ });
21
+
22
+ const docClient = DynamoDBDocumentClient.from(client);
23
+
24
+ export { docClient };
@@ -1,7 +1,31 @@
1
- const { ValidationError } = require("joi");
1
+ import { ValidationError } from "joi";
2
+ import { Request, Response, NextFunction } from "express";
2
3
 
3
- // eslint-disable-next-line no-unused-vars
4
- const handle = (err, req, res, next) => {
4
+ interface AxiosError extends Error {
5
+ isAxiosError: boolean;
6
+ response?: {
7
+ status: number;
8
+ };
9
+ }
10
+
11
+ interface ErrorWithError {
12
+ error: unknown;
13
+ }
14
+
15
+ const handle = (
16
+ err:
17
+ | string
18
+ | ValidationError
19
+ | AuthorizationError
20
+ | AuthenticationError
21
+ | NotFoundError
22
+ | AxiosError
23
+ | ErrorWithError
24
+ | Error,
25
+ _req: Request,
26
+ res: Response,
27
+ _next: NextFunction
28
+ ): Response => {
5
29
  if (typeof err === "string") {
6
30
  return res.status(400).json({ error: err });
7
31
  }
@@ -22,11 +46,11 @@ const handle = (err, req, res, next) => {
22
46
  return res.status(404).end();
23
47
  }
24
48
 
25
- if (err.isAxiosError) {
49
+ if ("isAxiosError" in err && err.isAxiosError) {
26
50
  return res.status(err.response?.status || 503).end();
27
51
  }
28
52
 
29
- if (err.error) {
53
+ if ("error" in err && err.error) {
30
54
  return res.status(400).json(err);
31
55
  } else {
32
56
  console.error(err);
@@ -38,9 +62,4 @@ class AuthorizationError extends Error {}
38
62
  class AuthenticationError extends Error {}
39
63
  class NotFoundError extends Error {}
40
64
 
41
- module.exports = {
42
- handle,
43
- AuthorizationError,
44
- NotFoundError,
45
- AuthenticationError,
46
- };
65
+ export { handle, AuthorizationError, NotFoundError, AuthenticationError };
package/src/express.ts ADDED
@@ -0,0 +1,66 @@
1
+ import express, { Request, Response, NextFunction } from "express";
2
+ import "express-async-errors";
3
+ import cors from "cors";
4
+ import morgan from "morgan";
5
+ import helmet from "helmet";
6
+ import * as error from "./error";
7
+ import * as authorization from "./authorization";
8
+ import settings from "./routes/settings";
9
+ import metrics from "./routes/metrics";
10
+ import { getConfig } from "./config";
11
+
12
+ const app = express();
13
+
14
+ const appConfig = getConfig();
15
+
16
+ app.use(helmet());
17
+ app.use(cors());
18
+ app.use(morgan("tiny"));
19
+
20
+ app.use(
21
+ express.json(),
22
+ (err: Error, _req: Request, res: Response, next: NextFunction) =>
23
+ err ? res.status(422).end() : next()
24
+ );
25
+
26
+ if (appConfig.project) {
27
+ import("./routes/oauth.ts").then((oauthModule) => {
28
+ const oauth = oauthModule.default || oauthModule;
29
+ app.use(
30
+ "/oauth",
31
+ express.urlencoded(),
32
+ (err: Error, _req: Request, res: Response, next: NextFunction) =>
33
+ err ? res.status(422).end() : next(),
34
+ oauth
35
+ );
36
+ });
37
+ }
38
+
39
+ app.use("/metrics", metrics);
40
+
41
+ setImmediate(async () => {
42
+ process.env.PROFILE === "TEST" && app.use(authorization.verify);
43
+
44
+ if (appConfig.project) {
45
+ const [permissionsModule, organizationsModule, projectsModule] =
46
+ await Promise.all([
47
+ import("./routes/permissions.ts"),
48
+ import("./routes/organizations.ts"),
49
+ import("./routes/projects.ts"),
50
+ ]);
51
+
52
+ const permissions = permissionsModule.default || permissionsModule;
53
+ const organizations = organizationsModule.default || organizationsModule;
54
+ const projects = projectsModule.default || projectsModule;
55
+
56
+ app.use("/projects", projects);
57
+ app.use("/organizations", organizations);
58
+ app.use("/permissions", permissions);
59
+ app.use("/projects/:projectId/settings", settings);
60
+ }
61
+
62
+ app.use((_req: Request, res: Response) => res.status(404).end());
63
+ app.use(error.handle);
64
+ });
65
+
66
+ export default app;
@@ -1,6 +1,10 @@
1
- const Settings = require("../models/Settings");
1
+ import Settings from "../models/Settings.model";
2
2
 
3
- async function get({ projectId }) {
3
+ async function get({
4
+ projectId,
5
+ }: {
6
+ projectId: string;
7
+ }): Promise<Record<string, unknown>> {
4
8
  const settingsInstance = await Settings.findOne({
5
9
  where: { projectId },
6
10
  });
@@ -12,7 +16,15 @@ async function get({ projectId }) {
12
16
  }
13
17
  }
14
18
 
15
- async function upsert({ projectId }, settings) {
19
+ async function upsert(
20
+ {
21
+ projectId,
22
+ }: {
23
+ projectId: string;
24
+ },
25
+ settings: Record<string, unknown>,
26
+ _params?: Record<string, unknown>
27
+ ): Promise<void> {
16
28
  const settingsInstance = await Settings.findOne({
17
29
  where: { projectId },
18
30
  });
@@ -33,4 +45,4 @@ async function upsert({ projectId }, settings) {
33
45
  }
34
46
  }
35
47
 
36
- module.exports = { get, upsert };
48
+ export { get, upsert };
@@ -0,0 +1,68 @@
1
+ import dotenv from "dotenv";
2
+ import platform from "../platform";
3
+ import { Sequelize } from "sequelize";
4
+
5
+ dotenv.config({ path: ".env.test" });
6
+
7
+ platform.init({
8
+ project: {
9
+ name: "test",
10
+ version: "1.0.0",
11
+ oauth: {
12
+ jwt: {
13
+ identifier: "email",
14
+ },
15
+ providers: {
16
+ github: {
17
+ tokenUrl: "https://github.com/login/oauth/access_token",
18
+ userUrl: "https://api.github.com/user",
19
+ clientId: "0c2844d3d19dc9293fc5",
20
+ redirectUri: "http://localhost:5173/callback",
21
+ userIdentifier: "id",
22
+ userFields: {
23
+ name: "name",
24
+ displayName: "login",
25
+ avatarUrl: "avatar_url",
26
+ email: "email",
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ postgres: {
33
+ uri: "sqlite::memory:",
34
+ debug: true,
35
+ sync: false,
36
+ },
37
+ });
38
+
39
+ import { init } from "../models";
40
+
41
+ async function reset(): Promise<void> {
42
+ const { sequelize }: { sequelize: Sequelize } = await import("../postgres");
43
+
44
+ if (await init()) {
45
+ await sequelize.sync({ force: true });
46
+ }
47
+
48
+ await sequelize.models.Organization.destroy({ truncate: true });
49
+ await sequelize.models.Project.destroy({ truncate: true });
50
+ await sequelize.models.Permission.destroy({ truncate: true });
51
+ await sequelize.models.Settings.destroy({ truncate: true });
52
+
53
+ async function seed(): Promise<void> {
54
+ const { seed: organizations } = await import("../seeds/Organization.json");
55
+ const { seed: permissions } = await import("../seeds/Permission.json");
56
+ const { seed: projects } = await import("../seeds/Project.json");
57
+ const { seed: settings } = await import("../seeds/Settings.json");
58
+
59
+ await sequelize.models.Organization.bulkCreate(organizations);
60
+ await sequelize.models.Project.bulkCreate(projects);
61
+ await sequelize.models.Permission.bulkCreate(permissions);
62
+ await sequelize.models.Settings.bulkCreate(settings);
63
+ }
64
+
65
+ await seed();
66
+ }
67
+
68
+ export { reset };
@@ -1,20 +1,22 @@
1
- const { ecsFormat } = require("@elastic/ecs-pino-format");
2
- const pino = require("pino");
3
- const pinoElastic = require("pino-elasticsearch");
1
+ import { ecsFormat } from "@elastic/ecs-pino-format";
2
+ import pino from "pino";
3
+ import pinoElastic from "pino-elasticsearch";
4
+ import { getConfig } from "./config";
4
5
 
5
- const config = require("./config");
6
+ const { logger: loggerConfig, project } = getConfig();
6
7
 
7
- const { logger: loggerConfig, project } = config();
8
+ if (!loggerConfig || !project) {
9
+ throw new Error("Logger and project configuration are required");
10
+ }
8
11
 
9
- const streams = [{ stream: process.stdout }];
12
+ const streams: pino.StreamEntry[] = [{ stream: process.stdout }];
10
13
 
11
14
  const streamToElastic = pinoElastic({
12
15
  index: loggerConfig.elasticsearch.index,
13
- consistency: loggerConfig.elasticsearch.consistency || "one",
14
16
  node: loggerConfig.elasticsearch.node,
15
17
  esVersion: loggerConfig.elasticsearch.esVersion || 8,
16
18
  flushBytes: loggerConfig.elasticsearch.flushBytes || 1000,
17
- });
19
+ } as Parameters<typeof pinoElastic>[0]);
18
20
 
19
21
  streams.push({ stream: streamToElastic });
20
22
 
@@ -31,4 +33,4 @@ const logger = pino(
31
33
  pino.multistream(streams)
32
34
  );
33
35
 
34
- module.exports = logger;
36
+ export default logger;