diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..19aae8c --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,37 @@ +component_management: + default_rules: + statuses: + - type: project + target: auto + - type: patch + target: auto + individual_components: + - component_id: server + name: Server + paths: + - "apps/server/src/**" + - component_id: config + name: Config + paths: + - libs/config/src/** + - component_id: web + name: Web + paths: + - libs/web/src/** + - component_id: utils + name: Utils + paths: + - libs/util/src/** + - component_id: all_tests + name: All Tests + paths: + - "apps/*/test/**" + - "libs/*/test/**" + +comment: + layout: diff, flags, components, files, header, footer + hide_project_coverage: false + show_carryforward_flags: true + +test_analytics: + flake_detection: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9975b6f..e22dcf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: CI +name: Affected CI on: push: branches: - - main + - !main + - !develop pull_request: permissions: actions: read contents: read - jobs: main: runs-on: ubuntu-22.04 @@ -26,7 +26,8 @@ jobs: # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun # Connect your workspace by running "nx connect" and uncomment this # - run: pnpm dlx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build" - + # env: + # NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} # Cache node_modules - uses: actions/setup-node@v3 with: @@ -35,19 +36,21 @@ jobs: - run: pnpm install --frozen-lockfile - uses: nrwl/nx-set-shas@v4 - + env: + NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud # - run: pnpm exec nx-cloud record -- echo Hello World # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected - run: make test env: NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} + - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} + run: make codecov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/full_tests.yml b/.github/workflows/full_tests.yml new file mode 100644 index 0000000..e0d7bd1 --- /dev/null +++ b/.github/workflows/full_tests.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: + - main + - develop + pull_request: + +permissions: + actions: read + contents: read + +jobs: + main: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v2 + + # This enables task distribution via Nx Cloud + # Run this command as early as possible, before dependencies are installed + # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun + # Connect your workspace by running "nx connect" and uncomment this + # - run: pnpm dlx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build" + # env: + # NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} + # Cache node_modules + - uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "pnpm" + + - run: pnpm install --frozen-lockfile + - uses: nrwl/nx-set-shas@v4 + env: + NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} + # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud + # - run: pnpm exec nx-cloud record -- echo Hello World + # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected + - run: make test_all + env: + NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload coverage reports to Codecov + run: make codecov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 535b02e..5111138 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,4 @@ node_modules coverage *.xml -# tell Git to ignore/stop ignoring this file -#$ git update-index --assume-unchanged -#$ git update-index --no-assume-unchanged -# Credit: https://web.archive.org/web/20240726013834/https://practicalgit.com/blog/make-git-ignore-local-changes-to-tracked-files.html -nx.json +.env diff --git a/Makefile b/Makefile index 30e6c75..4b6957d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,23 @@ test: - ./scripts/inject-token.sh - pnpm exec nx affected -t lint build test + pnpm exec nx affected -t lint test build -@PHONY: test \ No newline at end of file +test_all: + pnpm exec nx run-many --target=test --all + +codecov_install: + pip install codecov-cli + +codecov: codecov_install + codecovcli --verbose upload-process --fail-on-error -n Server -F Server -f apps/server/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Config -F Config -f libs/config/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Utils -F Utils -f libs/utils/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Web -F Web -f libs/web/coverage/lcov.info + +codecov_local: + codecovcli --verbose upload-process --fail-on-error -n Server -F Server -f apps/server/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Config -F Config -f libs/config/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Utils -F Utils -f libs/utils/coverage/lcov.info + codecovcli --verbose upload-process --fail-on-error -n Web -F Web -f libs/web/coverage/lcov.info + + +@PHONY: test codecov_install codecov \ No newline at end of file diff --git a/apps/server/.env.example b/apps/server/.env.example new file mode 100644 index 0000000..6b7e904 --- /dev/null +++ b/apps/server/.env.example @@ -0,0 +1,2 @@ +NX_CLOUD_AUTH_TOKEN= +SENTRY_DSN= \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json index 74d475f..9adb4d7 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,7 +8,8 @@ "build": "tsc --build --verbose", "check-types": "tsc --noEmit", "lint": "eslint src", - "start": "node dist/src/index.js", + "prestart": "nx run @rusty/server:build", + "start": "node --env-file .env dist/src/index.js", "test": "vitest", "dev": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' --watch src/index.ts" }, @@ -30,6 +31,8 @@ }, "dependencies": { "@rusty/util": "workspace:*", - "@rusty/web": "workspace:*" + "@rusty/web": "workspace:*", + "@sentry/node": "^8.20.0", + "@sentry/profiling-node": "^8.20.0" } } diff --git a/apps/server/src/ServerController.ts b/apps/server/src/ServerController.ts index d242c5c..d1ab4f7 100644 --- a/apps/server/src/ServerController.ts +++ b/apps/server/src/ServerController.ts @@ -4,7 +4,7 @@ import { ConsoleThread } from "./ConsoleThread.js"; import { WrappedServer } from "./WrappedServer.js"; import { exit } from "node:process"; import { log } from "@rusty/util"; -import { handleWebRequests } from "@rusty/web"; +import { processWebRequests } from "@rusty/web"; import { headersToRecords } from "./headersToRecords.js"; /** @@ -16,7 +16,7 @@ async function handleIncomingRequest( req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse ) { - const response = await handleWebRequests({ + const response = await processWebRequests({ headers: headersToRecords(req.headers), remoteAddress: req.socket.remoteAddress || "", method: req.method || "", diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 232b19f..18854cc 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,4 +1,27 @@ +// IMPORTANT: Make sure to import `instrument.js` at the top of your file. +// If you're using ECMAScript Modules (ESM) syntax, use `import "./instrument.js";` +void import ("./instrument.js"); + +// All other imports below +import * as Sentry from "@sentry/node"; import { ServerController } from "./ServerController.js"; -const serverController = new ServerController(); -void serverController.start(); +process.on("unhandledRejection", (reason, promise) => { + console.error("Unhandled Rejection at:", promise, "reason:", reason); + Sentry.captureException(reason as Error); + process.exit(1); + }) + +process.on("uncaughtException", (error) => { + console.error("Uncaught Exception thrown", error); + Sentry.captureException(error); + process.exit(1); + }) + +function main() { + const serverController = new ServerController(); + void serverController.start(); +} + +main(); + diff --git a/apps/server/src/instrument.ts b/apps/server/src/instrument.ts new file mode 100644 index 0000000..ae710ae --- /dev/null +++ b/apps/server/src/instrument.ts @@ -0,0 +1,18 @@ +// Import with `import * as Sentry from "@sentry/node"` if you are using ESM +import * as Sentry from "@sentry/node"; +import { nodeProfilingIntegration } from "@sentry/profiling-node"; + +if (typeof process.env.SENTRY_DSN === "undefined" || process.env.SENTRY_DSN === "") { + console.error("No SENTRY_DSN provided"); + process.exit(1); +} + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + integrations: [nodeProfilingIntegration()], + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions + + // Set sampling rate for profiling - this is relative to tracesSampleRate + profilesSampleRate: 1.0, +}); diff --git a/libs/config/eslint.config.js b/libs/config/eslint.config.js new file mode 100644 index 0000000..ec3eb89 --- /dev/null +++ b/libs/config/eslint.config.js @@ -0,0 +1,24 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ["eslint.config.js", "vitest.config.js", "**/*.d.ts"], + ...tseslint.configs.disableTypeChecked, + }, + { + ignores: ["**/coverage/*", "**/dist/*"], + } +); diff --git a/libs/config/index.ts b/libs/config/index.ts new file mode 100644 index 0000000..76d3657 --- /dev/null +++ b/libs/config/index.ts @@ -0,0 +1 @@ +export { getServerURL } from "./src/index.js"; \ No newline at end of file diff --git a/libs/config/package.json b/libs/config/package.json new file mode 100644 index 0000000..457a2ca --- /dev/null +++ b/libs/config/package.json @@ -0,0 +1,42 @@ +{ + "name": "@rusty/config", + "version": "1.0.0", + "private": true, + "type": "module", + "imports": { + "#internal": "./dist/src/index.js" + }, + "exports": { + ".": "./dist/index.js" + }, + "types": "./dist/index.d.ts", + "scripts": { + "clean": "rm -rf dist", + "build": "tsc --build --verbose", + "test": "vitest", + "check-types": "tsc --noEmit", + "lint": "eslint src" + }, + "keywords": [], + "author": "", + "license": "GPL-3.0-only", + "devDependencies": { + "@types/node": "20.14.12", + "@types/pino": "7.0.5", + "@vitest/coverage-v8": "^2.0.4", + "@vitest/ui": "^2.0.4", + "eslint": "^8.57.0", + "nx": "19.5.1", + "ts-node": "^10.9.2", + "tslib": "^2.6.3", + "typescript": "^5.5.4", + "vite": "2.0.4", + "vitest": "^2.0.4" + }, + "dependencies": { + "@rusty/util": "workspace:*", + "@sentry/node": "^8.20.0", + "@sentry/profiling-node": "^8.20.0", + "pino": "9.3.1" + } +} diff --git a/libs/config/src/config.ts b/libs/config/src/config.ts new file mode 100644 index 0000000..27e5de8 --- /dev/null +++ b/libs/config/src/config.ts @@ -0,0 +1,4 @@ +export function getServerURL(): string { + const serverURL = process.env.SERVER_URL ?? "https://rusty-motors.com"; + return serverURL; +} diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts new file mode 100644 index 0000000..5ffa937 --- /dev/null +++ b/libs/config/src/index.ts @@ -0,0 +1 @@ +export { getServerURL } from "./config.js"; \ No newline at end of file diff --git a/libs/config/src/types.ts b/libs/config/src/types.ts new file mode 100644 index 0000000..c60f7d1 --- /dev/null +++ b/libs/config/src/types.ts @@ -0,0 +1,24 @@ +export type rawHttpRequestData = { + headers: Record; + remoteAddress: string; + method: string; + url: string; +}; + +export type parsedHttpRequestData = { + headers: Record; + remoteAddress: string; + method: string; + pathname: string; + searchParams: URLSearchParams; +}; +export type RequestResponse = { + statusCode: number; + body: string; + headers: Record; +}; + +export type User = { + username: string; + password: string; +}; diff --git a/libs/config/test/config.test.ts b/libs/config/test/config.test.ts new file mode 100644 index 0000000..cea153f --- /dev/null +++ b/libs/config/test/config.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import { getServerURL } from "@rusty/config"; + +describe("config", () => { + it("should have a default serverURL if SERVER_URL environment variable is not set", () => { + expect(getServerURL()).toBe("https://rusty-motors.com"); + }); + + it("should use the SERVER_URL environment variable if set", () => { + process.env.SERVER_URL = "https://example.com"; + expect(getServerURL()).toBe("https://example.com"); + }); +}); diff --git a/libs/config/tsconfig.json b/libs/config/tsconfig.json new file mode 100644 index 0000000..6572291 --- /dev/null +++ b/libs/config/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declarationDir": "./dist", + "composite": true + }, + "include": ["index.ts", "src/**/*.ts", "test/**/*.ts"], + "exclude": ["node_modules", "dist"], + "references": [{ "path": "../util" }] +} diff --git a/libs/config/vitest.config.js b/libs/config/vitest.config.js new file mode 100644 index 0000000..c1aed66 --- /dev/null +++ b/libs/config/vitest.config.js @@ -0,0 +1,20 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + enabled: true, + all: true, + exclude: [ + "dist/**", + "eslint.config.js", + "vitest.config.js" + ], + reporter: ["lcov", "text", "cobertura"], + }, + reporters: ["junit", "default", "hanging-process"], + outputFile: "mcos.junit.xml", + pool: "forks", + watch: false, + }, +}); \ No newline at end of file diff --git a/libs/util/package.json b/libs/util/package.json index c18b3f7..a1bdb3d 100644 --- a/libs/util/package.json +++ b/libs/util/package.json @@ -31,6 +31,8 @@ "vitest": "^2.0.4" }, "dependencies": { + "@sentry/node": "^8.20.0", + "@sentry/profiling-node": "^8.20.0", "pino": "9.3.1" } } diff --git a/libs/web/index.ts b/libs/web/index.ts index cdf9f2a..e755951 100644 --- a/libs/web/index.ts +++ b/libs/web/index.ts @@ -1 +1,9 @@ -export { handleWebRequests } from "./src/index.js"; +/** + * External exports for the web library + */ +export { processWebRequests } from "./src/processors/processWebRequests.js"; +export { Session } from "./src/models/Session.js"; +export type { + SessionCreationAttributes, + SessionAttributes, +} from "./src/models/Session.js"; diff --git a/libs/web/package.json b/libs/web/package.json index 157afe6..174b5a1 100644 --- a/libs/web/package.json +++ b/libs/web/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "private": true, "type": "module", + "imports": { + "#internal": "./dist/src/index.js" + }, "exports": { ".": "./dist/index.js" }, @@ -32,6 +35,9 @@ }, "dependencies": { "@rusty/util": "workspace:*", + "@sentry/node": "^8.20.0", + "@sentry/profiling-node": "^8.20.0", + "@rusty/config": "workspace:*", "pino": "9.3.1", "sequelize": "6.37.3", "sqlite3": "5.1.7" diff --git a/libs/web/src/db.ts b/libs/web/src/db.ts index 12143ec..831a955 100644 --- a/libs/web/src/db.ts +++ b/libs/web/src/db.ts @@ -5,7 +5,7 @@ const DATABASE_URL = process.env.DATABASE_URL || ":memory:"; export const db = new Sequelize({ dialect: "sqlite", storage: DATABASE_URL, - logging: console.log, // Enable logging + logging: false, }); db.authenticate() diff --git a/libs/web/src/errors.ts b/libs/web/src/errors.ts deleted file mode 100644 index e984c25..0000000 --- a/libs/web/src/errors.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { log } from "@rusty/util"; - -export class ErrorMissingCredentials extends Error { - name = "ErrorMissingCredentials"; - - constructor(message: string) { - super(message); - this.name = "ErrorMissingCredentials"; - } -} - -export class ErrorUserExists extends Error { - name = "ErrorUserExists"; - - constructor(message: string) { - super(message); - this.name = "ErrorUserExists"; - } -} - -export class ErrorUserNotFound extends Error { - name = "ErrorUserNotFound"; - - constructor(message: string) { - super(message); - this.name = "ErrorUserNotFound"; - } -} -/** - * Handles the error that occurs when creating a user. - * If the error message indicates that the username must be unique, - * it throws an ErrorUserExists with the original error as the cause. - * Otherwise, it throws a generic Error with the original error as the cause. - * - * @param error - The error that occurred during user creation. - * @throws {ErrorUserExists} - If the error message indicates that the username must be unique. - * @throws {Error} - If the error message does not indicate that the username must be unique. - * @returns {never} - This function never returns a value. - */ -export function handleCreateUserError(error: unknown): never { - if ((error as Error).message.includes("username must be unique")) { - const err = new Error("Error creating user"); - err.name = "ErrorUserExists"; - err.cause = error; - log.error(`Error creating user: ${(error as Error).message}`); - throw err; - } - const err = new ErrorUserExists("Error creating user"); - err.cause = error; - log.error(`Error creating user: ${(error as Error).message}`); - throw err; -} diff --git a/libs/web/src/helpers.ts b/libs/web/src/helpers.ts new file mode 100644 index 0000000..d393195 --- /dev/null +++ b/libs/web/src/helpers.ts @@ -0,0 +1,31 @@ +import type { parsedHttpRequestData } from "./types.js"; + +/** + * Extracts the username and password from the parsed HTTP request data. + * @param info - The parsed HTTP request data. + * @returns An object containing the extracted username and password. + */ +export function extractCredentials(info: parsedHttpRequestData): { + username: string; + password: string; + } { + const username = (info.searchParams.get("username") as string) || ""; + const password = (info.searchParams.get("password") as string) || ""; + return { username, password }; + } + +/** + * Validates the provided username and password. + * @param {string} username - The username to validate. + * @param {string} password - The password to validate. + * @throws {ErrorMissingCredentials} If either the username or password is empty. + */ + export function validateCredentials(username: string, password: string): void { + if (username === "") { + throw new Error("Username is required"); + } + + if (password === "") { + throw new Error("Password is required"); + } + } \ No newline at end of file diff --git a/libs/web/src/index.ts b/libs/web/src/index.ts index d999f55..4a21cf4 100644 --- a/libs/web/src/index.ts +++ b/libs/web/src/index.ts @@ -1,51 +1,6 @@ -import { log } from "@rusty/util"; -import type { parsedHttpRequestData, rawHttpRequestData, RequestResponse } from "./types.js"; -import { handleAuthLogin } from "./services/AuthLogin.js"; - -const RouteHandlers: Record< - string, - (info: parsedHttpRequestData) => Promise<{ - statusCode: number; - body: string; - headers: Record; - }> -> = { - "/AuthLogin": handleAuthLogin, -}; - -export async function handleWebRequests({ - headers, - remoteAddress, - method, - url, -}: rawHttpRequestData): Promise { - log.debug("web.handleWebRequests"); - - const { pathname, searchParams } = new URL(url, "http://localhost"); - - log.debug( - `method: ${method} pathname: ${pathname} searchParams: ${searchParams.toString()}` - ); - - const handler = RouteHandlers[pathname]; - - if (handler) { - log.debug(`handler: ${handler.name}`); - return await handler({ - headers, - remoteAddress, - method, - pathname, - searchParams, - }); - } - - log.debug("handler not found"); - return new Promise((resolve) => { - resolve({ - statusCode: 404, - body: "Not Found\n", - headers: { "Content-Type": "text/plain" }, - }); - }); -} +/** + * Internal exports for the web library. + */ +export { User } from "./models/User.js"; +export type { UserAttributes } from "./models/User.js"; +export { RouteHandlers } from "./processors/index.js"; diff --git a/libs/web/src/models/Session.ts b/libs/web/src/models/Session.ts new file mode 100644 index 0000000..30612df --- /dev/null +++ b/libs/web/src/models/Session.ts @@ -0,0 +1,54 @@ +import { + Model, + type Optional, + DataTypes, + type CreationOptional, +} from "sequelize"; +import { db } from "../db.js"; + +export interface SessionAttributes { + id: CreationOptional; + customerId: number; + createdAt: CreationOptional; + sessionToken: string; +} + +export interface SessionCreationAttributes + extends Optional {} + +export class Session + extends Model + implements SessionAttributes +{ + declare id: CreationOptional; + declare customerId: number; + declare createdAt: CreationOptional; + declare sessionToken: string; +} + +Session.init( + { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + customerId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, + sessionToken: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + sequelize: db, + modelName: "Session", + } +); diff --git a/libs/web/src/models/User.ts b/libs/web/src/models/User.ts index e33e390..f19f8e5 100644 --- a/libs/web/src/models/User.ts +++ b/libs/web/src/models/User.ts @@ -5,6 +5,7 @@ export interface UserAttributes { id: number; username: string; password: string; + customerId: number; } export interface UserCreationAttributes @@ -17,6 +18,7 @@ export class User declare id: number; declare username: string; declare password: string; + declare customerId: number; } User.init( @@ -35,6 +37,10 @@ User.init( type: DataTypes.STRING, allowNull: false, }, + customerId: { + type: DataTypes.INTEGER, + allowNull: false, + }, }, { sequelize: db, diff --git a/libs/web/src/processors/index.ts b/libs/web/src/processors/index.ts new file mode 100644 index 0000000..346b992 --- /dev/null +++ b/libs/web/src/processors/index.ts @@ -0,0 +1,13 @@ +import type { parsedHttpRequestData } from "../types.js"; +import { authenticateUser } from "./processLogin.js"; + +export const RouteHandlers: Record< + string, + (info: parsedHttpRequestData) => Promise<{ + statusCode: number; + body: string; + headers: Record; + }> +> = { + "/AuthLogin": authenticateUser, +}; diff --git a/libs/web/src/processors/processLogin.ts b/libs/web/src/processors/processLogin.ts new file mode 100644 index 0000000..a29f2f0 --- /dev/null +++ b/libs/web/src/processors/processLogin.ts @@ -0,0 +1,64 @@ +import { log } from "@rusty/util"; +import { extractCredentials, validateCredentials } from "../helpers.js"; +import { userLogin } from "../services/AuthLogin.js"; +import type { parsedHttpRequestData, RequestResponse } from "../types.js"; +import { getServerURL } from "@rusty/config"; + +/** + * Handles the authentication login process. + * + * @param info - The parsed HTTP request data. + * @returns A promise that resolves to the request response. + */ +export async function authenticateUser( + info: parsedHttpRequestData +): Promise { + const { username, password } = extractCredentials(info); + + try { + validateCredentials(username, password); + + await userLogin(username, password); + + // TODO: Implement token generation + const token = "abc123"; + + return constructLoginResponse(`Valid=TRUE\nTicket=${token}`); + } catch (error: unknown) { + log.error(`Error validating credentials: ${(error as Error).message}`); + return generateLoginError("INV-200", "Unable to login", getServerURL()); + } +} + +/** + * Constructs a login response object. + * @param body - The response body. + * @returns The constructed login response object. + */ +function constructLoginResponse(body = ""): RequestResponse { + return { + statusCode: 200, + body, + headers: { "Content-Type": "text/plain" }, + }; +} + +/** + * Generates a login error response. + * + * @param errorCode - The error code. Default is "INV-200". + * @param errorText - The error text. Default is "Unable to login". + * @param errorUrl - The error URL. Default is "https://rusty-motors.com". + * @returns The login error response. + */ +function generateLoginError( + errorCode = "INV-200", + errorText = "Unable to login", + errorUrl = "https://rusty-motors.com" +): RequestResponse | PromiseLike { + return { + statusCode: 200, + body: `reasoncode=${errorCode}\nreasontext=${errorText}\nreasonurl=${errorUrl}`, + headers: { "Content-Type": "text/plain" }, + }; +} diff --git a/libs/web/src/processors/processWebRequests.ts b/libs/web/src/processors/processWebRequests.ts new file mode 100644 index 0000000..a5638bb --- /dev/null +++ b/libs/web/src/processors/processWebRequests.ts @@ -0,0 +1,38 @@ +import { log } from "@rusty/util"; +import { RouteHandlers } from "./index.js"; +import type { rawHttpRequestData, RequestResponse } from "../types.js"; + +export async function processWebRequests({ + headers, + remoteAddress, + method, + url, +}: rawHttpRequestData): Promise { + log.debug("processWebRequests"); + + const { pathname, searchParams } = new URL(url, "http://localhost"); + + log.debug( + `method: ${method} pathname: ${pathname} searchParams: ${searchParams.toString()}` + ); + + const handler = RouteHandlers[pathname]; + + if (handler) { + log.debug(`handler: ${handler.name}`); + return await handler({ + headers, + remoteAddress, + method, + pathname, + searchParams, + }); + } + + log.debug("handler not found"); + return { + statusCode: 404, + body: "Not Found\n", + headers: { "Content-Type": "text/plain" }, + }; +} diff --git a/libs/web/src/services/AuthLogin.ts b/libs/web/src/services/AuthLogin.ts index bc1a4e5..13a9082 100644 --- a/libs/web/src/services/AuthLogin.ts +++ b/libs/web/src/services/AuthLogin.ts @@ -1,46 +1,12 @@ import { User, type UserAttributes } from "../models/User.js"; -import type { parsedHttpRequestData, RequestResponse } from "../types.js"; import { log } from "@rusty/util"; -import { - ErrorMissingCredentials, - ErrorUserNotFound, - handleCreateUserError, -} from "../errors.js"; - -function validateCredentials(username: string, password: string): void { - if (username === "") { - throw new ErrorMissingCredentials("Username is required"); - } - - if (password === "") { - throw new ErrorMissingCredentials("Password is required"); - } -} - -export async function createUser( - username: string, - password: string -): Promise> { - log.debug("AuthLogin.create"); - - validateCredentials(username, password); - - log.debug(`Creating user: ${username}`); - try { - const user = await User.create({ username, password }); - log.debug(`User created: ${user ? user.username : "null"}`); - return { username: user.username, password: user.password }; - } catch (error: unknown) { - handleCreateUserError(error); - } -} /** * Authenticates a user by their username and password. * @param {string} username - The username of the user. * @param {string} password - The password of the user. * @returns {Promise>} - A promise that resolves to the user attributes (excluding the "id" field). - * @throws {ErrorUserNotFound} - If the user is not found. + * @throws {Error} - If the user is not found. */ export async function userLogin( username: string, @@ -56,103 +22,13 @@ export async function userLogin( }); if (!user) { - throw new ErrorUserNotFound("User not found"); + throw new Error("User not found"); } log.debug("User found"); return { username: user.username, password: user.password, + customerId: user.customerId, }; } -/** - * Handles the authentication login process. - * - * @param info - The parsed HTTP request data. - * @returns A promise that resolves to the request response. - */ -export async function handleAuthLogin( - info: parsedHttpRequestData -): Promise { - const { username, password } = extractCredentials(info); - - try { - validateCredentials(username, password); - - await userLogin(username, password); - - const token = "abc123"; - - return constructLoginResponse(`Valid=TRUE\nTicket=${token}`); - } catch (error: unknown) { - log.error(`Error validating credentials: ${(error as Error).message}`); - return generateLoginError( - "INV-200", - "Unable to login", - "https://rusty-motors.com" - ); - } -} - -/** - * Extracts the username and password from the parsed HTTP request data. - * @param info - The parsed HTTP request data. - * @returns An object containing the extracted username and password. - */ -function extractCredentials(info: parsedHttpRequestData): { - username: string; - password: string; -} { - const username = (info.searchParams.get("username") as string) || ""; - const password = (info.searchParams.get("password") as string) || ""; - return { username, password }; -} - -/** - * Constructs a login response object. - * @param body - The response body. - * @returns The constructed login response object. - */ -function constructLoginResponse(body = ""): RequestResponse { - return { - statusCode: 200, - body, - headers: { "Content-Type": "text/plain" }, - }; -} - -/** - * Generates a login error response. - * - * @param errorCode - The error code. Default is "INV-200". - * @param errorText - The error text. Default is "Unable to login". - * @param errorUrl - The error URL. Default is "https://rusty-motors.com". - * @returns The login error response. - */ -function generateLoginError( - errorCode = "INV-200", - errorText = "Unable to login", - errorUrl = "https://rusty-motors.com" -): RequestResponse | PromiseLike { - return { - statusCode: 200, - body: `reasoncode=${errorCode}\nreasontext=${errorText}\nreasonurl=${errorUrl}`, - headers: { "Content-Type": "text/plain" }, - }; -} - -/** - * Deletes a user from the database. - * - * @param username - The username of the user to delete. - * @returns A Promise that resolves when the user is successfully deleted. - * @throws If there is an error deleting the user. - */ -export async function deleteUser(username: string): Promise { - log.debug("Deleting user"); - return await User.destroy({ - where: { - username, - }, - }); -} diff --git a/libs/web/test/AuthLogin.test.ts b/libs/web/test/AuthLogin.test.ts deleted file mode 100644 index ab983be..0000000 --- a/libs/web/test/AuthLogin.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { describe, expect, it, test } from "vitest"; - -import { - handleAuthLogin, - createUser, - deleteUser, -} from "../src/services/AuthLogin.js"; -import { ErrorMissingCredentials, ErrorUserExists } from "../src/errors.js"; - -describe("handleAuthLogin -> User Login", () => { - it("When either username or password is not supplied, expect a generic error", async () => { - const searchParams = new URLSearchParams(); - searchParams.set("username", "validuser"); - searchParams.set("password", ""); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - const response = await handleAuthLogin(info); - - expect(response.statusCode).toBe(200); // Client expects all responses to be 200 - expect(response.body).toBe( - "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" - ); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); - - it("When user is not found, expect a generic error", async () => { - const searchParams = new URLSearchParams(); - searchParams.set("username", "nonexistent"); - searchParams.set("password", "password"); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - const response = await handleAuthLogin(info); - - expect(response.statusCode).toBe(200); - expect(response.body).toBe( - "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" - ); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); - - it("should return 200 with user data if login is successful", async () => { - const searchParams = new URLSearchParams(); - searchParams.set("username", "validuser"); - searchParams.set("password", "password"); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - await createUser("validuser", "password"); - - const response = await handleAuthLogin(info); - - expect(response.statusCode).toBe(200); - expect(response.body).toContain(`Valid=TRUE\nTicket=`); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); -}); - -describe("AuthLogin -> createUser", () => { - it("when username is blank, expect an ErrorMissingCredentials to be thrown", async () => { - // Arrange - const user = ""; - const password = "password"; - - // Assert - try { - await createUser(user, password); - } catch (error: unknown) { - expect(error).toBeInstanceOf(ErrorMissingCredentials); - expect((error as Error).message).toBe("Username is required"); - } - }); - - it("when password is blank, expect an ErrorMissingCredentials to be thrown", async () => { - // Arrange - const user = "validuser"; - const password = ""; - - // Assert - try { - await createUser(user, password); - } catch (error: unknown) { - expect(error).toBeInstanceOf(ErrorMissingCredentials); - expect((error as Error).message).toBe("Password is required"); - } - }); - - it("when user already exists, expect an ErrorUserExists to be thrown", async () => { - // Arrange - const user = "validuser"; - const password = "password"; - await deleteUser(user); - await createUser(user, password); - - // Assert - try { - await createUser(user, password); - } catch (error: unknown) { - expect(error).toBeInstanceOf(ErrorUserExists); - } - }); - - test("when user is created, expect a promise to be resolved with the user", async () => { - // Arrange - const user = "validuser"; - const password = "password"; - await deleteUser(user); - - // Act - const result = createUser(user, password); - - // Assert - await expect(result).resolves.toEqual({ username: user, password }); - }); -}); - -describe("AuthLogin -> deleteUser", () => { - it("when user is not found, expect a promise to be resolved with 0", async () => { - // Arrange - const user = "nonexistent"; - - // Assert - const result = await deleteUser(user); - expect(result).toEqual(0); - }); - - test("when user is deleted, expect a promise to be resolved with the number of rows affected (1)", async () => { - // Arrange - const user = "validuser"; - const password = "password"; - await deleteUser(user); - - // Act - await createUser(user, password); - const result = await deleteUser(user); - - // Assert - expect(result).toEqual(1); - }); -}); diff --git a/libs/web/test/Session.test.ts b/libs/web/test/Session.test.ts new file mode 100644 index 0000000..40ead91 --- /dev/null +++ b/libs/web/test/Session.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from "vitest"; +import { Session, type SessionCreationAttributes } from "@rusty/web"; + +describe("Session model", () => { + test("should create a new session", () => { + const sessionData: SessionCreationAttributes = { + customerId: 1, + sessionToken: "abc123", + createdAt: new Date(), + }; + + const session = new Session(sessionData); + + expect(session.customerId).toEqual(sessionData.customerId); + expect(session.sessionToken).toEqual(sessionData.sessionToken); + expect(session.createdAt).toEqual(sessionData.createdAt); + }); + + test("should have correct default values", () => { + const session = new Session(); + + expect(session.customerId).toBeUndefined(); + expect(session.sessionToken).toBeUndefined(); + expect(session.createdAt).not.toBeUndefined(); + }); +}); diff --git a/libs/web/test/errors.test.ts b/libs/web/test/errors.test.ts deleted file mode 100644 index 157d5c8..0000000 --- a/libs/web/test/errors.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { handleAuthLogin, createUser } from "../src/services/AuthLogin.js"; -import { - ErrorMissingCredentials, - ErrorUserExists, -} from "../src/errors.js"; -import { deleteUser } from "../src/services/AuthLogin.js"; - -describe("handleAuthLogin -> User Login", () => { - it("When either username or password is not supplied, expect a generic error", async () => { - // Arrange - const searchParams = new URLSearchParams(); - searchParams.set("username", "validuser"); - searchParams.set("password", ""); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - // Act - const response = await handleAuthLogin(info); - - // Assert - expect(response.statusCode).toBe(200); - expect(response.body).toBe( - "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" - ); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); - - it("When user is not found, expect a generic error", async () => { - // Arrange - const searchParams = new URLSearchParams(); - searchParams.set("username", "nonexistent"); - searchParams.set("password", "password"); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - // Act - const response = await handleAuthLogin(info); - - // Assert - expect(response.statusCode).toBe(200); - expect(response.body).toBe( - "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" - ); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); - - it("should return 200 with user data if login is successful", async () => { - // Arrange - const searchParams = new URLSearchParams(); - searchParams.set("username", "validuser"); - searchParams.set("password", "password"); - const info = { - headers: {}, - remoteAddress: "", - method: "", - pathname: "", - searchParams, - }; - - await createUser("validuser", "password"); - - // Act - const response = await handleAuthLogin(info); - - // Assert - expect(response.statusCode).toBe(200); - expect(response.body).toContain(`Valid=TRUE\nTicket=`); - expect(response.headers).toEqual({ "Content-Type": "text/plain" }); - }); -}); - -describe("AuthLogin -> createUser", () => { - it("when username is blank, expect an ErrorMissingCredentials to be thrown", async () => { - // Arrange - const user = ""; - const password = "password"; - - // Act & Assert - await expect(createUser(user, password)).rejects.toThrow( - ErrorMissingCredentials - ); - }); - - it("when password is blank, expect an ErrorMissingCredentials to be thrown", async () => { - // Arrange - const user = "validuser"; - const password = ""; - - // Act & Assert - await expect(createUser(user, password)).rejects.toThrow( - ErrorMissingCredentials - ); - }); - - it("when user already exists, expect an ErrorUserExists to be thrown", async () => { - // Arrange - const user = "validuser"; - const password = "password"; - await deleteUser(user); - await createUser(user, password); - - // Act & Assert - await expect(createUser(user, password)).rejects.toThrow(ErrorUserExists); - }); - - it("when user is created, expect a promise to be resolved with the user", async () => { - // Arrange - const user = "validuser"; - const password = "password"; - await deleteUser(user); - - // Act - const result = createUser(user, password); - - // Assert - await expect(result).resolves.toEqual({ username: user, password }); - }); -}); diff --git a/libs/web/test/helpers.test.ts b/libs/web/test/helpers.test.ts new file mode 100644 index 0000000..9c62086 --- /dev/null +++ b/libs/web/test/helpers.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "vitest"; +import { extractCredentials, validateCredentials } from "../src/helpers.js"; + +describe("extractCredentials", () => { + test("should extract username and password from parsed HTTP request data", () => { + // Arrange + const info = { + headers: {}, + remoteAddress: "", + method: "", + pathname: "", + searchParams: new URLSearchParams( + "username=testuser&password=testpassword" + ), + }; + + // Act + const result = extractCredentials(info); + + // Assert + expect(result).toEqual({ username: "testuser", password: "testpassword" }); + }); + + test("should return empty strings if username and password are missing", () => { + // Arrange + const info = { + headers: {}, + remoteAddress: "", + method: "", + pathname: "", + searchParams: new URLSearchParams(), + }; + + // Act + const result = extractCredentials(info); + + // Assert + expect(result).toEqual({ username: "", password: "" }); + }); +}); + +describe("validateCredentials", () => { + test("should throw an error if username is empty", () => { + // Arrange + const username = ""; + const password = "testpassword"; + + // Act & Assert + expect(() => validateCredentials(username, password)).toThrow( + "Username is required" + ); + }); + + test("should throw an error if password is empty", () => { + // Arrange + const username = "testuser"; + const password = ""; + + // Act & Assert + expect(() => validateCredentials(username, password)).toThrow( + "Password is required" + ); + }); + + test("should not throw an error if both username and password are provided", () => { + // Arrange + const username = "testuser"; + const password = "testpassword"; + + // Act & Assert + expect(() => validateCredentials(username, password)).not.toThrow(); + }); +}); diff --git a/libs/web/test/routes.test.ts b/libs/web/test/routes.test.ts new file mode 100644 index 0000000..e2077f5 --- /dev/null +++ b/libs/web/test/routes.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, test } from "vitest"; +import { RouteHandlers, User } from "#internal"; + +describe("RouteHandlers", () => { + test("when /AuthLogin is called with a missing username, expect a generic error", async () => { + // Arrange + const searchParams = new URLSearchParams(); + searchParams.set("password", "password"); + const info = { + headers: {}, + remoteAddress: "", + method: "", + pathname: "/AuthLogin", + searchParams, + }; + + // Act + const response = await RouteHandlers["/AuthLogin"](info); + + // Assert + expect(response.statusCode).toBe(200); // The legacy client expects a 200 status code and a specific response body + expect(response.body).toBe( + "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" + ); + expect(response.headers).toEqual({ "Content-Type": "text/plain" }); + }); + + test("when /AuthLogin is called with a missing password, expect a generic error", async () => { + // Arrange + const searchParams = new URLSearchParams(); + searchParams.set("username", "validuser"); + const info = { + headers: {}, + remoteAddress: "", + method: "", + pathname: "/AuthLogin", + searchParams, + }; + + // Act + const response = await RouteHandlers["/AuthLogin"](info); + + // Assert + expect(response.statusCode).toBe(200); // The legacy client expects a 200 status code and a specific response body + expect(response.body).toBe( + "reasoncode=INV-200\nreasontext=Unable to login\nreasonurl=https://rusty-motors.com" + ); + expect(response.headers).toEqual({ "Content-Type": "text/plain" }); + }); + + test("when /AuthLogin is called with a valid username and password, expect a login response", async () => { + // Arrange + const searchParams = new URLSearchParams(); + searchParams.set("username", "validuser"); + searchParams.set("password", "password"); + const info = { + headers: {}, + remoteAddress: "", + method: "", + pathname: "/AuthLogin", + searchParams, + }; + await User.sync({ force: true }); + await User.findOrCreate({ + where: { username: "validuser", password: "password", customerId: 1 }, + }).catch((error: unknown) => { + console.error((error as Error).message); + expect(true).toBe(false); + }); + + // Act + const response = await RouteHandlers["/AuthLogin"](info); + + // Assert + expect(response.statusCode).toBe(200); + expect(response.body).toContain(`Valid=TRUE\nTicket=`); + expect(response.headers).toEqual({ "Content-Type": "text/plain" }); + }); +}); diff --git a/nx.json b/nx.json index 4014854..b0ca9a5 100644 --- a/nx.json +++ b/nx.json @@ -2,25 +2,46 @@ "$schema": "./node_modules/nx/schemas/nx-schema.json", "targetDefaults": { "clean": { - "dependsOn": ["^clean"] + "dependsOn": [ + "^clean" + ] }, "build": { - "dependsOn": ["^build"], - "outputs": ["{projectRoot}/dist"], + "dependsOn": [ + "^build" + ], + "outputs": [ + "{projectRoot}/dist" + ], "cache": true }, "check-types": { - "dependsOn": ["check-types", "^check-types"], + "dependsOn": [ + "check-types", + "^check-types" + ], "cache": true }, "test": { - "dependsOn": ["build"], - "outputs": ["{projectRoot}/coverage"] + "dependsOn": [ + "build" + ], + "outputs": [ + "{projectRoot}/coverage" + ] }, "lint": { - "dependsOn": ["build", "^lint"] + "dependsOn": [ + "build", + "^lint" + ] } }, "defaultBase": "main", - "nxCloudAccessToken": "__NX_CLOUD_AUTH_TOKEN__" + "nxCloudAccessToken": "NOT_A_REAL_TOKEN-USES_NX_CLOUD_ACCESS_TOKEN_SECRET_ENV", + "namedInputs": { + "sharedGlobals": [ + "{workspaceRoot}/.github/workflows/ci.yml" + ] + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a4481b..1866027 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,61 @@ importers: '@rusty/web': specifier: workspace:* version: link:../../libs/web + '@sentry/node': + specifier: ^8.20.0 + version: 8.20.0 + '@sentry/profiling-node': + specifier: ^8.20.0 + version: 8.20.0 + devDependencies: + '@types/node': + specifier: 20.14.12 + version: 20.14.12 + '@types/pino': + specifier: 7.0.5 + version: 7.0.5 + '@vitest/coverage-v8': + specifier: ^2.0.4 + version: 2.0.4(vitest@2.0.4(@types/node@20.14.12)(@vitest/ui@2.0.4)) + '@vitest/ui': + specifier: ^2.0.4 + version: 2.0.4(vitest@2.0.4) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + nx: + specifier: 19.5.1 + version: 19.5.1 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.14.12)(typescript@5.5.4) + tslib: + specifier: ^2.6.3 + version: 2.6.3 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vite: + specifier: 2.0.4 + version: 2.0.4 + vitest: + specifier: ^2.0.4 + version: 2.0.4(@types/node@20.14.12)(@vitest/ui@2.0.4) + + libs/config: + dependencies: + '@rusty/util': + specifier: workspace:* + version: link:../util + '@sentry/node': + specifier: ^8.20.0 + version: 8.20.0 + '@sentry/profiling-node': + specifier: ^8.20.0 + version: 8.20.0 + pino: + specifier: 9.3.1 + version: 9.3.1 devDependencies: '@types/node': specifier: 20.14.12 @@ -90,6 +145,12 @@ importers: libs/util: dependencies: + '@sentry/node': + specifier: ^8.20.0 + version: 8.20.0 + '@sentry/profiling-node': + specifier: ^8.20.0 + version: 8.20.0 pino: specifier: 9.3.1 version: 9.3.1 @@ -130,9 +191,18 @@ importers: libs/web: dependencies: + '@rusty/config': + specifier: workspace:* + version: link:../config '@rusty/util': specifier: workspace:* version: link:../util + '@sentry/node': + specifier: ^8.20.0 + version: 8.20.0 + '@sentry/profiling-node': + specifier: ^8.20.0 + version: 8.20.0 pino: specifier: 9.3.1 version: 9.3.1 @@ -1144,6 +1214,160 @@ packages: '@nx/workspace@19.5.1': resolution: {integrity: sha512-I/O+kDxaLzVXITmddijffNzllioe84oGSQ3q8s3nW7RP2/T1LkmOS7Prnd80sEVteQowHe0kOnvAh+p82kAUjQ==} + '@opentelemetry/api-logs@0.52.1': + resolution: {integrity: sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.25.1': + resolution: {integrity: sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.25.1': + resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation-connect@0.38.0': + resolution: {integrity: sha512-2/nRnx3pjYEmdPIaBwtgtSviTKHWnDZN3R+TkRUnhIVrvBKVcq+I5B2rtd6mr6Fe9cHlZ9Ojcuh7pkNh/xdWWg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.41.0': + resolution: {integrity: sha512-/B7fbMdaf3SYe5f1P973tkqd6s7XZirjpfkoJ63E7nltU30qmlgm9tY5XwZOzAFI0rHS9tbrFI2HFPAvQUFe/A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fastify@0.38.0': + resolution: {integrity: sha512-HBVLpTSYpkQZ87/Df3N0gAw7VzYZV3n28THIBrJWfuqw3Or7UqdhnjeuMIPQ04BKk3aZc0cWn2naSQObbh5vXw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.42.0': + resolution: {integrity: sha512-N8SOwoKL9KQSX7z3gOaw5UaTeVQcfDO1c21csVHnmnmGUoqsXbArK2B8VuwPWcv6/BC/i3io+xTo7QGRZ/z28Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.40.0': + resolution: {integrity: sha512-8U/w7Ifumtd2bSN1OLaSwAAFhb9FyqWUki3lMMB0ds+1+HdSxYBe9aspEJEgvxAqOkrQnVniAPTEGf1pGM7SOw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.52.1': + resolution: {integrity: sha512-dG/aevWhaP+7OLv4BQQSEKMJv8GyeOp3Wxl31NHqE8xo9/fYMfEljiZphUHIfyg4gnZ9swMyWjfOQs5GUQe54Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.42.0': + resolution: {integrity: sha512-P11H168EKvBB9TUSasNDOGJCSkpT44XgoM6d3gRIWAa9ghLpYhl0uRkS8//MqPzcJVHr3h3RmfXIpiYLjyIZTw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.42.0': + resolution: {integrity: sha512-H1BEmnMhho8o8HuNRq5zEI4+SIHDIglNB7BPKohZyWG4fWNuR7yM4GTlR01Syq21vODAS7z5omblScJD/eZdKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.46.0': + resolution: {integrity: sha512-VF/MicZ5UOBiXrqBslzwxhN7TVqzu1/LN/QDpkskqM0Zm0aZ4CVRbUygL8d7lrjLn15x5kGIe8VsSphMfPJzlA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.40.0': + resolution: {integrity: sha512-niRi5ZUnkgzRhIGMOozTyoZIvJKNJyhijQI4nF4iFSb+FUx2v5fngfR+8XLmdQAO7xmsD8E5vEGdDVYVtKbZew==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.40.0': + resolution: {integrity: sha512-0xfS1xcqUmY7WE1uWjlmI67Xg3QsSUlNT+AcXHeA4BDUPwZtWqF4ezIwLgpVZfHOnkAEheqGfNSWd1PIu3Wnfg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.40.0': + resolution: {integrity: sha512-d7ja8yizsOCNMYIJt5PH/fKZXjb/mS48zLROO4BzZTtDfhNCl2UM/9VIomP2qkGIFVouSJrGr/T00EzY7bPtKA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-nestjs-core@0.39.0': + resolution: {integrity: sha512-mewVhEXdikyvIZoMIUry8eb8l3HUjuQjSjVbmLVTt4NQi35tkpnHQrG9bTRBrl3403LoWZ2njMPJyg4l6HfKvA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.43.0': + resolution: {integrity: sha512-og23KLyoxdnAeFs1UWqzSonuCkePUzCX30keSYigIzJe/6WSYA8rnEI5lobcxPEzg+GcU06J7jzokuEHbjVJNw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis-4@0.41.0': + resolution: {integrity: sha512-H7IfGTqW2reLXqput4yzAe8YpDC0fmVNal95GHMLOrS89W+qWUKIqxolSh63hJyfmwPSFwXASzj7wpSk8Az+Dg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.46.0': + resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.52.1': + resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.36.2': + resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} + engines: {node: '>=14'} + + '@opentelemetry/resources@1.25.1': + resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.25.1': + resolution: {integrity: sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.25.1': + resolution: {integrity: sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.25.1': + resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.40.1': + resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1151,6 +1375,9 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@prisma/instrumentation@5.17.0': + resolution: {integrity: sha512-c1Sle4ji8aasMcYfBBHFM56We4ljfenVtRmS8aY06BllS7SoU6SmJBwG7vil+GHiR0Yrh+t9iBwt4AY0Jr4KNQ==} + '@rollup/rollup-android-arm-eabi@4.19.0': resolution: {integrity: sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==} cpu: [arm] @@ -1231,6 +1458,37 @@ packages: cpu: [x64] os: [win32] + '@sentry/core@8.20.0': + resolution: {integrity: sha512-R81snuw+67VT4aCxr6ShST/s0Y6FlwN2YczhDwaGyzumn5rlvA6A4JtQDeExduNoDDyv4T3LrmW8wlYZn3CJJw==} + engines: {node: '>=14.18'} + + '@sentry/node@8.20.0': + resolution: {integrity: sha512-i4ywT2m0Gw65U3uwI4NwiNcyqp9YF6/RsusfH1pg4YkiL/RYp7FS0MPVgMggfvoue9S3KjCgRVlzTLwFATyPXQ==} + engines: {node: '>=14.18'} + + '@sentry/opentelemetry@8.20.0': + resolution: {integrity: sha512-NFcLK6+t9wUc4HlGKeuDn6W4KjZxZfZmWlrK2/tgC5KzG1cnVeOnWUrJzGHTa+YDDdIijpjiFUcpXGPkX3rmIg==} + engines: {node: '>=14.18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.25.1 + '@opentelemetry/instrumentation': ^0.52.1 + '@opentelemetry/sdk-trace-base': ^1.25.1 + '@opentelemetry/semantic-conventions': ^1.25.1 + + '@sentry/profiling-node@8.20.0': + resolution: {integrity: sha512-vQaMYjPM7o0qvmj4atxXZywIDhnxMwTlc6x24eKqT8zN0OFBuIc1nYIacT7pEmd7R6e/mXdiG04GH1Vg0bHfOQ==} + engines: {node: '>=14.18'} + hasBin: true + + '@sentry/types@8.20.0': + resolution: {integrity: sha512-6IP278KojOpiAA7vrd1hjhUyn26cl0n0nGsShzic5ztCVs92sTeVRnh7MTB9irDVtAbOEyt/YH6go3h+Jia1pA==} + engines: {node: '>=14.18'} + + '@sentry/utils@8.20.0': + resolution: {integrity: sha512-+1I5H8dojURiEUGPliDwheQk8dhjp8uV1sMccR/W/zjFrt4wZyPs+Ttp/V7gzm9LDJoNek9tmELert/jQqWTgg==} + engines: {node: '>=14.18'} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1253,6 +1511,9 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/connect@3.4.36': + resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1262,16 +1523,28 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/mysql@2.15.22': + resolution: {integrity: sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ==} + '@types/node@20.14.12': resolution: {integrity: sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/pg-pool@2.0.4': + resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} + + '@types/pg@8.6.1': + resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} + '@types/pino@7.0.5': resolution: {integrity: sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==} deprecated: This is a stub types definition. pino provides its own type definitions, so you do not need this installed. + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/validator@13.12.0': resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==} @@ -1382,6 +1655,16 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + acorn-import-assertions@1.9.0: + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1598,6 +1881,9 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2134,6 +2420,12 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} + import-in-the-middle@1.10.0: + resolution: {integrity: sha512-Z1jumVdF2GwnnYfM0a/y2ts7mZbwFMgt5rRuVmLgobgahC6iKgN5MBuXjzfTIOUpq5LSU10vJIPpVKe0X89fIw==} + + import-in-the-middle@1.7.1: + resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2458,6 +2750,9 @@ packages: engines: {node: '>=10'} hasBin: true + module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + moment-timezone@0.5.45: resolution: {integrity: sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==} @@ -2568,6 +2863,12 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true + opentelemetry-instrumentation-fetch-node@1.2.3: + resolution: {integrity: sha512-Qb11T7KvoCevMaSeuamcLsAD+pZnavkhDnlVL0kRozfhl42dKG5Q3anUklAFKJZjY3twLR+BnRa6DlwwkIE/+A==} + engines: {node: '>18.0.0'} + peerDependencies: + '@opentelemetry/api': ^1.6.0 + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2636,6 +2937,17 @@ packages: pg-connection-string@2.6.4: resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.6.1: + resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -2661,6 +2973,22 @@ packages: resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -2761,6 +3089,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.4.0: + resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} + engines: {node: '>=8.6.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -2882,6 +3214,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -3300,6 +3635,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -4530,11 +4869,223 @@ snapshots: - '@swc/core' - debug + '@opentelemetry/api-logs@0.52.1': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/instrumentation-connect@0.38.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@types/connect': 3.4.36 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.41.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fastify@0.38.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.42.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.40.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.42.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.42.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.46.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.40.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.40.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.40.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@types/mysql': 2.15.22 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-nestjs-core@0.39.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.43.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) + '@types/pg': 8.6.1 + '@types/pg-pool': 2.0.4 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis-4@0.41.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.7.1 + require-in-the-middle: 7.4.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + optional: true + + '@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.10.0 + require-in-the-middle: 7.4.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/redis-common@0.36.2': {} + + '@opentelemetry/resources@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/sdk-metrics@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + lodash.merge: 4.6.2 + + '@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/semantic-conventions@1.25.1': {} + + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@pkgjs/parseargs@0.11.0': optional: true '@polka/url@1.0.0-next.25': {} + '@prisma/instrumentation@5.17.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@rollup/rollup-android-arm-eabi@4.19.0': optional: true @@ -4583,6 +5134,74 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.19.0': optional: true + '@sentry/core@8.20.0': + dependencies: + '@sentry/types': 8.20.0 + '@sentry/utils': 8.20.0 + + '@sentry/node@8.20.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.38.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fastify': 0.38.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-nestjs-core': 0.39.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis-4': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@prisma/instrumentation': 5.17.0 + '@sentry/core': 8.20.0 + '@sentry/opentelemetry': 8.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.25.1) + '@sentry/types': 8.20.0 + '@sentry/utils': 8.20.0 + import-in-the-middle: 1.10.0 + optionalDependencies: + opentelemetry-instrumentation-fetch-node: 1.2.3(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@sentry/opentelemetry@8.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.25.1)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@sentry/core': 8.20.0 + '@sentry/types': 8.20.0 + '@sentry/utils': 8.20.0 + + '@sentry/profiling-node@8.20.0': + dependencies: + '@sentry/core': 8.20.0 + '@sentry/node': 8.20.0 + '@sentry/types': 8.20.0 + '@sentry/utils': 8.20.0 + detect-libc: 2.0.3 + node-abi: 3.65.0 + transitivePeerDependencies: + - supports-color + + '@sentry/types@8.20.0': {} + + '@sentry/utils@8.20.0': + dependencies: + '@sentry/types': 8.20.0 + '@sinclair/typebox@0.27.8': {} '@tootallnate/once@1.1.2': @@ -4600,6 +5219,10 @@ snapshots: dependencies: tslib: 2.6.3 + '@types/connect@3.4.36': + dependencies: + '@types/node': 20.14.12 + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -4608,16 +5231,32 @@ snapshots: '@types/ms@0.7.34': {} + '@types/mysql@2.15.22': + dependencies: + '@types/node': 20.14.12 + '@types/node@20.14.12': dependencies: undici-types: 5.26.5 '@types/parse-json@4.0.2': {} + '@types/pg-pool@2.0.4': + dependencies: + '@types/pg': 8.6.1 + + '@types/pg@8.6.1': + dependencies: + '@types/node': 20.14.12 + pg-protocol: 1.6.1 + pg-types: 2.2.0 + '@types/pino@7.0.5': dependencies: pino: 9.3.1 + '@types/shimmer@1.2.0': {} + '@types/validator@13.12.0': {} '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': @@ -4783,6 +5422,15 @@ snapshots: dependencies: event-target-shim: 5.0.1 + acorn-import-assertions@1.9.0(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + optional: true + + acorn-import-attributes@1.9.5(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -5036,6 +5684,8 @@ snapshots: chownr@2.0.0: {} + cjs-module-lexer@1.3.1: {} + clean-stack@2.2.0: optional: true @@ -5617,6 +6267,21 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@1.10.0: + dependencies: + acorn: 8.12.1 + acorn-import-attributes: 1.9.5(acorn@8.12.1) + cjs-module-lexer: 1.3.1 + module-details-from-path: 1.0.3 + + import-in-the-middle@1.7.1: + dependencies: + acorn: 8.12.1 + acorn-import-assertions: 1.9.0(acorn@8.12.1) + cjs-module-lexer: 1.3.1 + module-details-from-path: 1.0.3 + optional: true + imurmurhash@0.1.4: {} indent-string@4.0.0: @@ -5929,6 +6594,8 @@ snapshots: mkdirp@1.0.4: {} + module-details-from-path@1.0.3: {} + moment-timezone@0.5.45: dependencies: moment: 2.30.1 @@ -6080,6 +6747,15 @@ snapshots: opener@1.5.2: {} + opentelemetry-instrumentation-fetch-node@1.2.3(@opentelemetry/api@1.9.0): + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + optional: true + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6149,6 +6825,18 @@ snapshots: pg-connection-string@2.6.4: {} + pg-int8@1.0.1: {} + + pg-protocol@1.6.1: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.0.1: {} picomatch@2.3.1: {} @@ -6188,6 +6876,16 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -6295,6 +6993,14 @@ snapshots: require-directory@2.1.1: {} + require-in-the-middle@7.4.0: + dependencies: + debug: 4.3.5 + module-details-from-path: 1.0.3 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + requires-port@1.0.0: {} resolve-from@4.0.0: {} @@ -6408,6 +7114,8 @@ snapshots: shebang-regex@3.0.0: {} + shimmer@1.2.1: {} + side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -6834,6 +7542,8 @@ snapshots: wrappy@1.0.2: {} + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/scripts/inject-token.sh b/scripts/inject-token.sh deleted file mode 100755 index 702c1b4..0000000 --- a/scripts/inject-token.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Ensure the environment variable is set -if [ -z "$NX_CLOUD_AUTH_TOKEN" ]; then - echo "Error: NX_CLOUD_AUTH_TOKEN is not set." - exit 1 -fi - -# Replace the placeholder in nx.json with the environment variable value -sed -i 's/__NX_CLOUD_AUTH_TOKEN__/'"$NX_CLOUD_AUTH_TOKEN"'/g' nx.json - -echo "Token has been securely injected into nx.json." \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 05fa097..c934dfb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,7 @@ "references": [ { "path": "./apps/server" }, { "path": "./libs/util" }, - { "path": "./libs/web" } + { "path": "./libs/web" }, + { "path": "./libs/config" } ] }