diff --git a/.eslintrc.json b/.eslintrc.json index 9ca2e830..30b72661 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,25 +6,15 @@ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { - "@nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - } - ] - } - ] } }, { "files": ["*.ts", "*.tsx"], "extends": ["plugin:@nx/typescript"], - "rules": {} + "rules": { + "@typescript-eslint/no-non-null-asserted-optional-chain": "off", + "@typescript-eslint/no-explicit-any": "off" + } }, { "files": ["*.js", "*.jsx"], diff --git a/apps/backend/src/api/api.module.ts b/apps/backend/src/api/api.module.ts index a567ad26..d7fbb966 100644 --- a/apps/backend/src/api/api.module.ts +++ b/apps/backend/src/api/api.module.ts @@ -1,9 +1,21 @@ -import { Module } from '@nestjs/common'; +import {MiddlewareConsumer, Module, NestModule} from '@nestjs/common'; import {AuthController} from "@gitroom/backend/api/routes/auth.controller"; import {AuthService} from "@gitroom/backend/services/auth/auth.service"; +import {UsersController} from "@gitroom/backend/api/routes/users.controller"; +import {AuthMiddleware} from "@gitroom/backend/services/auth/auth.middleware"; + +const authenticatedController = [ + UsersController +]; @Module({ imports: [], - controllers: [AuthController], + controllers: [AuthController, ...authenticatedController], providers: [AuthService], }) -export class ApiModule {} +export class ApiModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(AuthMiddleware) + .forRoutes(...authenticatedController); + } +} diff --git a/apps/backend/src/api/routes/auth.controller.ts b/apps/backend/src/api/routes/auth.controller.ts index 20e901ca..4b336101 100644 --- a/apps/backend/src/api/routes/auth.controller.ts +++ b/apps/backend/src/api/routes/auth.controller.ts @@ -1,4 +1,6 @@ -import {Body, Controller, Post} from '@nestjs/common'; +import {Body, Controller, Post, Res} from '@nestjs/common'; +import {Response} from 'express'; + import {CreateOrgUserDto} from "@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto"; import {LoginUserDto} from "@gitroom/nestjs-libraries/dtos/auth/login.user.dto"; import {AuthService} from "@gitroom/backend/services/auth/auth.service"; @@ -10,16 +12,45 @@ export class AuthController { ) { } @Post('/register') - register( - @Body() body: CreateOrgUserDto + async register( + @Body() body: CreateOrgUserDto, + @Res({ passthrough: true }) response: Response ) { - return this._authService.routeAuth(body.provider, body); + try { + const jwt = await this._authService.routeAuth(body.provider, body); + response.cookie('auth', jwt, { + domain: '.' + new URL(process.env.FRONTEND_URL!).hostname, + secure: true, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); + response.header('reload', 'true'); + response.status(200).send(); + } + catch (e) { + response.status(400).send(e.message); + } } @Post('/login') - login( - @Body() body: LoginUserDto + async login( + @Body() body: LoginUserDto, + @Res({ passthrough: true }) response: Response ) { - return this._authService.routeAuth(body.provider, body); + try { + console.log('heghefrgefg'); + const jwt = await this._authService.routeAuth(body.provider, body); + response.cookie('auth', jwt, { + domain: '.' + new URL(process.env.FRONTEND_URL!).hostname, + secure: true, + httpOnly: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }); + response.header('reload', 'true'); + response.status(200).send(); + } + catch (e) { + response.status(400).send(e.message); + } } } diff --git a/apps/backend/src/api/routes/users.controller.ts b/apps/backend/src/api/routes/users.controller.ts new file mode 100644 index 00000000..592dd137 --- /dev/null +++ b/apps/backend/src/api/routes/users.controller.ts @@ -0,0 +1,13 @@ +import {Controller, Get} from '@nestjs/common'; +import {GetUserFromRequest} from "@gitroom/nestjs-libraries/user/user.from.request"; +import {User} from "@prisma/client"; + +@Controller('/user') +export class UsersController { + @Get('/self') + async getSelf( + @GetUserFromRequest() user: User + ) { + return user; + } +} diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index ef9fcf7d..5d613397 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -4,11 +4,12 @@ import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module"; import {AuthService} from "@gitroom/backend/services/auth/auth.service"; import {ApiModule} from "@gitroom/backend/api/api.module"; +import {AuthMiddleware} from "@gitroom/backend/services/auth/auth.middleware"; @Module({ imports: [DatabaseModule, RedisModule, ApiModule], controllers: [], - providers: [AuthService], + providers: [AuthService, AuthMiddleware], get exports() { return [...this.imports, ...this.providers]; } diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index 37b8be10..47fc8c23 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -1,12 +1,23 @@ +import cookieParser from 'cookie-parser'; import {Logger, ValidationPipe} from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + cors: { + credentials: true, + exposedHeaders: ['reload'], + origin: [process.env.FRONTEND_URL] + } + }); + app.useGlobalPipes(new ValidationPipe({ transform: true, })); + + app.use(cookieParser()); + const port = process.env.PORT || 3000; await app.listen(port); Logger.log( diff --git a/apps/backend/src/services/auth/auth.middleware.ts b/apps/backend/src/services/auth/auth.middleware.ts new file mode 100644 index 00000000..d2bf7d8d --- /dev/null +++ b/apps/backend/src/services/auth/auth.middleware.ts @@ -0,0 +1,31 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import * as console from "console"; +import {AuthService} from "@gitroom/helpers/auth/auth.service"; +import {User} from '@prisma/client'; + +@Injectable() +export class AuthMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + const auth = req.headers.auth || req.cookies.auth; + if (!auth) { + throw new Error('Unauthorized'); + } + try { + const user = AuthService.verifyJWT(auth) as User | null; + if (!user) { + throw new Error('Unauthorized'); + } + + delete user.password; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + req.user = user; + } + catch (err) { + throw new Error('Unauthorized'); + } + console.log('Request...'); + next(); + } +} diff --git a/apps/backend/src/services/auth/auth.service.ts b/apps/backend/src/services/auth/auth.service.ts index e58ec775..77a4f521 100644 --- a/apps/backend/src/services/auth/auth.service.ts +++ b/apps/backend/src/services/auth/auth.service.ts @@ -6,6 +6,7 @@ import {UsersService} from "@gitroom/nestjs-libraries/database/prisma/users/user import {OrganizationService} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.service"; import {AuthService as AuthChecker} from "@gitroom/helpers/auth/auth.service"; import {ProvidersFactory} from "@gitroom/backend/services/auth/providers/providers.factory"; +import * as console from "console"; @Injectable() export class AuthService { diff --git a/apps/commands/.eslintrc.json b/apps/commands/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/apps/commands/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/commands/jest.config.ts b/apps/commands/jest.config.ts new file mode 100644 index 00000000..b12f8f71 --- /dev/null +++ b/apps/commands/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'consumers', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/consumers', +}; diff --git a/apps/commands/project.json b/apps/commands/project.json new file mode 100644 index 00000000..6d6207f4 --- /dev/null +++ b/apps/commands/project.json @@ -0,0 +1,63 @@ +{ + "name": "commands", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/commands/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "target": "node", + "compiler": "tsc", + "outputPath": "dist/apps/commands", + "main": "apps/commands/src/main.ts", + "tsConfig": "apps/commands/tsconfig.app.json", + "webpackConfig": "apps/commands/webpack.config.js" + }, + "configurations": { + "development": {}, + "production": {} + } + }, + "command": { + "executor": "nx:run-commands", + "defaultConfiguration": "development", + "options": { + "buildTarget": "commands:build", + "command": "cd dist/apps/commands && node main.js" + }, + "configurations": { + "development": { + "buildTarget": "commands:build:development" + }, + "production": { + "buildTarget": "commands:build:production" + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/commands/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/commands/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/commands/src/command.module.ts b/apps/commands/src/command.module.ts new file mode 100644 index 00000000..b0e3338b --- /dev/null +++ b/apps/commands/src/command.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { CommandModule as ExternalCommandModule } from 'nestjs-command'; +import {CheckStars} from "./tasks/check.stars"; +import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module"; +import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module"; +import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module"; +import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service"; + +@Module({ + imports: [ExternalCommandModule, DatabaseModule, RedisModule, BullMqModule.forRoot({ + connection: ioRedis + })], + controllers: [], + providers: [CheckStars], + get exports() { + return [...this.imports, ...this.providers]; + } +}) +export class CommandModule {} diff --git a/apps/commands/src/main.ts b/apps/commands/src/main.ts new file mode 100644 index 00000000..b59ca393 --- /dev/null +++ b/apps/commands/src/main.ts @@ -0,0 +1,24 @@ +import { NestFactory } from '@nestjs/core'; +import {CommandModule} from "./command.module"; +import {CommandService} from "nestjs-command"; + +async function bootstrap() { + // some comment again + const app = await NestFactory.createApplicationContext(CommandModule, { + logger: ['error'] + }); + + try { + await app + .select(CommandModule) + .get(CommandService) + .exec(); + await app.close() + } catch (error) { + console.error(error); + await app.close(); + process.exit(1); + } +} + +bootstrap(); diff --git a/apps/commands/src/tasks/check.stars.ts b/apps/commands/src/tasks/check.stars.ts new file mode 100644 index 00000000..d1c65a5c --- /dev/null +++ b/apps/commands/src/tasks/check.stars.ts @@ -0,0 +1,27 @@ +import {Command, Positional} from 'nestjs-command'; +import { Injectable } from '@nestjs/common'; +import {BullMqClient} from "@gitroom/nestjs-libraries/bull-mq-transport/client/bull-mq.client"; +import * as console from "console"; + +@Injectable() +export class CheckStars { + constructor( + private _workerServiceProducer: BullMqClient + ) { + } + @Command({ + command: 'sync:stars ', + describe: 'Sync stars for a login', + }) + async create( + @Positional({ + name: 'login', + describe: 'login {owner}/{repo}', + type: 'string' + }) + login: string, + ) { + this._workerServiceProducer.emit('check_stars', {payload: {login}}).subscribe(); + return true; + } +} \ No newline at end of file diff --git a/apps/commands/tsconfig.app.json b/apps/commands/tsconfig.app.json new file mode 100644 index 00000000..a2ce7652 --- /dev/null +++ b/apps/commands/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2021" + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/apps/commands/tsconfig.json b/apps/commands/tsconfig.json new file mode 100644 index 00000000..c1e2dd4e --- /dev/null +++ b/apps/commands/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/commands/tsconfig.spec.json b/apps/commands/tsconfig.spec.json new file mode 100644 index 00000000..9b2a121d --- /dev/null +++ b/apps/commands/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/apps/commands/webpack.config.js b/apps/commands/webpack.config.js new file mode 100644 index 00000000..c1917ce0 --- /dev/null +++ b/apps/commands/webpack.config.js @@ -0,0 +1,13 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +// Nx plugins for webpack. +module.exports = composePlugins( + withNx({ + target: 'node', + }), + (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); diff --git a/apps/cron/src/cron.module.ts b/apps/cron/src/cron.module.ts index 447fbd30..9a1341a5 100644 --- a/apps/cron/src/cron.module.ts +++ b/apps/cron/src/cron.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; -import {CheckTrending} from "./app/tasks/check.trending"; +import {CheckTrending} from "./tasks/check.trending"; @Module({ imports: [ScheduleModule.forRoot()], diff --git a/apps/cron/src/tasks/check.stars.ts b/apps/cron/src/tasks/check.stars.ts new file mode 100644 index 00000000..8018898b --- /dev/null +++ b/apps/cron/src/tasks/check.stars.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import {Cron} from '@nestjs/schedule'; +import {StarsService} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.service"; +import {BullMqClient} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-client"; +import {WorkerServiceProducer} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-register"; + +@Injectable() +export class CheckStars { + constructor( + private _starsService: StarsService, + @WorkerServiceProducer() private _workerServiceProducer: BullMqClient + ) { + } + @Cron('0 0 * * *') + async checkStars() { + const allGitHubRepositories = await this._starsService.getAllGitHubRepositories(); + for (const repository of allGitHubRepositories) { + this._workerServiceProducer.emit('check_stars', JSON.stringify({login: repository.login})); + } + } +} \ No newline at end of file diff --git a/apps/cron/src/app/tasks/check.trending.ts b/apps/cron/src/tasks/check.trending.ts similarity index 58% rename from apps/cron/src/app/tasks/check.trending.ts rename to apps/cron/src/tasks/check.trending.ts index 38fdc79d..3430b9c1 100644 --- a/apps/cron/src/app/tasks/check.trending.ts +++ b/apps/cron/src/tasks/check.trending.ts @@ -1,11 +1,10 @@ import { Injectable } from '@nestjs/common'; import {Interval} from '@nestjs/schedule'; -import {isDev} from "@gitroom/helpers/utils/is.dev"; @Injectable() export class CheckTrending { - @Interval(isDev() ? 10000 : 3600000) - CheckTrending() { + @Interval(3600000) + checkTrending() { console.log('hello'); } } \ No newline at end of file diff --git a/apps/frontend/app/global.css b/apps/frontend/app/global.css deleted file mode 100644 index 819c00c9..00000000 --- a/apps/frontend/app/global.css +++ /dev/null @@ -1,409 +0,0 @@ -html { - -webkit-text-size-adjust: 100%; - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, - Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; - line-height: 1.5; - tab-size: 4; - scroll-behavior: smooth; -} -body { - font-family: inherit; - line-height: inherit; - margin: 0; -} -h1, -h2, -p, -pre { - margin: 0; -} -*, -::before, -::after { - box-sizing: border-box; - border-width: 0; - border-style: solid; - border-color: currentColor; -} -h1, -h2 { - font-size: inherit; - font-weight: inherit; -} -a { - color: inherit; - text-decoration: inherit; -} -pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - Liberation Mono, Courier New, monospace; -} -svg { - display: block; - vertical-align: middle; - shape-rendering: auto; - text-rendering: optimizeLegibility; -} -pre { - background-color: rgba(55, 65, 81, 1); - border-radius: 0.25rem; - color: rgba(229, 231, 235, 1); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - Liberation Mono, Courier New, monospace; - overflow: scroll; - padding: 0.5rem 0.75rem; -} - -.shadow { - box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), - 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} -.rounded { - border-radius: 1.5rem; -} -.wrapper { - width: 100%; -} -.container { - margin-left: auto; - margin-right: auto; - max-width: 768px; - padding-bottom: 3rem; - padding-left: 1rem; - padding-right: 1rem; - color: rgba(55, 65, 81, 1); - width: 100%; -} -#welcome { - margin-top: 2.5rem; -} -#welcome h1 { - font-size: 3rem; - font-weight: 500; - letter-spacing: -0.025em; - line-height: 1; -} -#welcome span { - display: block; - font-size: 1.875rem; - font-weight: 300; - line-height: 2.25rem; - margin-bottom: 0.5rem; -} -#hero { - align-items: center; - background-color: hsla(214, 62%, 21%, 1); - border: none; - box-sizing: border-box; - color: rgba(55, 65, 81, 1); - display: grid; - grid-template-columns: 1fr; - margin-top: 3.5rem; -} -#hero .text-container { - color: rgba(255, 255, 255, 1); - padding: 3rem 2rem; -} -#hero .text-container h2 { - font-size: 1.5rem; - line-height: 2rem; - position: relative; -} -#hero .text-container h2 svg { - color: hsla(162, 47%, 50%, 1); - height: 2rem; - left: -0.25rem; - position: absolute; - top: 0; - width: 2rem; -} -#hero .text-container h2 span { - margin-left: 2.5rem; -} -#hero .text-container a { - background-color: rgba(255, 255, 255, 1); - border-radius: 0.75rem; - color: rgba(55, 65, 81, 1); - display: inline-block; - margin-top: 1.5rem; - padding: 1rem 2rem; - text-decoration: inherit; -} -#hero .logo-container { - display: none; - justify-content: center; - padding-left: 2rem; - padding-right: 2rem; -} -#hero .logo-container svg { - color: rgba(255, 255, 255, 1); - width: 66.666667%; -} -#middle-content { - align-items: flex-start; - display: grid; - gap: 4rem; - grid-template-columns: 1fr; - margin-top: 3.5rem; -} -#learning-materials { - padding: 2.5rem 2rem; -} -#learning-materials h2 { - font-weight: 500; - font-size: 1.25rem; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -.list-item-link { - align-items: center; - border-radius: 0.75rem; - display: flex; - margin-top: 1rem; - padding: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 100%; -} -.list-item-link svg:first-child { - margin-right: 1rem; - height: 1.5rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1.5rem; -} -.list-item-link > span { - flex-grow: 1; - font-weight: 400; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.list-item-link > span > span { - color: rgba(107, 114, 128, 1); - display: block; - flex-grow: 1; - font-size: 0.75rem; - font-weight: 300; - line-height: 1rem; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.list-item-link svg:last-child { - height: 1rem; - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - width: 1rem; -} -.list-item-link:hover { - color: rgba(255, 255, 255, 1); - background-color: hsla(162, 47%, 50%, 1); -} -.list-item-link:hover > span { -} -.list-item-link:hover > span > span { - color: rgba(243, 244, 246, 1); -} -.list-item-link:hover svg:last-child { - transform: translateX(0.25rem); -} -#other-links { -} -.button-pill { - padding: 1.5rem 2rem; - transition-duration: 300ms; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - align-items: center; - display: flex; -} -.button-pill svg { - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; - flex-shrink: 0; - width: 3rem; -} -.button-pill > span { - letter-spacing: -0.025em; - font-weight: 400; - font-size: 1.125rem; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -.button-pill span span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; -} -.button-pill:hover svg, -.button-pill:hover { - color: rgba(255, 255, 255, 1) !important; -} -#nx-console:hover { - background-color: rgba(0, 122, 204, 1); -} -#nx-console svg { - color: rgba(0, 122, 204, 1); -} -#nx-console-jetbrains { - margin-top: 2rem; -} -#nx-console-jetbrains:hover { - background-color: rgba(255, 49, 140, 1); -} -#nx-console-jetbrains svg { - color: rgba(255, 49, 140, 1); -} -#nx-repo:hover { - background-color: rgba(24, 23, 23, 1); -} -#nx-repo svg { - color: rgba(24, 23, 23, 1); -} -#nx-cloud { - margin-bottom: 2rem; - margin-top: 2rem; - padding: 2.5rem 2rem; -} -#nx-cloud > div { - align-items: center; - display: flex; -} -#nx-cloud > div svg { - border-radius: 0.375rem; - flex-shrink: 0; - width: 3rem; -} -#nx-cloud > div h2 { - font-size: 1.125rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -#nx-cloud > div h2 span { - display: block; - font-size: 0.875rem; - font-weight: 300; - line-height: 1.25rem; -} -#nx-cloud p { - font-size: 1rem; - line-height: 1.5rem; - margin-top: 1rem; -} -#nx-cloud pre { - margin-top: 1rem; -} -#nx-cloud a { - color: rgba(107, 114, 128, 1); - display: block; - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 1.5rem; - text-align: right; -} -#nx-cloud a:hover { - text-decoration: underline; -} -#commands { - padding: 2.5rem 2rem; - margin-top: 3.5rem; -} -#commands h2 { - font-size: 1.25rem; - font-weight: 400; - letter-spacing: -0.025em; - line-height: 1.75rem; - padding-left: 1rem; - padding-right: 1rem; -} -#commands p { - font-size: 1rem; - font-weight: 300; - line-height: 1.5rem; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; -} -details { - align-items: center; - display: flex; - margin-top: 1rem; - padding-left: 1rem; - padding-right: 1rem; - width: 100%; -} -details pre > span { - color: rgba(181, 181, 181, 1); - display: block; -} -summary { - border-radius: 0.5rem; - display: flex; - font-weight: 400; - padding: 0.5rem; - cursor: pointer; - transition-property: background-color, border-color, color, fill, stroke, - opacity, box-shadow, transform, filter, backdrop-filter, - -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -summary:hover { - background-color: rgba(243, 244, 246, 1); -} -summary svg { - height: 1.5rem; - margin-right: 1rem; - width: 1.5rem; -} -#love { - color: rgba(107, 114, 128, 1); - font-size: 0.875rem; - line-height: 1.25rem; - margin-top: 3.5rem; - opacity: 0.6; - text-align: center; -} -#love svg { - color: rgba(252, 165, 165, 1); - width: 1.25rem; - height: 1.25rem; - display: inline; - margin-top: -0.25rem; -} -@media screen and (min-width: 768px) { - #hero { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - #hero .logo-container { - display: flex; - } - #middle-content { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} diff --git a/apps/frontend/app/layout.tsx b/apps/frontend/app/layout.tsx deleted file mode 100644 index 3c89ebd2..00000000 --- a/apps/frontend/app/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import './global.css'; - -export const metadata = { - title: 'Welcome to frontend', - description: 'Generated by create-nx-workspace', -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - {children} - - ); -} diff --git a/apps/frontend/app/page.module.scss b/apps/frontend/app/page.module.scss deleted file mode 100644 index 8a13e21c..00000000 --- a/apps/frontend/app/page.module.scss +++ /dev/null @@ -1,2 +0,0 @@ -.page { -} diff --git a/apps/frontend/app/page.tsx b/apps/frontend/app/page.tsx deleted file mode 100644 index 64e14a0f..00000000 --- a/apps/frontend/app/page.tsx +++ /dev/null @@ -1,450 +0,0 @@ -import styles from './page.module.scss'; - -export default async function Index() { - /* - * Replace the elements below with your own. - * - * Note: The corresponding styles are in the ./index.scss file. - */ - return ( -
-
-
-
-

- Hello there, - Welcome frontend 👋 -

-
- -
-
-

- - - - You're up and running -

- What's next? -
-
- - - -
-
- - - -
-

Next steps

-

Here are some things you can do with Nx:

-
- - - - - Add UI library - -
-                # Generate UI lib
-                nx g @nx/next:library ui
-                # Add a component
-                nx g @nx/next:component ui/src/lib/button
-              
-
-
- - - - - View interactive project graph - -
nx graph
-
-
- - - - - Run affected commands - -
-                # see what's been affected by changes
-                nx affected:graph
-                # run tests for current changes
-                nx affected:test
-                # run e2e tests for current changes
-                nx affected:e2e
-              
-
-
- -

- Carefully crafted with - - - -

-
-
-
- ); -} diff --git a/apps/frontend/postcss.config.js b/apps/frontend/postcss.config.js new file mode 100644 index 00000000..26b8d41b --- /dev/null +++ b/apps/frontend/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/apps/frontend/project.json b/apps/frontend/project.json index 6eb8e846..ce841169 100644 --- a/apps/frontend/project.json +++ b/apps/frontend/project.json @@ -9,7 +9,8 @@ "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { - "outputPath": "dist/apps/frontend" + "outputPath": "dist/apps/frontend", + "postcssConfig": "apps/{your app here}/postcss.config.js" }, "configurations": { "development": { diff --git a/apps/frontend/src/app/(site)/analytics/page.tsx b/apps/frontend/src/app/(site)/analytics/page.tsx new file mode 100644 index 00000000..fe088ce5 --- /dev/null +++ b/apps/frontend/src/app/(site)/analytics/page.tsx @@ -0,0 +1,5 @@ +export default async function Index() { + return ( + <>asd + ); +} diff --git a/apps/frontend/src/app/(site)/layout.tsx b/apps/frontend/src/app/(site)/layout.tsx new file mode 100644 index 00000000..350f4d76 --- /dev/null +++ b/apps/frontend/src/app/(site)/layout.tsx @@ -0,0 +1,15 @@ +import '../global.css'; +import {LayoutSettings} from "@gitroom/frontend/components/layout/layout.settings"; + +export default async function Layout({ children }: { children: React.ReactNode }) { + /* + * Replace the elements below with your own. + * + * Note: The corresponding styles are in the ./index.scss file. + */ + return ( + + {children} + + ); +} diff --git a/apps/frontend/src/app/(site)/page.tsx b/apps/frontend/src/app/(site)/page.tsx new file mode 100644 index 00000000..c5bdbcfd --- /dev/null +++ b/apps/frontend/src/app/(site)/page.tsx @@ -0,0 +1,5 @@ +import {redirect} from "next/navigation"; + +export default async function Page() { + return redirect('/analytics'); +} diff --git a/apps/frontend/src/app/(site)/schedule/page.tsx b/apps/frontend/src/app/(site)/schedule/page.tsx new file mode 100644 index 00000000..fe088ce5 --- /dev/null +++ b/apps/frontend/src/app/(site)/schedule/page.tsx @@ -0,0 +1,5 @@ +export default async function Index() { + return ( + <>asd + ); +} diff --git a/apps/frontend/app/api/hello/route.ts b/apps/frontend/src/app/api/hello/route.ts similarity index 100% rename from apps/frontend/app/api/hello/route.ts rename to apps/frontend/src/app/api/hello/route.ts diff --git a/apps/frontend/src/app/auth/layout.tsx b/apps/frontend/src/app/auth/layout.tsx new file mode 100644 index 00000000..427a1e10 --- /dev/null +++ b/apps/frontend/src/app/auth/layout.tsx @@ -0,0 +1,16 @@ +import '../global.css'; +import {ReactNode} from "react"; + +export default async function AuthLayout({children}: {children: ReactNode}) { + return ( +
+
+
+
+ {children} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/frontend/src/app/auth/login/page.tsx b/apps/frontend/src/app/auth/login/page.tsx new file mode 100644 index 00000000..298931cc --- /dev/null +++ b/apps/frontend/src/app/auth/login/page.tsx @@ -0,0 +1,7 @@ +import {Login} from "@gitroom/frontend/components/auth/login"; + +export default async function Auth() { + return ( + + ); +} diff --git a/apps/frontend/src/app/auth/page.tsx b/apps/frontend/src/app/auth/page.tsx new file mode 100644 index 00000000..ee383216 --- /dev/null +++ b/apps/frontend/src/app/auth/page.tsx @@ -0,0 +1,7 @@ +import {Register} from "@gitroom/frontend/components/auth/register"; + +export default async function Auth() { + return ( + + ); +} diff --git a/apps/frontend/src/app/global.css b/apps/frontend/src/app/global.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/apps/frontend/src/app/global.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx new file mode 100644 index 00000000..1d8df642 --- /dev/null +++ b/apps/frontend/src/app/layout.tsx @@ -0,0 +1,14 @@ +import LayoutContext from "@gitroom/frontend/components/layout/layout.context"; +import {ReactNode} from "react"; + +export default async function AppLayout({children}: {children: ReactNode}) { + return ( + + + + {children} + + + + ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/auth/login.tsx b/apps/frontend/src/components/auth/login.tsx new file mode 100644 index 00000000..afe9335e --- /dev/null +++ b/apps/frontend/src/components/auth/login.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useForm, SubmitHandler } from "react-hook-form"; +import {useFetch} from "@gitroom/helpers/utils/custom.fetch"; +import Link from "next/link"; + +type Inputs = { + email: string; + password: string; +} + +export function Login() { + const { + register, + handleSubmit, + } = useForm(); + + const fetchData = useFetch(); + + const onSubmit: SubmitHandler = (data) => { + fetchData('/auth/login', { + method: 'POST', + body: JSON.stringify({...data, provider: 'LOCAL'}) + }); + } + + return ( +
+
+

Create An Account

+
+
+ + +
+
+ +

Don{"'"}t Have An Account? Sign Up

+
+
+ ); +} diff --git a/apps/frontend/src/components/auth/register.tsx b/apps/frontend/src/components/auth/register.tsx new file mode 100644 index 00000000..92dfe918 --- /dev/null +++ b/apps/frontend/src/components/auth/register.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useForm, SubmitHandler } from "react-hook-form"; +import {useFetch} from "@gitroom/helpers/utils/custom.fetch"; +import Link from "next/link"; + +type Inputs = { + email: string; + password: string; + company: string; +} + +export function Register() { + const { + register, + handleSubmit, + } = useForm(); + + const fetchData = useFetch(); + + const onSubmit: SubmitHandler = (data) => { + fetchData('/auth/register', { + method: 'POST', + body: JSON.stringify({...data, provider: 'LOCAL'}) + }); + } + + return ( +
+
+

Create An Account

+
+
+ + + +
+
+ +

Already Have An Account? Sign In

+
+
+ ); +} diff --git a/apps/frontend/src/components/layout/layout.context.tsx b/apps/frontend/src/components/layout/layout.context.tsx new file mode 100644 index 00000000..262097ca --- /dev/null +++ b/apps/frontend/src/components/layout/layout.context.tsx @@ -0,0 +1,22 @@ +"use client"; + +import {ReactNode, useCallback} from "react"; +import {FetchWrapperComponent} from "@gitroom/helpers/utils/custom.fetch"; + +export default async function LayoutContext({children}: {children: ReactNode}) { + const afterRequest = useCallback(async (url: string, options: RequestInit, response: Response) => { + console.log(response?.headers.get('cookie')); + if (response?.headers?.get('reload')) { + window.location.reload(); + } + }, []); + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/layout/layout.settings.tsx b/apps/frontend/src/components/layout/layout.settings.tsx new file mode 100644 index 00000000..a8a65241 --- /dev/null +++ b/apps/frontend/src/components/layout/layout.settings.tsx @@ -0,0 +1,27 @@ +import {ReactNode} from "react"; +import {LeftMenu} from "@gitroom/frontend/components/layout/left.menu"; +import {Title} from "@gitroom/frontend/components/layout/title"; +import {headers} from "next/headers"; +import {ContextWrapper} from "@gitroom/frontend/components/layout/user.context"; + +export const LayoutSettings = ({children}: {children: ReactNode}) => { + const user = JSON.parse(headers().get('user')!); + return ( + +
+
+
+ Logo +
+ +
+
+
+ + {children} + </div> + </div> + </div> + </ContextWrapper> + ); +} \ No newline at end of file diff --git a/apps/frontend/src/components/layout/left.menu.tsx b/apps/frontend/src/components/layout/left.menu.tsx new file mode 100644 index 00000000..3d095a10 --- /dev/null +++ b/apps/frontend/src/components/layout/left.menu.tsx @@ -0,0 +1,54 @@ +"use client"; + +import {FC} from "react"; +import Link from "next/link"; +import clsx from "clsx"; +import {usePathname} from "next/navigation"; + +export const menuItems = [ + { + name: 'Analytics', + icon: 'analytics', + path: '/analytics', + }, + { + name: 'Schedule', + icon: 'schedule', + path: '/schedule', + }, + { + name: 'Media', + icon: 'media', + path: '/media', + }, + { + name: 'Settings', + icon: 'settings', + path: '/settings', + }, + { + name: 'Billing', + icon: 'billing', + path: '/billing', + }, +]; + +export const LeftMenu: FC = () => { + const path = usePathname(); + return ( + <div className="flex flex-col h-full"> + <ul className="gap-5 flex flex-col flex-1"> + {menuItems.map((item, index) => ( + <li key={item.name}> + <Link href={item.path} className={clsx("flex gap-2 items-center", menuItems.map(p => p.path).indexOf(path) === index && 'font-bold')}> + {item.name} + </Link> + </li> + ))} + </ul> + <div> + <a href="/auth/logout">Logout</a> + </div> + </div> + ); +} \ No newline at end of file diff --git a/apps/frontend/src/components/layout/title.tsx b/apps/frontend/src/components/layout/title.tsx new file mode 100644 index 00000000..bd34b82e --- /dev/null +++ b/apps/frontend/src/components/layout/title.tsx @@ -0,0 +1,23 @@ +"use client"; + +import {usePathname} from "next/navigation"; +import {useMemo} from "react"; +import {menuItems} from "@gitroom/frontend/components/layout/left.menu"; +import {useUser} from "@gitroom/frontend/components/layout/user.context"; + +export const Title = () => { + const path = usePathname(); + const currentTitle = useMemo(() => { + return menuItems.find(item => item.path === path)?.name; + }, [path]); + + const user = useUser(); + + return ( + <div className="flex"> + <h1 className="text-2xl mb-5 flex-1">{currentTitle}</h1> + <div>bell</div> + <div>{user?.email}</div> + </div> + ); +} \ No newline at end of file diff --git a/apps/frontend/src/components/layout/user.context.tsx b/apps/frontend/src/components/layout/user.context.tsx new file mode 100644 index 00000000..ed21b166 --- /dev/null +++ b/apps/frontend/src/components/layout/user.context.tsx @@ -0,0 +1,16 @@ +"use client"; + +import {createContext, FC, ReactNode, useContext} from "react"; +import {User} from "@prisma/client"; + +export const UserContext = createContext<undefined|User>(undefined); + +export const ContextWrapper: FC<{user: User, children: ReactNode}> = ({user, children}) => { + return ( + <UserContext.Provider value={user}> + {children} + </UserContext.Provider> + ) +} + +export const useUser = () => useContext(UserContext); \ No newline at end of file diff --git a/apps/frontend/src/middleware.ts b/apps/frontend/src/middleware.ts new file mode 100644 index 00000000..1e02d8fe --- /dev/null +++ b/apps/frontend/src/middleware.ts @@ -0,0 +1,57 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' +import {fetchBackend} from "@gitroom/helpers/utils/custom.fetch.func"; + +// This function can be marked `async` if using `await` inside +export async function middleware(request: NextRequest) { + const nextUrl = request.nextUrl; + const authCookie = request.cookies.get('auth'); + // If the URL is logout, delete the cookie and redirect to login + if (nextUrl.href.indexOf('/auth/logout') > -1) { + const response = NextResponse.redirect(new URL('/auth/login', nextUrl.href)); + response.cookies.set('auth', '', { + path: '/', + sameSite: false, + httpOnly: true, + secure: true, + maxAge: -1, + domain: '.' + new URL(process.env.FRONTEND_URL!).hostname + }); + return response; + } + + if (nextUrl.href.indexOf('/auth') === -1 && !authCookie) { + return NextResponse.redirect(new URL('/auth', nextUrl.href)); + } + + // If the url is /auth and the cookie exists, redirect to / + if (nextUrl.href.indexOf('/auth') > -1 && authCookie) { + return NextResponse.redirect(new URL('/', nextUrl.href)); + } + + if (nextUrl.href.indexOf('/auth') > -1) { + return NextResponse.next(); + } + + try { + const user = await (await fetchBackend('/user/self', { + headers: { + auth: authCookie?.value! + } + })).json(); + + const next = NextResponse.next(); + next.headers.set('user', JSON.stringify(user)); + + return next; + } + catch (err) { + return NextResponse.redirect(new URL('/auth/logout', nextUrl.href)); + } +} + +// See "Matching Paths" below to learn more +export const config = { + matcher: "/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)", +} + diff --git a/apps/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js new file mode 100644 index 00000000..385e75c1 --- /dev/null +++ b/apps/frontend/tailwind.config.js @@ -0,0 +1,21 @@ +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); +const { join } = require('path'); + +module.exports = { + content: [ + join( + __dirname, + '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' + ), + ...createGlobPatternsForDependencies(__dirname), + ], + theme: { + extend: { + colors: { + primary: '#090b13', + secondary: '#0b101b', + } + }, + }, + plugins: [], +}; \ No newline at end of file diff --git a/apps/workers/src/app/app.controller.ts b/apps/workers/src/app/app.controller.ts deleted file mode 100644 index f8fc2e7a..00000000 --- a/apps/workers/src/app/app.controller.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { EventPattern } from '@nestjs/microservices'; - -@Controller() -export class AppController { - @EventPattern('new_vote') - handleData(data) { - } -} diff --git a/apps/workers/src/app/app.module.ts b/apps/workers/src/app/app.module.ts index b1d30067..9d7ac4d3 100644 --- a/apps/workers/src/app/app.module.ts +++ b/apps/workers/src/app/app.module.ts @@ -1,12 +1,16 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; +import { StarsController } from './stars.controller'; import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module"; import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module"; +import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module"; +import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service"; @Module({ - imports: [RedisModule, DatabaseModule], - controllers: [AppController], + imports: [RedisModule, DatabaseModule, BullMqModule.forRoot({ + connection: ioRedis + })], + controllers: [StarsController], providers: [], }) export class AppModule {} diff --git a/apps/workers/src/app/stars.controller.ts b/apps/workers/src/app/stars.controller.ts new file mode 100644 index 00000000..bfbe0c43 --- /dev/null +++ b/apps/workers/src/app/stars.controller.ts @@ -0,0 +1,17 @@ +import {Controller} from '@nestjs/common'; +import {EventPattern, Transport} from '@nestjs/microservices'; +import { JSDOM } from "jsdom"; + +@Controller() +export class StarsController { + @EventPattern('check_stars', Transport.REDIS) + async handleData(data: {id: string, login: string}) { + const loadedHtml = await (await fetch(`https://github.com/${data.login}`)).text(); + const dom = new JSDOM(loadedHtml); + const totalStars = +( + dom.window.document.querySelector('#repo-stars-counter-star')?.getAttribute('title')?.replace(/,/g, '') + ) || 0; + + console.log(totalStars); + } +} diff --git a/apps/workers/src/main.ts b/apps/workers/src/main.ts index 501d16da..ac1ebb84 100644 --- a/apps/workers/src/main.ts +++ b/apps/workers/src/main.ts @@ -1,23 +1,19 @@ -import { NestFactory } from '@nestjs/core'; +import {NestFactory} from '@nestjs/core'; -import { AppModule } from './app/app.module'; -import { MicroserviceOptions } from '@nestjs/microservices'; -import {BullMqTransport} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-transport"; +import {AppModule} from './app/app.module'; +import {MicroserviceOptions} from '@nestjs/microservices'; +import {BullMqServer} from "@gitroom/nestjs-libraries/bull-mq-transport/server/bull-mq.server"; async function bootstrap() { - const strategy = new BullMqTransport(); - // some comment again - const app = await NestFactory.createMicroservice<MicroserviceOptions>( - AppModule, - { - strategy, - } - ); + const load = await NestFactory.create(AppModule); + const strategy = load.get(BullMqServer); - await app.listen(); + // some comment again + const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { + strategy, + }); - // Let's make sure everything runs first! - await strategy.activate(); + await app.listen(); } -bootstrap(); +bootstrap(); \ No newline at end of file diff --git a/libraries/helpers/src/utils/custom.fetch.func.ts b/libraries/helpers/src/utils/custom.fetch.func.ts new file mode 100644 index 00000000..1e7ba077 --- /dev/null +++ b/libraries/helpers/src/utils/custom.fetch.func.ts @@ -0,0 +1,28 @@ +export interface Params { + baseUrl: string, + beforeRequest?: (url: string, options: RequestInit) => Promise<RequestInit>, + afterRequest?: (url: string, options: RequestInit, response: Response) => Promise<void> +} +export const customFetch = (params: Params, auth?: string) => { + return async function newFetch (url: string, options: RequestInit = {}) { + const newRequestObject = await params?.beforeRequest?.(url, options); + const fetchRequest = await fetch(params.baseUrl + url, { + credentials: 'include', + ...( + newRequestObject || options + ), + headers: { + ...options?.headers, + ...auth ? {auth} : {}, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + }); + await params?.afterRequest?.(url, options, fetchRequest); + return fetchRequest; + } +} + +export const fetchBackend = customFetch({ + baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL! +}); diff --git a/libraries/helpers/src/utils/custom.fetch.tsx b/libraries/helpers/src/utils/custom.fetch.tsx new file mode 100644 index 00000000..0ba3ef38 --- /dev/null +++ b/libraries/helpers/src/utils/custom.fetch.tsx @@ -0,0 +1,28 @@ +"use client"; + +import {createContext, FC, ReactNode, useContext, useRef, useState} from "react"; +import {customFetch, Params} from "./custom.fetch.func"; + +const FetchProvider = createContext(customFetch( + // @ts-ignore + { + baseUrl: '', + beforeRequest: () => {}, + afterRequest: () => {} + } as Params)); + +export const FetchWrapperComponent: FC<Params & {children: ReactNode}> = (props) => { + const {children, ...params} = props; + // @ts-ignore + const fetchData = useRef(customFetch(params)); + return ( + // @ts-ignore + <FetchProvider.Provider value={fetchData.current}> + {children} + </FetchProvider.Provider> + ) +} + +export const useFetch = () => { + return useContext(FetchProvider); +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq-core.module.ts b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq-core.module.ts new file mode 100644 index 00000000..1c955935 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq-core.module.ts @@ -0,0 +1,89 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { BullMqClient } from './client/bull-mq.client'; +import { BULLMQ_MODULE_OPTIONS } from './constants/bull-mq.constants'; +import { QueueEventsFactory } from './factories/queue-events.factory'; +import { QueueFactory } from './factories/queue.factory'; +import { WorkerFactory } from './factories/worker.factory'; +import { IBullMqModuleOptionsAsync } from './interfaces/bull-mq-module-options-async.interface'; +import { IBullMqModuleOptionsFactory } from './interfaces/bull-mq-module-options-factory.interface'; +import { IBullMqModuleOptions } from './interfaces/bull-mq-module-options.interface'; +import { BullMqServer } from './server/bull-mq.server'; + +@Module({}) +export class BullMqCoreModule { + static forRoot(options: IBullMqModuleOptions): DynamicModule { + return { + module: BullMqCoreModule, + global: true, + providers: [ + { provide: BULLMQ_MODULE_OPTIONS, useValue: options }, + QueueFactory, + QueueEventsFactory, + WorkerFactory, + BullMqServer, + BullMqClient, + ], + exports: [BullMqServer, BullMqClient, BULLMQ_MODULE_OPTIONS], + }; + } + + static forRootAsync(options: IBullMqModuleOptionsAsync): DynamicModule { + return { + module: BullMqCoreModule, + global: true, + imports: options.imports ?? [], + providers: [ + ...(options.providers ?? []), + ...this.createAsyncProviders(options), + QueueFactory, + QueueEventsFactory, + WorkerFactory, + BullMqServer, + BullMqClient, + ], + exports: [BullMqServer, BullMqClient, BULLMQ_MODULE_OPTIONS], + }; + } + + private static createAsyncProviders(options: IBullMqModuleOptionsAsync) { + if (options.useExisting ?? options.useFactory) { + return [this.createAsyncOptionsProvider(options)]; + } + + if (options.useClass) { + return [ + this.createAsyncOptionsProvider(options), + { provide: options.useClass, useClass: options.useClass }, + ]; + } + + throw new Error( + 'Invalid BullMqModule async options: one of `useClass`, `useExisting` or `useFactory` should be defined.', + ); + } + + private static createAsyncOptionsProvider( + options: IBullMqModuleOptionsAsync, + ) { + if (options.useFactory) { + return { + provide: BULLMQ_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject ?? [], + }; + } + + const inject: any[] = []; + + if (options.useClass ?? options.useExisting) { + inject.push(options.useClass ?? options.useExisting); + } + + return { + provide: BULLMQ_MODULE_OPTIONS, + useFactory: async (optionsFactory: IBullMqModuleOptionsFactory) => + await optionsFactory.createModuleOptions(), + inject, + }; + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts new file mode 100644 index 00000000..be6f4c10 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts @@ -0,0 +1,21 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { BullMqCoreModule } from './bull-mq-core.module'; +import { IBullMqModuleOptionsAsync } from './interfaces/bull-mq-module-options-async.interface'; +import { IBullMqModuleOptions } from './interfaces/bull-mq-module-options.interface'; + +@Module({}) +export class BullMqModule { + static forRoot(options: IBullMqModuleOptions): DynamicModule { + return { + module: BullMqModule, + imports: [BullMqCoreModule.forRoot(options)], + }; + } + + static forRootAsync(options: IBullMqModuleOptionsAsync): DynamicModule { + return { + module: BullMqModule, + imports: [BullMqCoreModule.forRootAsync(options)], + }; + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts new file mode 100644 index 00000000..ee428a42 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts @@ -0,0 +1,94 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + ClientProxy, + ReadPacket, + RpcException, + WritePacket, +} from '@nestjs/microservices'; +import { Queue } from 'bullmq'; +import { v4 } from 'uuid'; +import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants'; +import { QueueEventsFactory } from '../factories/queue-events.factory'; +import { QueueFactory } from '../factories/queue.factory'; +import { IBullMqEvent } from '../interfaces/bull-mq-event.interface'; +import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface"; + +@Injectable() +export class BullMqClient extends ClientProxy { + constructor( + @Inject(BULLMQ_MODULE_OPTIONS) + private readonly options: IBullMqModuleOptions, + private readonly queueFactory: QueueFactory, + private readonly queueEventsFactory: QueueEventsFactory, + ) { + super(); + } + + async connect(): Promise<void> { + return; + } + + async close(): Promise<void> { + return; + } + + protected publish( + packet: ReadPacket<IBullMqEvent<any>>, + callback: (packet: WritePacket<any>) => void, + ): () => void { + const queue = this.getQueue(packet.pattern); + const events = this.queueEventsFactory.create(packet.pattern, { + connection: this.options.connection, + }); + events.on('completed', (job) => + callback({ + response: job.returnvalue, + isDisposed: true, + }), + ); + events.on('failed', async (jobInfo) => { + const job = await queue.getJob(jobInfo.jobId); + const err = new RpcException(jobInfo.failedReason); + err.stack = job?.stacktrace?.[0]; + callback({ + err, + isDisposed: true, + }); + }); + queue + .add(packet.pattern, packet.data, { + jobId: packet.data.id ?? v4(), + ...packet.data.options, + }) + .then(async (job) => { + try { + await job.waitUntilFinished(events); + } catch { + // BullMq unnecessarily re-throws the error we're handling in + // waitUntilFinished(), so we ignore that here. + } finally { + await events.close(); + await queue.close(); + } + }); + return () => void 0; + } + + protected async dispatchEvent( + packet: ReadPacket<IBullMqEvent<any>>, + ): Promise<any> { + const queue = this.getQueue(packet.pattern); + await queue.add(packet.pattern, packet.data, { + jobId: packet.data.id ?? v4(), + ...packet.data.options, + }); + await queue.close(); + } + + protected getQueue(pattern: any): Queue { + const queue = this.queueFactory.create(pattern, { + connection: this.options.connection, + }); + return queue; + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/constants/bull-mq.constants.ts b/libraries/nestjs-libraries/src/bull-mq-transport/constants/bull-mq.constants.ts new file mode 100644 index 00000000..f6757110 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/constants/bull-mq.constants.ts @@ -0,0 +1 @@ +export const BULLMQ_MODULE_OPTIONS = 'BULLMQ_MODULE_OPTIONS'; diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-exception.filter.ts b/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-exception.filter.ts new file mode 100644 index 00000000..7ed83006 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-exception.filter.ts @@ -0,0 +1,77 @@ +import { + ArgumentsHost, + Catch, + Inject, + InternalServerErrorException, + Logger, + LogLevel, + RpcExceptionFilter, +} from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { RpcException } from '@nestjs/microservices'; +import { Observable, of, throwError } from 'rxjs'; +import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants'; +import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface"; + +@Catch(RpcException) +export class BullMqRpcExceptionFilter + extends BaseExceptionFilter + implements RpcExceptionFilter<RpcException> +{ + private readonly logger = new Logger(); + + constructor( + @Inject(BULLMQ_MODULE_OPTIONS) + private readonly options: IBullMqModuleOptions, + ) { + super(); + } + + override catch(exception: RpcException, host: ArgumentsHost): Observable<void> { + if (host.getType() === 'http') { + const err = new InternalServerErrorException( + exception.message, + exception.constructor.name, + ); + if (exception.stack) { + err.stack = exception.stack; + } + this.logException(err, host); + return of(super.catch(err, host)); + } + + const err = { + name: exception.name, + error: exception.name, + message: exception.message, + stack: exception.stack || undefined, + }; + + this.logException(err, host); + + return throwError(() => err); + } + + logException(exception: Error, host: ArgumentsHost): void { + const defaultLogLevel: LogLevel = 'error'; + + switch (this.options.logExceptionsAsLevel ?? defaultLogLevel) { + case 'off': + return; + case 'log': + return this.logger.log(exception.stack, host.getType()); + case 'error': + return this.logger.error( + exception.message, + exception.stack, + host.getType(), + ); + case 'warn': + return this.logger.warn(exception.stack, host.getType()); + case 'debug': + return this.logger.debug(exception.stack, host.getType()); + case 'verbose': + return this.logger.verbose(exception.stack, host.getType()); + } + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-validation.exception.ts b/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-validation.exception.ts new file mode 100644 index 00000000..d8f5d1f3 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/exceptions/bull-mq-rpc-validation.exception.ts @@ -0,0 +1,5 @@ +import { RpcException } from '@nestjs/microservices'; + +export class BullMqRpcValidationException extends RpcException { + override name = this.constructor.name; +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue-events.factory.ts b/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue-events.factory.ts new file mode 100644 index 00000000..13dc70f1 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue-events.factory.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; +import { QueueEvents, QueueEventsOptions } from 'bullmq'; + +@Injectable() +export class QueueEventsFactory { + create(name: string, options?: QueueEventsOptions): QueueEvents { + return new QueueEvents(name, options); + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue.factory.ts b/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue.factory.ts new file mode 100644 index 00000000..158a8400 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/factories/queue.factory.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; +import { Queue, QueueOptions } from 'bullmq'; + +@Injectable() +export class QueueFactory { + create(name: string, options?: QueueOptions): Queue { + return new Queue(name, options); + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/factories/worker.factory.ts b/libraries/nestjs-libraries/src/bull-mq-transport/factories/worker.factory.ts new file mode 100644 index 00000000..7cea0168 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/factories/worker.factory.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common'; +import { Processor, Worker, WorkerOptions } from 'bullmq'; + +@Injectable() +export class WorkerFactory { + create<T, R, N extends string>( + name: string, + processor?: string | Processor<T, R, N>, + opts?: WorkerOptions, + ): Worker<T, R, N> { + return new Worker(name, processor, opts); + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-event.interface.ts b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-event.interface.ts new file mode 100644 index 00000000..cde75d0b --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-event.interface.ts @@ -0,0 +1,7 @@ +import { JobsOptions } from 'bullmq'; + +export interface IBullMqEvent<T> { + id?: string; + payload: T; + options?: JobsOptions; +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-async.interface.ts b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-async.interface.ts new file mode 100644 index 00000000..d334d2a2 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-async.interface.ts @@ -0,0 +1,14 @@ +import { Type } from '@nestjs/common'; +import { IBullMqModuleOptionsFactory } from './bull-mq-module-options-factory.interface'; +import { IBullMqModuleOptions } from './bull-mq-module-options.interface'; + +export interface IBullMqModuleOptionsAsync { + imports?: any[]; + providers?: any[]; + inject?: any[]; + useClass?: Type<IBullMqModuleOptionsFactory>; + useExisting?: Type<IBullMqModuleOptionsFactory>; + useFactory?: ( + ...args: any[] + ) => IBullMqModuleOptions | Promise<IBullMqModuleOptions>; +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-factory.interface.ts b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-factory.interface.ts new file mode 100644 index 00000000..148baca3 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options-factory.interface.ts @@ -0,0 +1,5 @@ +import { IBullMqModuleOptions } from './bull-mq-module-options.interface'; + +export interface IBullMqModuleOptionsFactory { + createModuleOptions(): IBullMqModuleOptions | Promise<IBullMqModuleOptions>; +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options.interface.ts b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options.interface.ts new file mode 100644 index 00000000..0e5ede5c --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/interfaces/bull-mq-module-options.interface.ts @@ -0,0 +1,6 @@ +import { LogLevel } from '@nestjs/common'; +import { WorkerOptions } from 'bullmq'; + +export interface IBullMqModuleOptions extends WorkerOptions { + logExceptionsAsLevel?: LogLevel | 'off'; +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/pipes/bull-mq-rpc-validation.pipe.ts b/libraries/nestjs-libraries/src/bull-mq-transport/pipes/bull-mq-rpc-validation.pipe.ts new file mode 100644 index 00000000..42a5121f --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/pipes/bull-mq-rpc-validation.pipe.ts @@ -0,0 +1,19 @@ +import { + ValidationError, + ValidationPipe, + ValidationPipeOptions, +} from '@nestjs/common'; +import {BullMqRpcValidationException} from "@gitroom/nestjs-libraries/bull-mq-transport/exceptions/bull-mq-rpc-validation.exception"; + +export class BullMqRpcValidationPipe extends ValidationPipe { + constructor(options?: ValidationPipeOptions) { + super({ + exceptionFactory: ( + errors: ValidationError[], + ): BullMqRpcValidationException => { + return new BullMqRpcValidationException(errors.toString()); + }, + ...options, + }); + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts b/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts new file mode 100644 index 00000000..90e969e4 --- /dev/null +++ b/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts @@ -0,0 +1,69 @@ +import { Inject, Injectable, Logger } from '@nestjs/common'; +import { + CustomTransportStrategy, + Server, + Transport, +} from '@nestjs/microservices'; +import { Job, Worker } from 'bullmq'; +import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants'; +import { WorkerFactory } from '../factories/worker.factory'; +import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface"; + +@Injectable() +export class BullMqServer extends Server implements CustomTransportStrategy { + transportId = Transport.REDIS; + + protected override readonly logger = new Logger(this.constructor.name); + protected readonly workers = new Map<string, Worker>(); + + constructor( + @Inject(BULLMQ_MODULE_OPTIONS) + private readonly options: IBullMqModuleOptions, + private readonly workerFactory: WorkerFactory, + ) { + super(); + + this.initializeSerializer(this.serializer); + this.initializeDeserializer(this.deserializer); + } + + listen(callback: (...optionalParams: unknown[]) => void) { + for (const [pattern, handler] of this.messageHandlers) { + if ( + pattern && + handler && + !this.workers.has(pattern) + ) { + const worker = this.workerFactory.create( + pattern, + (job: Job) => { + // eslint-disable-next-line no-async-promise-executor + return new Promise<unknown>(async (resolve, reject) => { + const stream$ = this.transformToObservable( + await handler(job.data.payload, job), + ); + this.send(stream$, (packet) => { + if (packet.err) { + return reject(packet.err); + } + resolve(packet.response); + }); + }); + }, + { + ...this.options, + }, + ); + this.workers.set(pattern, worker); + this.logger.log(`Registered queue "${pattern}"`); + } + } + callback(); + } + + async close() { + for (const worker of this.workers.values()) { + await worker.close(); + } + } +} diff --git a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-client.ts b/libraries/nestjs-libraries/src/bullmq-transport/bullmq-client.ts deleted file mode 100644 index 2070c801..00000000 --- a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-client.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ClientProxy, ReadPacket, WritePacket } from '@nestjs/microservices'; -import { Queue } from 'bullmq'; -import { ioRedis } from '../redis/redis.service'; - -export class BullMqClient extends ClientProxy { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - private queue: Queue; - - override async connect() { - return; - } - - async dispatchEvent(packet: ReadPacket<any>): Promise<any> { - this.queue = this.queue || new Queue(packet.pattern, { connection: ioRedis }); - return this.queue.add('default', packet.data); - } - - override close(): any { - return this.queue.close(); - } - - protected publish( - packet: ReadPacket, - callback: (packet: WritePacket) => void - ): () => void { - return () => { - return; - }; - } -} diff --git a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-register.ts b/libraries/nestjs-libraries/src/bullmq-transport/bullmq-register.ts deleted file mode 100644 index 7cf2510c..00000000 --- a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-register.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Inject, Module } from '@nestjs/common'; -import { ClientsModule } from '@nestjs/microservices'; -import { BullMqClient } from './bullmq-client'; - -@Module({ - imports: [ - ClientsModule.register([ - { - name: 'BULL_MQ_QUEUE', - customClass: BullMqClient, - }, - ]), - ], - get exports() { - return this.imports; - } -}) -export class BullmqRegister {} - -export const VoteServiceProducer = () => Inject('BULL_MQ_QUEUE'); diff --git a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-transport.ts b/libraries/nestjs-libraries/src/bullmq-transport/bullmq-transport.ts deleted file mode 100644 index 881f69f4..00000000 --- a/libraries/nestjs-libraries/src/bullmq-transport/bullmq-transport.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Server, CustomTransportStrategy } from '@nestjs/microservices'; -import { Job, Worker } from 'bullmq'; -import { ioRedis } from '../redis/redis.service'; - -export class BullMqTransport extends Server implements CustomTransportStrategy { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - private worker: Worker[]; - - public listen(callback: () => void) { - this.worker = []; - - this.messageHandlers.forEach((message, key) => { - this.worker.push( - new Worker( - key, - async (job: Job) => { - console.log('processing', job.id); - await message(JSON.parse(job.data)); - }, - { - connection: ioRedis, - runRetryDelay: 3000, - concurrency: 5, - autorun: false, - } - ) - ); - }); - callback(); - } - - activate() { - return Promise.all(this.worker.map((p) => { - console.log('listening to', p.name); - return p.run(); - })); - } - - public close() { - return Promise.all(this.worker.map((p) => p.close())); - } -} diff --git a/libraries/nestjs-libraries/src/database/prisma/database.module.ts b/libraries/nestjs-libraries/src/database/prisma/database.module.ts index 1211b512..96ee851b 100644 --- a/libraries/nestjs-libraries/src/database/prisma/database.module.ts +++ b/libraries/nestjs-libraries/src/database/prisma/database.module.ts @@ -4,6 +4,8 @@ import {OrganizationRepository} from "@gitroom/nestjs-libraries/database/prisma/ import {OrganizationService} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.service"; import {UsersService} from "@gitroom/nestjs-libraries/database/prisma/users/users.service"; import {UsersRepository} from "@gitroom/nestjs-libraries/database/prisma/users/users.repository"; +import {StarsService} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.service"; +import {StarsRepository} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.repository"; @Global() @Module({ @@ -15,7 +17,9 @@ import {UsersRepository} from "@gitroom/nestjs-libraries/database/prisma/users/u UsersService, UsersRepository, OrganizationService, - OrganizationRepository + OrganizationRepository, + StarsService, + StarsRepository ], get exports() { return this.providers; diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 43db264f..24af4ac3 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -26,6 +26,7 @@ model Organization { postTags PostTag[] postMedia PostMedia[] post Post[] + slots Slots[] } model User { @@ -160,6 +161,14 @@ model PostMedia { updatedAt DateTime @updatedAt } +model Slots { + id String @id @default(cuid()) + organizationId String + organization Organization @relation(fields: [organizationId], references: [id]) + day Int + time Int +} + model Post { id String @id @default(cuid()) state State @default(QUEUE) @@ -174,6 +183,7 @@ model Post { canonicalUrl String? canonicalPostId String? parentPostId String? + releaseURL String? canonicalPost Post? @relation("canonicalPostId", fields: [canonicalPostId], references: [id]) parentPost Post? @relation("parentPostId", fields: [parentPostId], references: [id]) canonicalChildren Post[] @relation("canonicalPostId") diff --git a/libraries/nestjs-libraries/src/database/prisma/stars/stars.repository.ts b/libraries/nestjs-libraries/src/database/prisma/stars/stars.repository.ts new file mode 100644 index 00000000..c07c2f35 --- /dev/null +++ b/libraries/nestjs-libraries/src/database/prisma/stars/stars.repository.ts @@ -0,0 +1,16 @@ +import {PrismaRepository} from "@gitroom/nestjs-libraries/database/prisma/prisma.service"; +import {Injectable} from "@nestjs/common"; + +@Injectable() +export class StarsRepository { + constructor( + private _github: PrismaRepository<'gitHub'> + ) { + } + + getAllGitHubRepositories() { + return this._github.model.gitHub.findMany({ + distinct: ['login'], + }); + } +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts b/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts new file mode 100644 index 00000000..c3b85ffd --- /dev/null +++ b/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts @@ -0,0 +1,13 @@ +import {Injectable} from "@nestjs/common"; +import {StarsRepository} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.repository"; + +@Injectable() +export class StarsService { + constructor( + private _starsRepository: StarsRepository + ){} + + getAllGitHubRepositories() { + return this._starsRepository.getAllGitHubRepositories(); + } +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/user/user.from.request.ts b/libraries/nestjs-libraries/src/user/user.from.request.ts new file mode 100644 index 00000000..7104409c --- /dev/null +++ b/libraries/nestjs-libraries/src/user/user.from.request.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const GetUserFromRequest = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + } +); diff --git a/libraries/react-shared-libraries/.eslintrc.json b/libraries/react-shared-libraries/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/libraries/react-shared-libraries/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libraries/react-shared-libraries/README.md b/libraries/react-shared-libraries/README.md new file mode 100644 index 00000000..07e7e7e4 --- /dev/null +++ b/libraries/react-shared-libraries/README.md @@ -0,0 +1,3 @@ +# nestjs-libraries + +This library was generated with [Nx](https://nx.dev). diff --git a/libraries/react-shared-libraries/project.json b/libraries/react-shared-libraries/project.json new file mode 100644 index 00000000..19839e61 --- /dev/null +++ b/libraries/react-shared-libraries/project.json @@ -0,0 +1,13 @@ +{ + "name": "react-shared-libraries", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libraries/react-shared-libraries/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + }, + "tags": [] +} diff --git a/libraries/react-shared-libraries/tsconfig.json b/libraries/react-shared-libraries/tsconfig.json new file mode 100644 index 00000000..d6072f46 --- /dev/null +++ b/libraries/react-shared-libraries/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libraries/react-shared-libraries/tsconfig.lib.json b/libraries/react-shared-libraries/tsconfig.lib.json new file mode 100644 index 00000000..faa09cc1 --- /dev/null +++ b/libraries/react-shared-libraries/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/package-lock.json b/package-lock.json index b1531b0d..f198507c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,22 +27,28 @@ "bullmq": "^5.1.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "clsx": "^2.1.0", + "cookie-parser": "^1.4.6", "ioredis": "^5.3.2", "jsonwebtoken": "^9.0.2", + "nestjs-command": "^3.1.4", "next": "13.4.4", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "^7.49.3", + "react-query": "^3.39.3", "react-router-dom": "6.11.2", "redis": "^4.6.12", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "stripe": "^14.14.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "yargs": "^17.7.2" }, "devDependencies": { "@nestjs/schematics": "^10.0.1", "@nestjs/testing": "^10.0.2", - "@nx/eslint": "17.2.8", + "@nx/eslint": "^17.2.8", "@nx/eslint-plugin": "17.2.8", "@nx/jest": "17.2.8", "@nx/js": "17.2.8", @@ -58,15 +64,19 @@ "@swc/cli": "~0.1.62", "@swc/core": "~1.3.85", "@testing-library/react": "14.0.0", + "@types/cookie-parser": "^1.4.6", "@types/jest": "^29.4.0", "@types/node": "18.16.9", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", + "@types/uuid": "^9.0.8", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "@vitejs/plugin-react": "^4.2.0", "@vitest/coverage-v8": "~0.34.6", "@vitest/ui": "~0.34.6", + "autoprefixer": "^10.4.17", "babel-jest": "^29.4.1", "eslint": "~8.48.0", "eslint-config-next": "13.4.4", @@ -80,10 +90,12 @@ "jest-environment-node": "^29.4.1", "jsdom": "~22.1.0", "nx": "17.2.8", + "postcss": "^8.4.33", "prettier": "^2.6.2", "prisma": "^5.8.1", "react-refresh": "^0.10.0", "sass": "1.62.1", + "tailwindcss": "^3.4.1", "ts-jest": "^29.1.0", "ts-node": "10.9.1", "typescript": "~5.2.2", @@ -107,6 +119,18 @@ "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "dev": true }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -2267,7 +2291,6 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2841,6 +2864,102 @@ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4612,6 +4731,16 @@ "typescript": "^3 || ^4 || ^5" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -5915,6 +6044,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/eslint": { "version": "8.56.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", @@ -6225,6 +6363,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@types/validator": { "version": "13.11.8", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", @@ -7218,6 +7362,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -7849,6 +7999,14 @@ "node": ">= 10.0.0" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -8069,6 +8227,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browserslist": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", @@ -8364,6 +8537,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -8562,7 +8744,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -8607,6 +8788,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -8883,10 +9072,22 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "engines": { "node": ">= 0.6" } @@ -9835,8 +10036,7 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/detect-port": { "version": "1.5.1", @@ -9852,6 +10052,12 @@ "detect-port": "bin/detect-port.js" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -9882,6 +10088,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -10017,6 +10229,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -10323,7 +10541,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -11260,6 +11477,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -11686,6 +11911,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "7.2.13", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", @@ -11939,7 +12192,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -13328,6 +13580,24 @@ "node": ">=6" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -14154,6 +14424,20 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14640,6 +14924,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.compact": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.compact/-/lodash.compact-3.0.1.tgz", + "integrity": "sha512-2ozeiPi+5eBXW1CLtzjk8XQFhQOEMwwfxblqeq6EGyTxZJ1bPATqilY0e6g2SLQpP4KuMeuioBhEnWz5Pr7ICQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -14651,6 +14940,11 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -14831,6 +15125,15 @@ "tmpl": "1.0.5" } }, + "node_modules/match-sorter": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.3.tgz", + "integrity": "sha512-sgiXxrRijEe0SzHKGX4HouCpfHRPnqteH42UdMEW7BlWy990ZkzcvonJGv4Uu9WE7Y1f8Yocm91+4qFPCbmNww==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -14898,6 +15201,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -15175,6 +15483,25 @@ "multicast-dns": "cli.js" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -15229,6 +15556,23 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nestjs-command": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nestjs-command/-/nestjs-command-3.1.4.tgz", + "integrity": "sha512-0RJGR/Z1ePrf/tzvVJjM95U0sJQeMjFCFad1FqWy4ypQ+VxIG9fGkEf6i8zckQOYjHXBZoWG5YwX+/v4Nof/nA==", + "dependencies": { + "lodash.compact": "^3.0.1", + "lodash.flattendeep": "^4.4.0" + }, + "bin": { + "nestjs-command": "bin/cli" + }, + "peerDependencies": { + "@nestjs/common": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "yargs": "^16.0.0 || ^17.0.0" + } + }, "node_modules/next": { "version": "13.4.4", "resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz", @@ -15584,6 +15928,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -15696,6 +16049,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -15989,6 +16347,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -16358,6 +16741,69 @@ "postcss": "^8.0.0" } }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", @@ -16553,6 +16999,25 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-normalize-charset": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", @@ -17081,12 +17546,53 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "engines": { + "node": ">=18", + "pnpm": "8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz", @@ -17234,8 +17740,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -17301,6 +17806,11 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -17314,7 +17824,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18284,6 +18793,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -18360,6 +18884,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -18629,6 +19166,89 @@ "node": ">= 8" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18689,6 +19309,115 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tailwindcss": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/tailwindcss/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -18863,6 +19592,27 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -19044,6 +19794,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -19440,6 +20196,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -20302,6 +21067,23 @@ "dev": true }, "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -20384,7 +21166,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -20408,7 +21189,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -20426,7 +21206,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index e915a614..a2507bba 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "license": "MIT", "scripts": { "dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers,cron --parallel=4\"", + "workers": "nx run workers:serve:development", "cron": "nx run cron:serve:development", + "command": "nx run commands:build && nx run commands:command", "prisma-generate": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma generate", "prisma-db-push": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma db push" }, @@ -25,22 +27,28 @@ "bullmq": "^5.1.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "clsx": "^2.1.0", + "cookie-parser": "^1.4.6", "ioredis": "^5.3.2", "jsonwebtoken": "^9.0.2", + "nestjs-command": "^3.1.4", "next": "13.4.4", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "^7.49.3", + "react-query": "^3.39.3", "react-router-dom": "6.11.2", "redis": "^4.6.12", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "stripe": "^14.14.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "yargs": "^17.7.2" }, "devDependencies": { "@nestjs/schematics": "^10.0.1", "@nestjs/testing": "^10.0.2", - "@nx/eslint": "17.2.8", + "@nx/eslint": "^17.2.8", "@nx/eslint-plugin": "17.2.8", "@nx/jest": "17.2.8", "@nx/js": "17.2.8", @@ -56,15 +64,19 @@ "@swc/cli": "~0.1.62", "@swc/core": "~1.3.85", "@testing-library/react": "14.0.0", + "@types/cookie-parser": "^1.4.6", "@types/jest": "^29.4.0", "@types/node": "18.16.9", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", + "@types/uuid": "^9.0.8", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "@vitejs/plugin-react": "^4.2.0", "@vitest/coverage-v8": "~0.34.6", "@vitest/ui": "~0.34.6", + "autoprefixer": "^10.4.17", "babel-jest": "^29.4.1", "eslint": "~8.48.0", "eslint-config-next": "13.4.4", @@ -78,10 +90,12 @@ "jest-environment-node": "^29.4.1", "jsdom": "~22.1.0", "nx": "17.2.8", + "postcss": "^8.4.33", "prettier": "^2.6.2", "prisma": "^5.8.1", "react-refresh": "^0.10.0", "sass": "1.62.1", + "tailwindcss": "^3.4.1", "ts-jest": "^29.1.0", "ts-node": "10.9.1", "typescript": "~5.2.2",