From 922fe9f977fc0e7b8139eb5b7b795a2993754504 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 16:48:31 +0200 Subject: [PATCH 01/43] chore: upped sentry on `./api` --- api/package.json | 4 +- api/src/_utils/setup-sentry.ts | 17 + api/src/app/index.ts | 20 +- .../app/middlewares/sentry-error-handler.ts | 11 - .../app/middlewares/sentry-request-handler.ts | 10 - api/src/config/generate-config.ts | 31 + api/src/config/service.ts | 26 +- yarn.lock | 568 ++++++++++++++++-- 8 files changed, 563 insertions(+), 124 deletions(-) create mode 100644 api/src/_utils/setup-sentry.ts delete mode 100644 api/src/app/middlewares/sentry-error-handler.ts delete mode 100644 api/src/app/middlewares/sentry-request-handler.ts create mode 100644 api/src/config/generate-config.ts diff --git a/api/package.json b/api/package.json index dd9f9c57a..e495e77c6 100644 --- a/api/package.json +++ b/api/package.json @@ -10,8 +10,8 @@ "@dzcode.io/data": "*", "@dzcode.io/models": "*", "@dzcode.io/utils": "*", - "@sentry/node": "^7.46.0", - "@sentry/tracing": "^7.46.0", + "@sentry/node": "^8.28.0", + "@sentry/profiling-node": "^8.28.0", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "class-validator-jsonschema": "^3.1.0", diff --git a/api/src/_utils/setup-sentry.ts b/api/src/_utils/setup-sentry.ts new file mode 100644 index 000000000..0ea1bb005 --- /dev/null +++ b/api/src/_utils/setup-sentry.ts @@ -0,0 +1,17 @@ +import * as Sentry from "@sentry/node"; +import { nodeProfilingIntegration } from "@sentry/profiling-node"; +import { generateConfig } from "src/config/generate-config"; + +const { NODE_ENV, BUNDLE_INFO } = generateConfig(); + +if (NODE_ENV !== "development") { + Sentry.init({ + dsn: "https://5f9d7ae6e98944e1815f8d1944fc3c12@o953637.ingest.sentry.io/5904452", + integrations: [nodeProfilingIntegration()], + tracesSampleRate: NODE_ENV === "production" ? 0.1 : 1.0, + profilesSampleRate: 1.0, // relative to tracesSampleRate + environment: NODE_ENV, + debug: NODE_ENV !== "production", + release: `api@${BUNDLE_INFO.version}`, + }); +} diff --git a/api/src/app/index.ts b/api/src/app/index.ts index b4e95e402..9281b394e 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -1,4 +1,6 @@ +// Import these two first! in this order import "reflect-metadata"; +import "src/_utils/setup-sentry"; import { fsConfig } from "@dzcode.io/utils/dist/config"; import * as Sentry from "@sentry/node"; @@ -20,23 +22,11 @@ import { ErrorMiddleware } from "./middlewares/error"; import { LoggerMiddleware } from "./middlewares/logger"; import { RobotsMiddleware } from "./middlewares/robots"; import { SecurityMiddleware } from "./middlewares/security"; -import { SentryErrorHandlerMiddleware } from "./middlewares/sentry-error-handler"; -import { SentryRequestHandlerMiddleware } from "./middlewares/sentry-request-handler"; // Use typedi container useContainer(Container); -const { NODE_ENV, PORT, BUNDLE_INFO } = Container.get(ConfigService).env(); - -if (NODE_ENV !== "development") { - Sentry.init({ - dsn: "https://5f9d7ae6e98944e1815f8d1944fc3c12@o953637.ingest.sentry.io/5904452", - tracesSampleRate: 1.0, - environment: NODE_ENV, - debug: NODE_ENV !== "production", - release: `api@${BUNDLE_INFO.version}`, - }); -} +const { NODE_ENV, PORT } = Container.get(ConfigService).env(); // Create the app: export const routingControllersOptions: RoutingControllersOptions = { @@ -50,9 +40,7 @@ export const routingControllersOptions: RoutingControllersOptions = { DocumentationController, ], middlewares: [ - SentryRequestHandlerMiddleware, SecurityMiddleware, - SentryErrorHandlerMiddleware, ErrorMiddleware, LoggerMiddleware, DocsMiddleware, @@ -65,6 +53,8 @@ const app: Application = createExpressServer(routingControllersOptions); const logger = Container.get(LoggerService); +Sentry.setupExpressErrorHandler(app); + // Start it app.listen(PORT, () => { const commonConfig = fsConfig(NODE_ENV); diff --git a/api/src/app/middlewares/sentry-error-handler.ts b/api/src/app/middlewares/sentry-error-handler.ts deleted file mode 100644 index e34f181fc..000000000 --- a/api/src/app/middlewares/sentry-error-handler.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Sentry from "@sentry/node"; -import { RequestHandler } from "express"; -import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers"; -import { Service } from "typedi"; - -@Service() -@Middleware({ type: "after" }) -export class SentryErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface { - error: RequestHandler = - Sentry.Handlers.errorHandler() as unknown as SentryErrorHandlerMiddleware["error"]; -} diff --git a/api/src/app/middlewares/sentry-request-handler.ts b/api/src/app/middlewares/sentry-request-handler.ts deleted file mode 100644 index 12dc39c50..000000000 --- a/api/src/app/middlewares/sentry-request-handler.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from "@sentry/node"; -import { RequestHandler } from "express"; -import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; -import { Service } from "typedi"; - -@Service() -@Middleware({ type: "before", priority: 0 }) -export class SentryRequestHandlerMiddleware implements ExpressMiddlewareInterface { - use: RequestHandler = Sentry.Handlers.requestHandler(); -} diff --git a/api/src/config/generate-config.ts b/api/src/config/generate-config.ts new file mode 100644 index 000000000..2fa8817ff --- /dev/null +++ b/api/src/config/generate-config.ts @@ -0,0 +1,31 @@ +import { plainToClass } from "class-transformer"; +import { validateSync } from "class-validator"; +import { config } from "dotenv"; + +import { ENVDto } from "./dto"; + +let cachedEnv: ENVDto | undefined = undefined; + +export function generateConfig(): ENVDto { + if (cachedEnv) return cachedEnv; + + const _config = config(); + const output = plainToClass(ENVDto, { + ...process.env, + ...(_config.parsed || {}), + }); + + const errors = validateSync(output); + + if (errors.length > 0) + throw new Error( + `⚠️ Errors in .env file in the following keys:${errors.reduce( + (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), + "", + )}`, + ); + + cachedEnv = output; + + return output; +} diff --git a/api/src/config/service.ts b/api/src/config/service.ts index 26590d5b6..b6431653a 100644 --- a/api/src/config/service.ts +++ b/api/src/config/service.ts @@ -1,37 +1,15 @@ -import { plainToClass } from "class-transformer"; -import { validateSync } from "class-validator"; -import { config } from "dotenv"; import { Service } from "typedi"; import { ENVDto } from "./dto"; +import { generateConfig } from "./generate-config"; let _env: ENVDto; @Service() export class ConfigService { constructor() { - this.generateConfig(); + _env = generateConfig(); } public env = () => _env; - - private generateConfig = () => { - const _config = config(); - const output = plainToClass(ENVDto, { - ...process.env, - ...(_config.parsed || {}), - }); - - const errors = validateSync(output); - - if (errors.length > 0) - throw new Error( - `⚠️ Errors in .env file in the following keys:${errors.reduce( - (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), - "", - )}`, - ); - - _env = output; - }; } diff --git a/yarn.lock b/yarn.lock index b47515f6f..ff4d069b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1958,16 +1958,251 @@ dependencies: "@octokit/openapi-types" "^11.2.0" -"@opentelemetry/api@~1.9.0": +"@opentelemetry/api-logs@0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" + integrity sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0", "@opentelemetry/api@~1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/semantic-conventions@~1.25.1": +"@opentelemetry/context-async-hooks@^1.25.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz#fa92f722cf685685334bba95f258d3ef9fce60f6" + integrity sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg== + +"@opentelemetry/core@1.25.1": + version "1.25.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.25.1.tgz#ff667d939d128adfc7c793edae2f6bca177f829d" + integrity sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.25.1" + +"@opentelemetry/core@1.26.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.25.1", "@opentelemetry/core@^1.8.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" + integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/instrumentation-connect@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.38.0.tgz#1f4aa27894eac2538fb3c8fce7b1be92cae0217e" + integrity sha512-2/nRnx3pjYEmdPIaBwtgtSviTKHWnDZN3R+TkRUnhIVrvBKVcq+I5B2rtd6mr6Fe9cHlZ9Ojcuh7pkNh/xdWWg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@types/connect" "3.4.36" + +"@opentelemetry/instrumentation-express@0.41.1": + version "0.41.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.41.1.tgz#658561df6ffbae86f5ad33e8d7ef2abb7b4967fc" + integrity sha512-uRx0V3LPGzjn2bxAnV8eUsDT82vT7NTwI0ezEuPMBOTOsnPpGhWdhcdNdhH80sM4TrWrOfXm9HGEdfWE3TRIww== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-fastify@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.38.0.tgz#0cb02ee1156197075e8a90e4fd18a6b6c94221ba" + integrity sha512-HBVLpTSYpkQZ87/Df3N0gAw7VzYZV3n28THIBrJWfuqw3Or7UqdhnjeuMIPQ04BKk3aZc0cWn2naSQObbh5vXw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-fs@0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.14.0.tgz#19f1cb38a8c2d05f3b96af67f1c8d43f0af2829b" + integrity sha512-pVc8P5AgliC1DphyyBUgsxXlm2XaPH4BpYvt7rAZDMIqUpRk8gs19SioABtKqqxvFzg5jPtgJfJsdxq0Y+maLw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + +"@opentelemetry/instrumentation-graphql@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.42.0.tgz#588a18c39e3b3f655bc09243566172ab0b638d35" + integrity sha512-N8SOwoKL9KQSX7z3gOaw5UaTeVQcfDO1c21csVHnmnmGUoqsXbArK2B8VuwPWcv6/BC/i3io+xTo7QGRZ/z28Q== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + +"@opentelemetry/instrumentation-hapi@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.40.0.tgz#ae11190f0f57cdb4dc8d792cb8bca61e5343684c" + integrity sha512-8U/w7Ifumtd2bSN1OLaSwAAFhb9FyqWUki3lMMB0ds+1+HdSxYBe9aspEJEgvxAqOkrQnVniAPTEGf1pGM7SOw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-http@0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.52.1.tgz#12061501601838d1c912f9c29bdd40a13a7e44cf" + integrity sha512-dG/aevWhaP+7OLv4BQQSEKMJv8GyeOp3Wxl31NHqE8xo9/fYMfEljiZphUHIfyg4gnZ9swMyWjfOQs5GUQe54Q== + dependencies: + "@opentelemetry/core" "1.25.1" + "@opentelemetry/instrumentation" "0.52.1" + "@opentelemetry/semantic-conventions" "1.25.1" + semver "^7.5.2" + +"@opentelemetry/instrumentation-ioredis@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.42.0.tgz#0f488ffc68af3caa474e2f67861759075170729c" + integrity sha512-P11H168EKvBB9TUSasNDOGJCSkpT44XgoM6d3gRIWAa9ghLpYhl0uRkS8//MqPzcJVHr3h3RmfXIpiYLjyIZTw== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.23.0" + +"@opentelemetry/instrumentation-koa@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.42.0.tgz#1c180f3605448c2e57a4ba073b69ffba7b2970b3" + integrity sha512-H1BEmnMhho8o8HuNRq5zEI4+SIHDIglNB7BPKohZyWG4fWNuR7yM4GTlR01Syq21vODAS7z5omblScJD/eZdKw== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-mongodb@0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.46.0.tgz#e3720e8ca3ca9f228fbf02f0812f7518c030b05e" + integrity sha512-VF/MicZ5UOBiXrqBslzwxhN7TVqzu1/LN/QDpkskqM0Zm0aZ4CVRbUygL8d7lrjLn15x5kGIe8VsSphMfPJzlA== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/sdk-metrics" "^1.9.1" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-mongoose@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.40.0.tgz#9c888312e524c381bfdf56a094c799150332dd51" + integrity sha512-niRi5ZUnkgzRhIGMOozTyoZIvJKNJyhijQI4nF4iFSb+FUx2v5fngfR+8XLmdQAO7xmsD8E5vEGdDVYVtKbZew== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-mysql2@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.40.0.tgz#fa2992c36d54427dccea68e5c69fff01103dabe6" + integrity sha512-0xfS1xcqUmY7WE1uWjlmI67Xg3QsSUlNT+AcXHeA4BDUPwZtWqF4ezIwLgpVZfHOnkAEheqGfNSWd1PIu3Wnfg== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@opentelemetry/sql-common" "^0.40.1" + +"@opentelemetry/instrumentation-mysql@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.40.0.tgz#bde5894c8eb447a4b8e940b030b2b73898da03fa" + integrity sha512-d7ja8yizsOCNMYIJt5PH/fKZXjb/mS48zLROO4BzZTtDfhNCl2UM/9VIomP2qkGIFVouSJrGr/T00EzY7bPtKA== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@types/mysql" "2.15.22" + +"@opentelemetry/instrumentation-nestjs-core@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz#733fef4306c796951d7ea1951b45f9df0aed234d" + integrity sha512-mewVhEXdikyvIZoMIUry8eb8l3HUjuQjSjVbmLVTt4NQi35tkpnHQrG9bTRBrl3403LoWZ2njMPJyg4l6HfKvA== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.23.0" + +"@opentelemetry/instrumentation-pg@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.43.0.tgz#3cd94ad5144e1fd326a921280fa8bb7b49005eb5" + integrity sha512-og23KLyoxdnAeFs1UWqzSonuCkePUzCX30keSYigIzJe/6WSYA8rnEI5lobcxPEzg+GcU06J7jzokuEHbjVJNw== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@opentelemetry/sql-common" "^0.40.1" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.4" + +"@opentelemetry/instrumentation-redis-4@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.41.0.tgz#6c1b1a37c18478887f346a3bc7ef309ee9f726c0" + integrity sha512-H7IfGTqW2reLXqput4yzAe8YpDC0fmVNal95GHMLOrS89W+qWUKIqxolSh63hJyfmwPSFwXASzj7wpSk8Az+Dg== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation@0.52.1", "@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0", "@opentelemetry/instrumentation@^0.52.0", "@opentelemetry/instrumentation@^0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz#2e7e46a38bd7afbf03cf688c862b0b43418b7f48" + integrity sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw== + dependencies: + "@opentelemetry/api-logs" "0.52.1" + "@types/shimmer" "^1.0.2" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/instrumentation@^0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz#a8a252306f82e2eace489312798592a14eb9830e" + integrity sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw== + dependencies: + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.7.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47" + integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== + +"@opentelemetry/resources@1.26.0", "@opentelemetry/resources@^1.25.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" + integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/sdk-metrics@^1.9.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz#37bb0afb1d4447f50aab9cdd05db6f2d8b86103e" + integrity sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + +"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.25.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" + integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@~1.25.1": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz#0deecb386197c5e9c2c28f2f89f51fb8ae9f145e" integrity sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ== +"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.22.0", "@opentelemetry/semantic-conventions@^1.23.0", "@opentelemetry/semantic-conventions@^1.25.1": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" + integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== + +"@opentelemetry/sql-common@^0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz#93fbc48d8017449f5b3c3274f2268a08af2b83b6" + integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg== + dependencies: + "@opentelemetry/core" "^1.1.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -2001,6 +2236,15 @@ dependencies: pug-lexer "^5.0.0" +"@prisma/instrumentation@5.18.0": + version "5.18.0" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.18.0.tgz#8b49e25bf3f8f756eb0c4c199b4cf8b6631db891" + integrity sha512-r074avGkpPXItk+josQPhufZEmGhUCb16PQx4ITPS40vWTpTPET4VsgCBZB2alIN6SS7pRFod2vz2M2HHEEylQ== + dependencies: + "@opentelemetry/api" "^1.8" + "@opentelemetry/instrumentation" "^0.49 || ^0.50 || ^0.51 || ^0.52.0" + "@opentelemetry/sdk-trace-base" "^1.22" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2212,16 +2456,6 @@ "@sentry/types" "8.27.0" "@sentry/utils" "8.27.0" -"@sentry-internal/tracing@7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.46.0.tgz#26febabe21a2c2cab45a3de75809d88753ec07eb" - integrity sha512-KYoppa7PPL8Er7bdPoxTNUfIY804JL7hhOEomQHYD22rLynwQ4AaLm3YEY75QWwcGb0B7ZDMV+tSumW7Rxuwuw== - dependencies: - "@sentry/core" "7.46.0" - "@sentry/types" "7.46.0" - "@sentry/utils" "7.46.0" - tslib "^1.9.3" - "@sentry/browser@8.27.0": version "8.27.0" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.27.0.tgz#997eb6b3c298a659a109704a0fb660eae365cd3a" @@ -2247,15 +2481,6 @@ progress "^2.0.3" proxy-from-env "^1.1.0" -"@sentry/core@7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.46.0.tgz#f377e556d8679f29bde1cce15b1682b6c689d6b7" - integrity sha512-BnNHGh/ZTztqQedFko7vb2u6yLs/kWesOQNivav32ZbsEpVCjcmG1gOJXh2YmGIvj3jXOC9a4xfIuh+lYFcA6A== - dependencies: - "@sentry/types" "7.46.0" - "@sentry/utils" "7.46.0" - tslib "^1.9.3" - "@sentry/core@8.27.0": version "8.27.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.27.0.tgz#a0ebe31cdd9313186a14d9738238ed9cf7a59c01" @@ -2264,19 +2489,71 @@ "@sentry/types" "8.27.0" "@sentry/utils" "8.27.0" -"@sentry/node@^7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.46.0.tgz#f85ee74926372d19d6b6a23f68f19023d7a528a7" - integrity sha512-+GrgJMCye2WXGarRiU5IJHCK27xg7xbPc2XjGojBKbBoZfqxVAWbXEK4bnBQgRGP1pCmrU/M6ZhVgR3dP580xA== +"@sentry/core@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.28.0.tgz#dd28fa913c296b443d4070f147c63e81edf429c8" + integrity sha512-+If9uubvpZpvaQQw4HLiKPhrSS9/KcoA/AcdQkNm+5CVwAoOmDPtyYfkPBgfo2hLZnZQqR1bwkz/PrNoOm+gqA== + dependencies: + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry/node@8.28.0", "@sentry/node@^8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.28.0.tgz#fef8cccd2c75345cb3e8840dba8b748a07376060" + integrity sha512-444hx0S7EAYDdq3g2U37qHFC/WFErgf8ZvXqhWfoCI4RweHHntdFbz3azexYnO61iUsmSAnFAX6htJtAG2zNdA== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/context-async-hooks" "^1.25.1" + "@opentelemetry/core" "^1.25.1" + "@opentelemetry/instrumentation" "^0.52.1" + "@opentelemetry/instrumentation-connect" "0.38.0" + "@opentelemetry/instrumentation-express" "0.41.1" + "@opentelemetry/instrumentation-fastify" "0.38.0" + "@opentelemetry/instrumentation-fs" "0.14.0" + "@opentelemetry/instrumentation-graphql" "0.42.0" + "@opentelemetry/instrumentation-hapi" "0.40.0" + "@opentelemetry/instrumentation-http" "0.52.1" + "@opentelemetry/instrumentation-ioredis" "0.42.0" + "@opentelemetry/instrumentation-koa" "0.42.0" + "@opentelemetry/instrumentation-mongodb" "0.46.0" + "@opentelemetry/instrumentation-mongoose" "0.40.0" + "@opentelemetry/instrumentation-mysql" "0.40.0" + "@opentelemetry/instrumentation-mysql2" "0.40.0" + "@opentelemetry/instrumentation-nestjs-core" "0.39.0" + "@opentelemetry/instrumentation-pg" "0.43.0" + "@opentelemetry/instrumentation-redis-4" "0.41.0" + "@opentelemetry/resources" "^1.25.1" + "@opentelemetry/sdk-trace-base" "^1.25.1" + "@opentelemetry/semantic-conventions" "^1.25.1" + "@prisma/instrumentation" "5.18.0" + "@sentry/core" "8.28.0" + "@sentry/opentelemetry" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + import-in-the-middle "^1.11.0" + optionalDependencies: + opentelemetry-instrumentation-fetch-node "1.2.3" + +"@sentry/opentelemetry@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.28.0.tgz#402465eb5f2cf015f389321361953b9637e24e5e" + integrity sha512-xClK/fa2Y9AMoaV6f7sWfoHAz56actn2RN3UuYAfxlgmNEfZEa0tc78x4XygCT+2b83QbUb+qf1q4+1ft+HEsQ== dependencies: - "@sentry-internal/tracing" "7.46.0" - "@sentry/core" "7.46.0" - "@sentry/types" "7.46.0" - "@sentry/utils" "7.46.0" - cookie "^0.4.1" - https-proxy-agent "^5.0.0" - lru_map "^0.3.3" - tslib "^1.9.3" + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry/profiling-node@^8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/profiling-node/-/profiling-node-8.28.0.tgz#d6796e0b1f376b9c273b1c2bb5fa73473dd9e450" + integrity sha512-VJFj0XxodeRm+mRJlLYMEmn6HKnYkEm07Zb2mdhG979bQwt2VRoPd+Cv4M6irEfmFoRD1OAR9HX0/p9ClcWzXg== + dependencies: + "@sentry/core" "8.28.0" + "@sentry/node" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + detect-libc "^2.0.2" + node-abi "^3.61.0" "@sentry/react@^8.27.0": version "8.27.0" @@ -2289,30 +2566,15 @@ "@sentry/utils" "8.27.0" hoist-non-react-statics "^3.3.2" -"@sentry/tracing@^7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.46.0.tgz#cdda3a20edbb35789e4e8623492e9f0ca5f9af48" - integrity sha512-7qBtzmu7CDHclSKp+ZRrxoDcMyrev6/rxD2rSVJgB3o8gd2XGcO5vx9vuUOoYF0xTfOMXscR6Ft6JXE49xovYg== - dependencies: - "@sentry-internal/tracing" "7.46.0" - -"@sentry/types@7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.46.0.tgz#8573ba8676342c594fcfefff4552123278cfec51" - integrity sha512-2FMEMgt2h6u7AoELhNhu9L54GAh67KKfK2pJ1kEXJHmWxM9FSCkizjLs/t+49xtY7jEXr8qYq8bV967VfDPQ9g== - "@sentry/types@8.27.0": version "8.27.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.27.0.tgz#a5c7d2877c6c3620f812b2b31377b58d390b89d4" integrity sha512-B6lrP46+m2x0lfqWc9F4VcUbN893mVGnPEd7KIMRk95mPzkFJ3sNxggTQF5/ZfNO7lDQYQb22uysB5sj/BqFiw== -"@sentry/utils@7.46.0": - version "7.46.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.46.0.tgz#7a713724db3d1c8bc0aef6d19a7fe2c76db0bdf2" - integrity sha512-elRezDAF84guMG0OVIIZEWm6wUpgbda4HGks98CFnPsrnMm3N1bdBI9XdlxYLtf+ir5KsGR5YlEIf/a0kRUwAQ== - dependencies: - "@sentry/types" "7.46.0" - tslib "^1.9.3" +"@sentry/types@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.28.0.tgz#a1cfc004d5714679cb3fed06c27298b0275d13b5" + integrity sha512-hOfqfd92/AzBrEdMgmmV1VfOXJbIfleFTnerRl0mg/+CcNgP/6+Fdonp354TD56ouWNF2WkOM6sEKSXMWp6SEQ== "@sentry/utils@8.27.0": version "8.27.0" @@ -2321,6 +2583,13 @@ dependencies: "@sentry/types" "8.27.0" +"@sentry/utils@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.28.0.tgz#0feb46015033879b2a3cee4c0661386610025f47" + integrity sha512-smhk7PJpvDMQ2DB5p2qn9UeoUHdU41IgjMmS2xklZpa8tjzBTxDeWpGvrX2fuH67D9bAJuLC/XyZjJCHLoEW5g== + dependencies: + "@sentry/types" "8.28.0" + "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -2479,6 +2748,13 @@ dependencies: "@types/node" "*" +"@types/connect@3.4.36": + version "3.4.36" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" + integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== + dependencies: + "@types/node" "*" + "@types/cors@^2.8.9": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" @@ -2650,6 +2926,13 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== +"@types/mysql@2.15.22": + version "2.15.22" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.22.tgz#8705edb9872bf4aa9dbc004cd494e00334e5cdb4" + integrity sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.5.12": version "2.5.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" @@ -2678,6 +2961,31 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/pg-pool@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.4.tgz#b5c60f678094ff3acf3442628a7f708928fcf263" + integrity sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ== + dependencies: + "@types/pg" "*" + +"@types/pg@*": + version "8.11.8" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.11.8.tgz#bc712f1ad8ca664acb1d321b42691d1a166a88d6" + integrity sha512-IqpCf8/569txXN/HoP5i1LjXfKZWL76Yr2R77xgeIICUbAYHeoaEZFhYHo2uDftecLWrTJUq63JvQu8q3lnDyA== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" @@ -2767,6 +3075,11 @@ "@types/mime" "^1" "@types/node" "*" +"@types/shimmer@^1.0.2": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.2.0.tgz#9b706af96fa06416828842397a70dfbbf1c14ded" + integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== + "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -3048,6 +3361,16 @@ accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -3068,6 +3391,11 @@ acorn@^8.4.1, acorn@^8.6.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.8.2: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -4177,6 +4505,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +cjs-module-lexer@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz#677de7ed7efff67cc40c9bf1897fea79d41b5215" + integrity sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g== + cjson@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/cjson/-/cjson-0.3.3.tgz#a92d9c786e5bf9b930806329ee05d5d3261b4afa" @@ -4723,7 +5056,7 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -cookie@0.4.1, cookie@^0.4.0, cookie@^0.4.1: +cookie@0.4.1, cookie@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== @@ -5038,6 +5371,13 @@ debug@^4.3.4: dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -5217,6 +5557,11 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -7364,6 +7709,26 @@ import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz#3e111ff79c639d0bde459bd7ba29dd9fdf357364" + integrity sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg== + dependencies: + acorn "^8.8.2" + acorn-import-assertions "^1.9.0" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + +import-in-the-middle@^1.11.0, import-in-the-middle@^1.8.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.0.tgz#a94c4925b8da18256cde3b3b7b38253e6ca5e708" + integrity sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q== + dependencies: + acorn "^8.8.2" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -9243,11 +9608,6 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru_map@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" - integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= - make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -10034,6 +10394,11 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +module-details-from-path@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== + moo@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" @@ -10205,6 +10570,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-abi@^3.61.0: + version "3.67.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df" + integrity sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw== + dependencies: + semver "^7.3.5" + node-emoji@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" @@ -10640,6 +11012,11 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + on-finished@^2.2.0, on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -10707,6 +11084,14 @@ openapi3-ts@^3.1.1: dependencies: yaml "^2.2.1" +opentelemetry-instrumentation-fetch-node@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/opentelemetry-instrumentation-fetch-node/-/opentelemetry-instrumentation-fetch-node-1.2.3.tgz#beb24048bdccb1943ba2a5bbadca68020e448ea7" + integrity sha512-Qb11T7KvoCevMaSeuamcLsAD+pZnavkhDnlVL0kRozfhl42dKG5Q3anUklAFKJZjY3twLR+BnRa6DlwwkIE/+A== + dependencies: + "@opentelemetry/instrumentation" "^0.46.0" + "@opentelemetry/semantic-conventions" "^1.17.0" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -11141,17 +11526,22 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + pg-pool@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== -pg-protocol@^1.6.1: +pg-protocol@*, pg-protocol@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== -pg-types@^2.1.0: +pg-types@^2.1.0, pg-types@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== @@ -11162,6 +11552,19 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg-types@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" + integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.1.0" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + pg@^8.11.3: version "8.12.0" resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79" @@ -11332,16 +11735,33 @@ postgres-array@~2.0.0: resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== +postgres-date@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" + integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== + postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -11349,6 +11769,16 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" + integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== + postinstall-postinstall@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" @@ -12158,6 +12588,15 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz#606977820d4b5f9be75e5a108ce34cfed25b3bb4" + integrity sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + resolve "^1.22.8" + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -12212,7 +12651,7 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.22.2: +resolve@^1.22.2, resolve@^1.22.8: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -12566,6 +13005,11 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -13710,7 +14154,7 @@ ts-prune@^0.10.3: "true-myth" "^4.1.0" ts-morph "^13.0.1" -tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== From a538ab4517d6e6e864b4112c8f24f9c55b035852 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 17:27:01 +0200 Subject: [PATCH 02/43] feat: setup digest cron --- api/package.json | 1 + api/src/app/index.ts | 5 +++++ api/src/digest/cron.ts | 46 ++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 18 +++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 api/src/digest/cron.ts diff --git a/api/package.json b/api/package.json index e495e77c6..8f4d754bd 100644 --- a/api/package.json +++ b/api/package.json @@ -16,6 +16,7 @@ "class-validator": "^0.13.2", "class-validator-jsonschema": "^3.1.0", "cors": "^2.8.5", + "cron": "^3.1.7", "dotenv": "^8.2.0", "express": "^4.17.1", "express-rate-limit": "^5.2.6", diff --git a/api/src/app/index.ts b/api/src/app/index.ts index 9281b394e..7199f98b4 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -9,6 +9,7 @@ import { createExpressServer, RoutingControllersOptions, useContainer } from "ro import { ArticleController } from "src/article/controller"; import { ConfigService } from "src/config/service"; import { ContributionController } from "src/contribution/controller"; +import { DigestCron } from "src/digest/cron"; import { DocumentationController } from "src/documentation/controller"; import { GithubController } from "src/github/controller"; import { LoggerService } from "src/logger/service"; @@ -28,6 +29,10 @@ useContainer(Container); const { NODE_ENV, PORT } = Container.get(ConfigService).env(); +// Add crons to DI container +const CronServices = [DigestCron]; +CronServices.forEach((service) => Container.get(service)); + // Create the app: export const routingControllersOptions: RoutingControllersOptions = { controllers: [ diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts new file mode 100644 index 000000000..d4a7f4562 --- /dev/null +++ b/api/src/digest/cron.ts @@ -0,0 +1,46 @@ +import { captureException, cron } from "@sentry/node"; +import { CronJob } from "cron"; +import { LoggerService } from "src/logger/service"; +import { Service } from "typedi"; + +@Service() +export class DigestCron { + private readonly schedule = "*/2 * * * * *"; + private isRunning = false; + + constructor(logger: LoggerService) { + const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron"); + new SentryCronJob( + this.schedule, + async () => { + if (this.isRunning) { + logger.warn({ message: "Digest cron already running" }); + return; + } + + this.isRunning = true; + try { + await this.run(); + } catch (error) { + this.isRunning = false; + captureException(error, { tags: { type: "CRON" } }); + logger.error({ + message: `Digest cron failed: ${error}`, + meta: { error }, + }); + } + this.isRunning = false; + }, + () => { + this.isRunning = false; + }, + true, + ); + logger.info({ message: "Digest cron initialized" }); + } + + private async run() { + await new Promise((resolve) => setTimeout(resolve, 4000)); + // throw new Error("Digest cron failed"); + } +} diff --git a/yarn.lock b/yarn.lock index ff4d069b6..eaff5435c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2883,6 +2883,11 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== +"@types/luxon@~3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7" + integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA== + "@types/make-fetch-happen@^9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@types/make-fetch-happen/-/make-fetch-happen-9.0.1.tgz#90d391c9b8f6ba1470302e9f44d6c83f77bf7835" @@ -5177,6 +5182,14 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cron@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.7.tgz#3423d618ba625e78458fff8cb67001672d49ba0d" + integrity sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw== + dependencies: + "@types/luxon" "~3.4.0" + luxon "~3.4.0" + cross-env@^5.1.3: version "5.2.1" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" @@ -9608,6 +9621,11 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +luxon@~3.4.0: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" From 463d2c7de8c6c79c190df65a7b3541f56fe39022 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 19:01:32 +0200 Subject: [PATCH 03/43] feat: setup sqlite database with an ts friendly orm --- .gitignore | 1 + api/db/migrations/0000_fresh_nightshade.sql | 6 + api/db/migrations/meta/0000_snapshot.json | 55 +++ api/db/migrations/meta/_journal.json | 13 + api/drizzle.config.ts | 7 + api/oracle-cloud/docker-compose.yml | 1 + api/package.json | 5 + api/src/app/index.ts | 4 + api/src/config/dto.ts | 3 + api/src/github/service.spec.ts | 1 + api/src/github/types.ts | 3 +- api/src/project/repository.ts | 13 + api/src/project/table.ts | 15 + api/src/sqlite/service.ts | 26 ++ packages/tooling/.prettierignore | 1 + yarn.lock | 470 +++++++++++++++++++- 16 files changed, 613 insertions(+), 11 deletions(-) create mode 100644 api/db/migrations/0000_fresh_nightshade.sql create mode 100644 api/db/migrations/meta/0000_snapshot.json create mode 100644 api/db/migrations/meta/_journal.json create mode 100644 api/drizzle.config.ts create mode 100644 api/src/project/repository.ts create mode 100644 api/src/project/table.ts create mode 100644 api/src/sqlite/service.ts diff --git a/.gitignore b/.gitignore index 1c45eedf3..6c1a3bef1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ coverage # api api/oracle-cloud/build api/fetch_cache +api/sqlite_db api/nodemon.json # web diff --git a/api/db/migrations/0000_fresh_nightshade.sql b/api/db/migrations/0000_fresh_nightshade.sql new file mode 100644 index 000000000..cdcc0a6bd --- /dev/null +++ b/api/db/migrations/0000_fresh_nightshade.sql @@ -0,0 +1,6 @@ +CREATE TABLE `projects` ( + `id` text, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL +); diff --git a/api/db/migrations/meta/0000_snapshot.json b/api/db/migrations/meta/0000_snapshot.json new file mode 100644 index 000000000..4ab92c76e --- /dev/null +++ b/api/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,55 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "b2864bc9-afaf-46a4-b6e3-a0056050b0db", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json new file mode 100644 index 000000000..c39cfe983 --- /dev/null +++ b/api/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1725382220501, + "tag": "0000_fresh_nightshade", + "breakpoints": true + } + ] +} diff --git a/api/drizzle.config.ts b/api/drizzle.config.ts new file mode 100644 index 000000000..e4f7ef132 --- /dev/null +++ b/api/drizzle.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "src/**/*table.ts", + out: "./db/migrations", + dialect: "sqlite", +}); diff --git a/api/oracle-cloud/docker-compose.yml b/api/oracle-cloud/docker-compose.yml index c66ec79cb..d3e522570 100644 --- a/api/oracle-cloud/docker-compose.yml +++ b/api/oracle-cloud/docker-compose.yml @@ -9,3 +9,4 @@ services: - /home/ubuntu/app-env/api.env volumes: - /home/ubuntu/app-data/api/fetch_cache:/usr/src/repo/api/fetch_cache + - /home/ubuntu/app-data/api/sqlite_db:/usr/src/repo/api/sqlite_db diff --git a/api/package.json b/api/package.json index 8f4d754bd..73ed5ee1d 100644 --- a/api/package.json +++ b/api/package.json @@ -12,12 +12,14 @@ "@dzcode.io/utils": "*", "@sentry/node": "^8.28.0", "@sentry/profiling-node": "^8.28.0", + "better-sqlite3": "^11.2.1", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "class-validator-jsonschema": "^3.1.0", "cors": "^2.8.5", "cron": "^3.1.7", "dotenv": "^8.2.0", + "drizzle-orm": "^0.33.0", "express": "^4.17.1", "express-rate-limit": "^5.2.6", "express-robots-txt": "^1.0.0", @@ -34,6 +36,7 @@ }, "devDependencies": { "@dzcode.io/tooling": "*", + "@types/better-sqlite3": "^7.6.11", "@types/body-parser": "^1.19.0", "@types/cors": "^2.8.9", "@types/express": "^4.17.9", @@ -44,6 +47,7 @@ "@types/make-fetch-happen": "^9.0.1", "@types/morgan": "^1.9.2", "@types/swagger-ui-express": "^4.1.2", + "drizzle-kit": "^0.24.2", "faker": "^5.5.3", "glob": "^7.1.7" }, @@ -70,6 +74,7 @@ "build:watch": "lerna run build:alone:watch --scope=@dzcode.io/api --include-dependencies --parallel", "clean": "lerna run clean:alone --scope=@dzcode.io/api --include-dependencies --stream", "clean:alone": "rimraf dist coverage fetch_cache oracle-cloud/build", + "db:generate-migration": "drizzle-kit generate", "deploy": "rimraf ./oracle-cloud/build && ts-node oracle-cloud/deploy.ts production", "deploy:stg": "rimraf ./oracle-cloud/build && ts-node oracle-cloud/deploy.ts staging", "generate:bundle-info": "ts-node ../packages/tooling/bundle-info.ts", diff --git a/api/src/app/index.ts b/api/src/app/index.ts index 7199f98b4..b3ae1f3d9 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -15,6 +15,7 @@ import { GithubController } from "src/github/controller"; import { LoggerService } from "src/logger/service"; import { MilestoneController } from "src/milestone/controller"; import { ProjectController } from "src/project/controller"; +import { SQLiteService } from "src/sqlite/service"; import { TeamController } from "src/team/controller"; import Container from "typedi"; @@ -27,6 +28,9 @@ import { SecurityMiddleware } from "./middlewares/security"; // Use typedi container useContainer(Container); +// Initialize Database +Container.get(SQLiteService); + const { NODE_ENV, PORT } = Container.get(ConfigService).env(); // Add crons to DI container diff --git a/api/src/config/dto.ts b/api/src/config/dto.ts index d9461218c..88b80e00b 100644 --- a/api/src/config/dto.ts +++ b/api/src/config/dto.ts @@ -19,6 +19,9 @@ export class ENVDto { @IsString() FETCH_CACHE_PATH = "./fetch_cache"; + @IsString() + SQLITE_DB_PATH = "./sqlite_db"; + @IsString() @IsOptional() GITHUB_TOKEN?: string; diff --git a/api/src/github/service.spec.ts b/api/src/github/service.spec.ts index ddc826306..7be1f346e 100644 --- a/api/src/github/service.spec.ts +++ b/api/src/github/service.spec.ts @@ -22,6 +22,7 @@ describe("GithubService", () => { const configService = mock({ env: () => ({ FETCH_CACHE_PATH: "", + SQLITE_DB_PATH: "", NODE_ENV: "development", PORT: 0, BUNDLE_INFO: { version: "test" }, diff --git a/api/src/github/types.ts b/api/src/github/types.ts index 3c9578890..a54858519 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -1,4 +1,5 @@ import { IsNumber } from "class-validator"; +import { GeneralResponseDto } from "src/app/types"; export interface GithubUser { login: string; @@ -163,7 +164,7 @@ export interface GitHubRateLimitApiResponse { }; } -export class GetRateLimitResponseDto { +export class GetRateLimitResponseDto extends GeneralResponseDto { @IsNumber() limit!: number; diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts new file mode 100644 index 000000000..fc34179d4 --- /dev/null +++ b/api/src/project/repository.ts @@ -0,0 +1,13 @@ +import { SQLiteService } from "src/sqlite/service"; +import { Service } from "typedi"; + +import { projectsTable } from "./table"; + +@Service() +export class ProjectRepository { + constructor(private readonly sqliteService: SQLiteService) {} + + public async find() { + return this.sqliteService.db.select().from(projectsTable); + } +} diff --git a/api/src/project/table.ts b/api/src/project/table.ts new file mode 100644 index 000000000..a96a0a355 --- /dev/null +++ b/api/src/project/table.ts @@ -0,0 +1,15 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { sql } from "drizzle-orm"; +import { sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const projectsTable = sqliteTable("projects", { + id: text("id"), + recordImportedAt: text("record_imported_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + name: text("name").notNull(), + slug: text("slug").notNull(), +}); + +// projectsTable.$inferSelect satisfies Model; diff --git a/api/src/sqlite/service.ts b/api/src/sqlite/service.ts new file mode 100644 index 000000000..c1865d03f --- /dev/null +++ b/api/src/sqlite/service.ts @@ -0,0 +1,26 @@ +import Database from "better-sqlite3"; +import { drizzle } from "drizzle-orm/better-sqlite3"; +import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { mkdirSync } from "fs"; +import { join } from "path"; +import { ConfigService } from "src/config/service"; +import { LoggerService } from "src/logger/service"; +import { Service } from "typedi"; + +@Service() +export class SQLiteService { + public db; + + constructor( + private readonly configService: ConfigService, + private readonly loggerService: LoggerService, + ) { + this.loggerService.info({ message: "Initializing SQLite database" }); + const { SQLITE_DB_PATH } = this.configService.env(); + mkdirSync(SQLITE_DB_PATH, { recursive: true }); + const sqlite = new Database(join(SQLITE_DB_PATH, "main.sqlite")); + this.db = drizzle(sqlite); + migrate(this.db, { migrationsFolder: join(__dirname, "../../db/migrations") }); + this.loggerService.info({ message: "Database migration complete" }); + } +} diff --git a/packages/tooling/.prettierignore b/packages/tooling/.prettierignore index 8fcc4f0bc..1e8c7d40b 100644 --- a/packages/tooling/.prettierignore +++ b/packages/tooling/.prettierignore @@ -34,3 +34,4 @@ nodemon.json *.ttf *.ts-prunerc *.patch +*.sql diff --git a/yarn.lock b/yarn.lock index eaff5435c..5576102c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -613,6 +613,252 @@ enabled "2.0.x" kuler "^2.0.0" +"@drizzle-team/brocli@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@drizzle-team/brocli/-/brocli-0.10.1.tgz#8b73d65eaf2f6d04f45718ea6f4d789d69526cd3" + integrity sha512-AHy0vjc+n/4w/8Mif+w86qpppHuF3AyXbcWW+R/W7GNA3F5/p2nuhlkCJaTXSLZheB4l1rtHzOfr9A7NwoR/Zg== + +"@esbuild-kit/core-utils@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz#186b6598a5066f0413471d7c4d45828e399ba96c" + integrity sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ== + dependencies: + esbuild "~0.18.20" + source-map-support "^0.5.21" + +"@esbuild-kit/esm-loader@^2.5.5": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz#6eedee46095d7d13b1efc381e2211ed1c60e64ea" + integrity sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA== + dependencies: + "@esbuild-kit/core-utils" "^3.3.2" + get-tsconfig "^4.7.0" + +"@esbuild/aix-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" + integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== + +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" + integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" + integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/android-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" + integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" + integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/darwin-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" + integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" + integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/freebsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" + integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" + integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" + integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" + integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-loong64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" + integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-mips64el@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" + integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" + integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-riscv64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" + integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-s390x@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" + integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/linux-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" + integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/netbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" + integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/openbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" + integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/sunos-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" + integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" + integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" + integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + +"@esbuild/win32-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" + integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== + "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -2728,6 +2974,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/better-sqlite3@^7.6.11": + version "7.6.11" + resolved "https://registry.yarnpkg.com/@types/better-sqlite3/-/better-sqlite3-7.6.11.tgz#95acf22fcf5577624eea202058e26ba239760b9f" + integrity sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg== + dependencies: + "@types/node" "*" + "@types/body-parser@*", "@types/body-parser@^1.19.0": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -3970,6 +4223,14 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== +better-sqlite3@^11.2.1: + version "11.2.1" + resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-11.2.1.tgz#3c6b8a8e2e12444d380e811796b59c8aba012e03" + integrity sha512-Xbt1d68wQnUuFIEVsbt6V+RG30zwgbtCGQ4QOcXVrOH0FE4eHk64FWZ9NUfRHS4/x1PXqwz/+KOrnXD7f0WieA== + dependencies: + bindings "^1.5.0" + prebuild-install "^7.1.1" + bignumber.js@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" @@ -3992,7 +4253,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.1.0: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -4485,7 +4746,7 @@ chokidar@^3.5.3, chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.4: +chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -5428,6 +5689,13 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -5570,7 +5838,7 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -detect-libc@^2.0.2: +detect-libc@^2.0.0, detect-libc@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== @@ -5668,6 +5936,21 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +drizzle-kit@^0.24.2: + version "0.24.2" + resolved "https://registry.yarnpkg.com/drizzle-kit/-/drizzle-kit-0.24.2.tgz#928a56a6a2bec1c5725321f559ec51dc5a943412" + integrity sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ== + dependencies: + "@drizzle-team/brocli" "^0.10.1" + "@esbuild-kit/esm-loader" "^2.5.5" + esbuild "^0.19.7" + esbuild-register "^3.5.0" + +drizzle-orm@^0.33.0: + version "0.33.0" + resolved "https://registry.yarnpkg.com/drizzle-orm/-/drizzle-orm-0.33.0.tgz#ece81e3e85f7559b5f7c01fc09e654e9a2f087fe" + integrity sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA== + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -5860,6 +6143,70 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-register@^3.5.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.6.0.tgz#cf270cfa677baebbc0010ac024b823cbf723a36d" + integrity sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg== + dependencies: + debug "^4.3.4" + +esbuild@^0.19.7: + version "0.19.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04" + integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.12" + "@esbuild/android-arm" "0.19.12" + "@esbuild/android-arm64" "0.19.12" + "@esbuild/android-x64" "0.19.12" + "@esbuild/darwin-arm64" "0.19.12" + "@esbuild/darwin-x64" "0.19.12" + "@esbuild/freebsd-arm64" "0.19.12" + "@esbuild/freebsd-x64" "0.19.12" + "@esbuild/linux-arm" "0.19.12" + "@esbuild/linux-arm64" "0.19.12" + "@esbuild/linux-ia32" "0.19.12" + "@esbuild/linux-loong64" "0.19.12" + "@esbuild/linux-mips64el" "0.19.12" + "@esbuild/linux-ppc64" "0.19.12" + "@esbuild/linux-riscv64" "0.19.12" + "@esbuild/linux-s390x" "0.19.12" + "@esbuild/linux-x64" "0.19.12" + "@esbuild/netbsd-x64" "0.19.12" + "@esbuild/openbsd-x64" "0.19.12" + "@esbuild/sunos-x64" "0.19.12" + "@esbuild/win32-arm64" "0.19.12" + "@esbuild/win32-ia32" "0.19.12" + "@esbuild/win32-x64" "0.19.12" + +esbuild@~0.18.20: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -6237,6 +6584,11 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expect-more@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/expect-more/-/expect-more-1.1.0.tgz#f484ac5bc3f504d34a872d41092e3912778eade9" @@ -6788,6 +7140,11 @@ fresh@0.5.2, fresh@~0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@10.0.0, fs-extra@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" @@ -7004,6 +7361,13 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +get-tsconfig@^4.7.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.0.tgz#125dc13a316f61650a12b20c97c11b8fd996fedd" + integrity sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw== + dependencies: + resolve-pkg-maps "^1.0.0" + get-uri@^6.0.1: version "6.0.3" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.3.tgz#0d26697bc13cf91092e519aa63aa60ee5b6f385a" @@ -7082,6 +7446,11 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -10202,6 +10571,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -10256,16 +10630,16 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.3, minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -10379,6 +10753,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -10543,6 +10922,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -10588,7 +10972,7 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-abi@^3.61.0: +node-abi@^3.3.0, node-abi@^3.61.0: version "3.67.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df" integrity sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw== @@ -11802,6 +12186,24 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -12152,7 +12554,7 @@ raw-body@^2.3.3: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.8: +rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -12642,6 +13044,11 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -13062,6 +13469,20 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -13255,6 +13676,14 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" @@ -13798,6 +14227,27 @@ tailwindcss@^3.4.4: resolve "^1.22.2" sucrase "^3.32.0" +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^3.0.0: version "3.1.7" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" From a6893f6d5dc60e975e61d0ed13f4df88908ee920 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 19:07:30 +0200 Subject: [PATCH 04/43] chore: upgraded prettier --- api/src/app/middlewares/security.ts | 4 ++-- api/src/contribution/types.ts | 2 +- .../content.md | 2 +- package.json | 2 +- packages/models/src/language/index.ts | 8 ++++---- packages/models/src/repository/index.ts | 2 +- packages/utils/src/config/environment.ts | 2 +- packages/utils/src/ts/index.ts | 16 ++++++++-------- web/src/_entry/index.html | 2 +- web/src/components/locale/languages.ts | 2 +- yarn.lock | 8 ++++---- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/api/src/app/middlewares/security.ts b/api/src/app/middlewares/security.ts index dc47787d5..e751bd38b 100644 --- a/api/src/app/middlewares/security.ts +++ b/api/src/app/middlewares/security.ts @@ -16,8 +16,8 @@ export class SecurityMiddleware implements ExpressMiddlewareInterface { this.env === "staging" ? ["https://stage.dzcode.io"] : this.env === "production" - ? ["https://www.dzcode.io"] - : []; + ? ["https://www.dzcode.io"] + : []; this.router.use(helmet()); diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index a80c985c4..77c154aaf 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -21,7 +21,7 @@ export const allFilterNames = ["projects", "languages", "labels"] as const; export class FilterDto { @IsIn(allFilterNames) - name!: typeof allFilterNames[number]; + name!: (typeof allFilterNames)[number]; @ValidateNested({ each: true }) @Type(() => OptionDto) diff --git a/data/models/articles/A_Python_Website_No_Framework_Needed_PART_2/content.md b/data/models/articles/A_Python_Website_No_Framework_Needed_PART_2/content.md index ebe305765..053c99710 100644 --- a/data/models/articles/A_Python_Website_No_Framework_Needed_PART_2/content.md +++ b/data/models/articles/A_Python_Website_No_Framework_Needed_PART_2/content.md @@ -43,7 +43,7 @@ now let's move to the HTML template add this code to it ```html - + diff --git a/package.json b/package.json index 981b45689..1b768b273 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "npm-run-all": "^4.1.5", "patch-package": "^6.5.1", "postinstall-postinstall": "^2.1.0", - "prettier": "^2.4.1", + "prettier": "^3.3.3", "rimraf": "^3.0.2", "semver": "^7.3.5", "syncpack": "^5.8.15", diff --git a/packages/models/src/language/index.ts b/packages/models/src/language/index.ts index 1c62dc4c5..745561bdb 100644 --- a/packages/models/src/language/index.ts +++ b/packages/models/src/language/index.ts @@ -8,14 +8,14 @@ export const allLanguages = [ export class LanguageEntity extends BaseEntity { @IsIn(allLanguages.map(({ code }) => code)) - code!: typeof allLanguages[number]["code"]; + code!: (typeof allLanguages)[number]["code"]; @IsIn(allLanguages.map(({ shortLabel }) => shortLabel)) - shortLabel!: typeof allLanguages[number]["shortLabel"]; + shortLabel!: (typeof allLanguages)[number]["shortLabel"]; @IsIn(allLanguages.map(({ label }) => label)) - label!: typeof allLanguages[number]["label"]; + label!: (typeof allLanguages)[number]["label"]; @IsIn(allLanguages.map(({ direction }) => direction)) - direction!: typeof allLanguages[number]["direction"]; + direction!: (typeof allLanguages)[number]["direction"]; } diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index eddf07d57..69c968e85 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -13,7 +13,7 @@ export class RepositoryStatsEntity extends BaseEntity { } const RepositoryProviders = ["github", "gitlab"] as const; -type RepositoryProvider = typeof RepositoryProviders[number]; +type RepositoryProvider = (typeof RepositoryProviders)[number]; export class RepositoryEntity extends BaseEntity { @IsIn(RepositoryProviders) diff --git a/packages/utils/src/config/environment.ts b/packages/utils/src/config/environment.ts index f4bd3fcd1..33d3e7e44 100644 --- a/packages/utils/src/config/environment.ts +++ b/packages/utils/src/config/environment.ts @@ -1,3 +1,3 @@ export const environments = ["development", "staging", "production"] as const; -export type Environment = typeof environments[number]; +export type Environment = (typeof environments)[number]; diff --git a/packages/utils/src/ts/index.ts b/packages/utils/src/ts/index.ts index a5913d73b..e51728a96 100644 --- a/packages/utils/src/ts/index.ts +++ b/packages/utils/src/ts/index.ts @@ -26,18 +26,18 @@ export type TreeItem> = { export type SplitString = string extends S ? string[] : S extends "" - ? [] - : S extends `${infer T}${D}${infer U}` - ? [T, ...SplitString] - : [S]; + ? [] + : S extends `${infer T}${D}${infer U}` + ? [T, ...SplitString] + : [S]; export type PopSubString = string extends S ? string : S extends "" - ? [] - : S extends `${string}${D}${infer U}` - ? PopSubString - : S; + ? [] + : S extends `${string}${D}${infer U}` + ? PopSubString + : S; export type PyramidSplitString< S extends string, diff --git a/web/src/_entry/index.html b/web/src/_entry/index.html index d681155db..d0b66c75c 100644 --- a/web/src/_entry/index.html +++ b/web/src/_entry/index.html @@ -1,4 +1,4 @@ - + <% if (stage !== "development") { %> {{googleAnalytics}} <% } %> diff --git a/web/src/components/locale/languages.ts b/web/src/components/locale/languages.ts index 97dbf757c..a1764fe1a 100644 --- a/web/src/components/locale/languages.ts +++ b/web/src/components/locale/languages.ts @@ -3,4 +3,4 @@ export const Languages = [ { code: "ar", label: "العربية" }, ] as const; -export type Language = typeof Languages[number]; +export type Language = (typeof Languages)[number]; diff --git a/yarn.lock b/yarn.lock index 5576102c9..0f0fc9091 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12219,10 +12219,10 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier@^2.4.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893" - integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg== +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-bytes@^5.6.0: version "5.6.0" From a52fe0608beafb010bd16c17e2dcbe21de3df861 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 19:08:05 +0200 Subject: [PATCH 05/43] feat: type-checked table against model --- api/src/project/table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/project/table.ts b/api/src/project/table.ts index a96a0a355..76f47f104 100644 --- a/api/src/project/table.ts +++ b/api/src/project/table.ts @@ -12,4 +12,4 @@ export const projectsTable = sqliteTable("projects", { slug: text("slug").notNull(), }); -// projectsTable.$inferSelect satisfies Model; +projectsTable.$inferSelect satisfies Model; From 60ec96e093cc2f13ca149380edcbefcf685f6943 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 21:18:33 +0200 Subject: [PATCH 06/43] fixed lint issues --- api/package.json | 2 +- api/src/project/controller.ts | 13 +++---------- api/src/project/types.ts | 3 +-- data/package.json | 2 +- mobile/package.json | 2 +- package.json | 3 ++- packages/models/package.json | 2 +- packages/tooling/package.json | 2 +- packages/ui-mobile/package.jsonc | 2 +- packages/utils/package.json | 2 +- web/package.json | 2 +- web/rsbuild.config.ts | 2 +- web/src/pages/not-found/index.tsx | 1 - web/src/pages/projects/index.tsx | 5 +++-- web/src/redux/slices/settings.ts | 2 +- yarn.lock | 13 ++++--------- 16 files changed, 23 insertions(+), 35 deletions(-) diff --git a/api/package.json b/api/package.json index 73ed5ee1d..537884909 100644 --- a/api/package.json +++ b/api/package.json @@ -84,7 +84,7 @@ "lint:eslint": "eslint --config ../packages/tooling/.eslintrc.json --ignore-path ../packages/tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --log-level warn", "lint:ts-prune": "ts-node ../packages/tooling/setup-ts-prune.ts && ts-prune --error", "lint:tsc": "tspc --noEmit", "start": "node dist/app/index.js", diff --git a/api/src/project/controller.ts b/api/src/project/controller.ts index c5f3715ac..629926d67 100644 --- a/api/src/project/controller.ts +++ b/api/src/project/controller.ts @@ -1,20 +1,14 @@ import { Controller, Get } from "routing-controllers"; import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; -import { DataService } from "src/data/service"; -import { GithubService } from "src/github/service"; -import { LoggerService } from "src/logger/service"; import { Service } from "typedi"; +import { ProjectRepository } from "./repository"; import { GetProjectsResponseDto } from "./types"; @Service() @Controller("/Projects") export class ProjectController { - constructor( - private readonly githubService: GithubService, - private readonly dataService: DataService, - private readonly loggerService: LoggerService, - ) {} + constructor(private readonly projectRepository: ProjectRepository) {} @Get("/") @OpenAPI({ @@ -22,8 +16,7 @@ export class ProjectController { }) @ResponseSchema(GetProjectsResponseDto) public async getProjects(): Promise { - // get projects from /data folder: - const projects = await this.dataService.listProjects(); + const projects = await this.projectRepository.find(); return { projects, diff --git a/api/src/project/types.ts b/api/src/project/types.ts index 35f66202b..70d57738e 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -1,6 +1,5 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; -import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { Type } from "class-transformer"; import { ValidateNested } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; @@ -8,5 +7,5 @@ import { GeneralResponseDto } from "src/app/types"; export class GetProjectsResponseDto extends GeneralResponseDto { @ValidateNested({ each: true }) @Type(() => ProjectEntity) - projects!: Array & { repositories: Model[] }>; + projects!: Array>; } diff --git a/data/package.json b/data/package.json index 261661e2d..628ce361a 100644 --- a/data/package.json +++ b/data/package.json @@ -49,7 +49,7 @@ "lint:eslint": "eslint --config ../packages/tooling/.eslintrc.json --ignore-path ../packages/tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --log-level warn", "lint:ts-prune": "ts-node ../packages/tooling/setup-ts-prune.ts && ts-prune --error", "lint:tsc": "tsc --noEmit", "test": "jest src", diff --git a/mobile/package.json b/mobile/package.json index 40368db0a..a2ca443b1 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -89,7 +89,7 @@ "lint:eslint": "eslint --config ../packages/tooling/.eslintrc.json --ignore-path ../packages/tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --log-level warn", "lint:tsc": "tsc --noEmit", "start:dev": "echo \" \n\\033[0;32mPlease run in a separate terminal session the following command:\n \n\\033[0;32myarn --cwd=mobile start:expo\n \n \"", "start:expo": "rimraf .bundle-info.json && expo start --port 1010", diff --git a/package.json b/package.json index 1b768b273..00b159b01 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@dzcode.io/root", + "dependencies": {}, "devDependencies": { "@prettier/plugin-pug": "^1.17.3", "@sentry/cli": "^1.71.0", @@ -39,7 +40,7 @@ "syncpack format" ], "./*.*": [ - "prettier --config ./packages/tooling/.prettierrc --ignore-path ./packages/tooling/.prettierignore --loglevel warn --write" + "prettier --config ./packages/tooling/.prettierrc --ignore-path ./packages/tooling/.prettierignore --log-level warn --write" ] }, "private": true, diff --git a/packages/models/package.json b/packages/models/package.json index 45f34b3c9..d992b80da 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -37,7 +37,7 @@ "lint:eslint": "eslint --config ../tooling/.eslintrc.json --ignore-path ../tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --log-level warn", "test": "yarn build && yarn test:alone", "test:alone": "jest --config ../tooling/jest.config.ts --rootDir .", "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" diff --git a/packages/tooling/package.json b/packages/tooling/package.json index 439879183..95ba99600 100644 --- a/packages/tooling/package.json +++ b/packages/tooling/package.json @@ -21,6 +21,6 @@ "lint:eslint": "eslint --config .eslintrc.json --ignore-path .eslintignore --report-unused-disable-directives", "lint:fix": "yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config .prettierrc --ignore-path .prettierignore --loglevel warn" + "lint:prettier": "prettier --config .prettierrc --ignore-path .prettierignore --log-level warn" } } diff --git a/packages/ui-mobile/package.jsonc b/packages/ui-mobile/package.jsonc index e86072b5a..329778682 100644 --- a/packages/ui-mobile/package.jsonc +++ b/packages/ui-mobile/package.jsonc @@ -49,7 +49,7 @@ "lint:eslint": "eslint --config ../tooling/.eslintrc.json --ignore-path ../tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --log-level warn", "test": "yarn build && yarn test:alone", "test:alone": "echo \"skipped for now\"", "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" diff --git a/packages/utils/package.json b/packages/utils/package.json index b6938f23a..9aca034e3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -31,7 +31,7 @@ "lint:eslint": "eslint --config ../tooling/.eslintrc.json --ignore-path ../tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --log-level warn", "test": "yarn build && yarn test:alone", "test:alone": "jest --config ../tooling/jest.config.ts --rootDir .", "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" diff --git a/web/package.json b/web/package.json index 423204ce1..34235334d 100644 --- a/web/package.json +++ b/web/package.json @@ -73,7 +73,7 @@ "lint:eslint": "eslint --config ../packages/tooling/.eslintrc.json --ignore-path ../packages/tooling/.eslintignore --report-unused-disable-directives", "lint:fix": "yarn build && yarn lint:fix:alone", "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", + "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --log-level warn", "lint:ts-prune": "ts-node ../packages/tooling/setup-ts-prune.ts && ts-prune --error", "lint:tsc": "tsc --noEmit", "start:dev": "rsbuild dev --open", diff --git a/web/rsbuild.config.ts b/web/rsbuild.config.ts index 4976fe954..af7b49719 100644 --- a/web/rsbuild.config.ts +++ b/web/rsbuild.config.ts @@ -1,4 +1,4 @@ -import { Environment, environments } from "@dzcode.io/utils/dist/config/environment"; +import { environments } from "@dzcode.io/utils/dist/config/environment"; import { defineConfig } from "@rsbuild/core"; import { pluginReact } from "@rsbuild/plugin-react"; import { readFileSync } from "fs"; diff --git a/web/src/pages/not-found/index.tsx b/web/src/pages/not-found/index.tsx index b5dc16a96..4b64c9104 100644 --- a/web/src/pages/not-found/index.tsx +++ b/web/src/pages/not-found/index.tsx @@ -2,7 +2,6 @@ import { Helmet } from "react-helmet-async"; import svg from "src/assets/svg/404.svg"; import { Link } from "src/components/link"; import { Locale, useLocale } from "src/components/locale"; -import { Markdown } from "src/components/markdown"; // ts-prune-ignore-next export default function Page(): JSX.Element { diff --git a/web/src/pages/projects/index.tsx b/web/src/pages/projects/index.tsx index c39afeeb9..60e69263c 100644 --- a/web/src/pages/projects/index.tsx +++ b/web/src/pages/projects/index.tsx @@ -46,13 +46,14 @@ export default function Page(): JSX.Element {

{project.name}

    - {project.repositories.map((repository, repositoryIndex) => ( + {/* @TODO-ZM: put this back */} + {/* {project.repositories.map((repository, repositoryIndex) => (
  • {getRepositoryName(repository)}
  • - ))} + ))} */}
diff --git a/web/src/redux/slices/settings.ts b/web/src/redux/slices/settings.ts index c55ac3382..43dda2a4d 100644 --- a/web/src/redux/slices/settings.ts +++ b/web/src/redux/slices/settings.ts @@ -1,5 +1,5 @@ import { createSlice } from "@reduxjs/toolkit"; -import { Language, Languages } from "src/components/locale/languages"; +import { Language } from "src/components/locale/languages"; import { getInitialLanguageCode } from "src/utils/website-language"; // ts-prune-ignore-next diff --git a/yarn.lock b/yarn.lock index 0f0fc9091..308f1cb5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4616,15 +4616,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== -caniuse-lite@^1.0.30001280, caniuse-lite@^1.0.30001370: - version "1.0.30001473" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz#3859898b3cab65fc8905bb923df36ad35058153c" - integrity sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg== - -caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001616, caniuse-lite@^1.0.30001640: - version "1.0.30001643" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" - integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== +caniuse-lite@^1.0.30001280, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001616, caniuse-lite@^1.0.30001640: + version "1.0.30001655" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz" + integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== caseless@~0.12.0: version "0.12.0" From af16f60ae4595570b37164a31865d93fc6e80b60 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Tue, 3 Sep 2024 23:48:26 +0200 Subject: [PATCH 07/43] added projects to database --- api/db/migrations/0000_fresh_nightshade.sql | 6 -- .../0000_vengeful_the_liberteens.sql | 9 +++ api/db/migrations/meta/0000_snapshot.json | 26 +++++-- api/db/migrations/meta/_journal.json | 4 +- api/src/data/service.ts | 2 +- api/src/digest/cron.ts | 71 +++++++++++++++++-- api/src/project/repository.ts | 14 ++++ api/src/project/table.ts | 7 +- packages/models/src/project/index.spec.ts | 2 + packages/models/src/project/index.ts | 10 ++- packages/models/src/repository/index.spec.ts | 1 - packages/models/src/repository/index.ts | 16 ++--- 12 files changed, 133 insertions(+), 35 deletions(-) delete mode 100644 api/db/migrations/0000_fresh_nightshade.sql create mode 100644 api/db/migrations/0000_vengeful_the_liberteens.sql diff --git a/api/db/migrations/0000_fresh_nightshade.sql b/api/db/migrations/0000_fresh_nightshade.sql deleted file mode 100644 index cdcc0a6bd..000000000 --- a/api/db/migrations/0000_fresh_nightshade.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE `projects` ( - `id` text, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `name` text NOT NULL, - `slug` text NOT NULL -); diff --git a/api/db/migrations/0000_vengeful_the_liberteens.sql b/api/db/migrations/0000_vengeful_the_liberteens.sql new file mode 100644 index 000000000..414fb0717 --- /dev/null +++ b/api/db/migrations/0000_vengeful_the_liberteens.sql @@ -0,0 +1,9 @@ +CREATE TABLE `projects` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `projects_slug_unique` ON `projects` (`slug`); \ No newline at end of file diff --git a/api/db/migrations/meta/0000_snapshot.json b/api/db/migrations/meta/0000_snapshot.json index 4ab92c76e..9faf74bdd 100644 --- a/api/db/migrations/meta/0000_snapshot.json +++ b/api/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "b2864bc9-afaf-46a4-b6e3-a0056050b0db", + "id": "a7c87785-bca5-4d27-981f-97be7c2428c6", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "projects": { @@ -9,10 +9,10 @@ "columns": { "id": { "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true }, "record_imported_at": { "name": "record_imported_at", @@ -35,9 +35,23 @@ "primaryKey": false, "notNull": true, "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": ["slug"], + "isUnique": true } }, - "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index c39cfe983..baaecc362 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1725382220501, - "tag": "0000_fresh_nightshade", + "when": 1725398893540, + "tag": "0000_vengeful_the_liberteens", "breakpoints": true } ] diff --git a/api/src/data/service.ts b/api/src/data/service.ts index 7b58806d5..82a3da263 100644 --- a/api/src/data/service.ts +++ b/api/src/data/service.ts @@ -7,7 +7,7 @@ import { DataArticleEntity, DataDocumentationEntity, DataProjectEntity } from ". @Service() export class DataService { - public listProjects = async (): Promise => { + public listProjects = async (): Promise[]> => { const projects = getCollection(this.dataModelsPath, "projects", "list.json"); if (projects === 404) throw new Error("Projects list not found"); diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index d4a7f4562..569260bc6 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -1,14 +1,25 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { captureException, cron } from "@sentry/node"; import { CronJob } from "cron"; +import { DataService } from "src/data/service"; +import { GithubService } from "src/github/service"; import { LoggerService } from "src/logger/service"; +import { ProjectRepository } from "src/project/repository"; import { Service } from "typedi"; @Service() export class DigestCron { - private readonly schedule = "*/2 * * * * *"; + private readonly schedule = "15 * * * *"; private isRunning = false; - constructor(logger: LoggerService) { + constructor( + private readonly logger: LoggerService, + private readonly dataService: DataService, + private readonly githubService: GithubService, + private readonly projectsRepository: ProjectRepository, + ) { const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron"); new SentryCronJob( this.schedule, @@ -35,12 +46,64 @@ export class DigestCron { this.isRunning = false; }, true, + undefined, + undefined, + true, ); logger.info({ message: "Digest cron initialized" }); } + /** + * Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId. + */ private async run() { - await new Promise((resolve) => setTimeout(resolve, 4000)); - // throw new Error("Digest cron failed"); + const runId = Math.random().toString(36).slice(2); + this.logger.info({ message: `Digest cron started, runId: ${runId}` }); + + const projects: Model[] = []; + const projectsFromDataFolder = await this.dataService.listProjects(); + // @TODO-ZM: add relations + for (const project of projectsFromDataFolder) { + try { + const repositories: Model[] = []; + const repositoriesFromDataFolder = project.repositories; + for (const repository of repositoriesFromDataFolder) { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const languages: string[] = await this.githubService.listRepositoryLanguages({ + owner: repository.owner, + repository: repository.repository, + }); + + // do more stuff here + repositories.push({ + ...repository, + }); + } catch (error) { + // @TODO-ZM: capture error + this.logger.error({ + message: `Failed to fetch languages for repository: ${repository.owner}/${repository.repository}`, + meta: { error }, + }); + } + } + + if (repositories.length > 0) { + projects.push({ ...project, runId }); + } + } catch (error) { + // @TODO-ZM: capture error + this.logger.error({ + message: `Failed to fetch repositories for project: ${project.name}`, + meta: { error }, + }); + } + } + + for (const project of projects) { + await this.projectsRepository.upsert(project); + } + await this.projectsRepository.deleteAllButWithRunId(runId); + this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); } } diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index fc34179d4..cdc096aac 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,3 +1,6 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { ne } from "drizzle-orm"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -10,4 +13,15 @@ export class ProjectRepository { public async find() { return this.sqliteService.db.select().from(projectsTable); } + + public async upsert(project: Model) { + return await this.sqliteService.db.insert(projectsTable).values(project).onConflictDoUpdate({ + target: projectsTable.slug, + set: project, + }); + } + + public async deleteAllButWithRunId(runId: string) { + return await this.sqliteService.db.delete(projectsTable).where(ne(projectsTable.runId, runId)); + } } diff --git a/api/src/project/table.ts b/api/src/project/table.ts index 76f47f104..faf41b369 100644 --- a/api/src/project/table.ts +++ b/api/src/project/table.ts @@ -1,15 +1,16 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { sql } from "drizzle-orm"; -import { sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const projectsTable = sqliteTable("projects", { - id: text("id"), + id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), recordImportedAt: text("record_imported_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), name: text("name").notNull(), - slug: text("slug").notNull(), + slug: text("slug").notNull().unique(), + runId: text("run_id").notNull().default("initial-run-id"), }); projectsTable.$inferSelect satisfies Model; diff --git a/packages/models/src/project/index.spec.ts b/packages/models/src/project/index.spec.ts index 2edf4c72f..792eb94ed 100644 --- a/packages/models/src/project/index.spec.ts +++ b/packages/models/src/project/index.spec.ts @@ -5,6 +5,8 @@ import { ProjectEntity } from "."; runDTOTestCases( ProjectEntity, { + id: 0, + runId: "initial-run-id", name: "Leblad", repositories: [ { diff --git a/packages/models/src/project/index.ts b/packages/models/src/project/index.ts index c060e8279..811b41ff1 100644 --- a/packages/models/src/project/index.ts +++ b/packages/models/src/project/index.ts @@ -1,9 +1,17 @@ import { Type } from "class-transformer"; -import { IsString, ValidateNested } from "class-validator"; +import { IsNumber, IsString, ValidateNested } from "class-validator"; import { BaseEntity, Model } from "src/_base"; import { RepositoryEntity } from "src/repository"; export class ProjectEntity extends BaseEntity { + // @TODO-ZM: move this to BaseEntity + @IsNumber() + id!: number; + + // @TODO-ZM: move this to BaseEntity + @IsString() + runId!: string; + @IsString() slug!: string; diff --git a/packages/models/src/repository/index.spec.ts b/packages/models/src/repository/index.spec.ts index 2d14be94d..fe1140862 100644 --- a/packages/models/src/repository/index.spec.ts +++ b/packages/models/src/repository/index.spec.ts @@ -12,6 +12,5 @@ runDTOTestCases( { contributions: [], contributors: [], - stats: { contributionCount: 10, languages: ["TypeScript", "Rust"] }, }, ); diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index 69c968e85..6e46f6f69 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -1,17 +1,9 @@ import { Type } from "class-transformer"; -import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator"; +import { IsIn, IsString, ValidateNested } from "class-validator"; import { BaseEntity, Model } from "src/_base"; import { AccountEntity } from "src/account"; import { ContributionEntity } from "src/contribution"; -export class RepositoryStatsEntity extends BaseEntity { - @IsNumber() - contributionCount!: number; - - @IsString({ each: true }) - languages!: string[]; -} - const RepositoryProviders = ["github", "gitlab"] as const; type RepositoryProvider = (typeof RepositoryProviders)[number]; @@ -25,8 +17,10 @@ export class RepositoryEntity extends BaseEntity { @IsString() repository!: string; - @Type(() => RepositoryStatsEntity) - stats?: Model; + // TODO-ZM: add programming languages + // @ValidateNested({ each: true }) + // @Type(() => ProgrammingLanguageEntity) + // programmingLanguages?: Model[]; @ValidateNested({ each: true }) @Type(() => AccountEntity) From fe5079cad9ea14653bb8f5a96d8ce18dd8627d87 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 4 Sep 2024 16:31:43 +0200 Subject: [PATCH 08/43] added repositories to database --- api/db/migrations/0001_brave_sentry.sql | 10 ++ api/db/migrations/meta/0001_snapshot.json | 128 ++++++++++++++++++ api/db/migrations/meta/_journal.json | 7 + api/src/contribution/repository.ts | 2 +- api/src/digest/cron.ts | 9 +- api/src/repository/repository.ts | 28 ++++ api/src/repository/table.ts | 25 ++++ api/src/team/repository.ts | 2 +- data/models/projects/AiPalettes/info.json | 2 +- .../Algeria_Covid19_Tracker/info.json | 2 +- data/models/projects/Algeria_Logos/info.json | 2 +- .../projects/Algeria_Startup_Jobs/info.json | 4 +- data/models/projects/Algeria_boukal/info.json | 2 +- .../info.json | 2 +- .../AlgeriansCoinsObjectDetection/info.json | 2 +- data/models/projects/Archiy_Package/info.json | 2 +- data/models/projects/Check_Hadith/info.json | 2 +- .../projects/Dzair_Data_Usage/info.json | 2 +- .../projects/Face_Mask_Detect/info.json | 2 +- .../Flutter_Reaction_Button/info.json | 2 +- .../projects/Godaddy_Reseller/info.json | 2 +- .../Godaddy_Reseller_Api_Client/info.json | 2 +- .../models/projects/Gold_Prices_Web/info.json | 2 +- data/models/projects/Kuliya/info.json | 2 +- .../info.json | 2 +- .../Laravel_Algerian_Provinces/info.json | 2 +- data/models/projects/Leblad/info.json | 8 +- .../projects/List_to_Tree_aka_l2t/info.json | 2 +- .../MERN_Auth_Roles_Boilerplate/info.json | 2 +- data/models/projects/Madinati_Meteo/info.json | 2 +- data/models/projects/Melyon_Theme/info.json | 2 +- data/models/projects/Mishkal/info.json | 2 +- data/models/projects/Moadaly/info.json | 2 +- data/models/projects/Mylinks_Space/info.json | 2 +- data/models/projects/Open-listings/info.json | 2 +- data/models/projects/Openadhan/info.json | 2 +- .../models/projects/PHP_JSON_Tongue/info.json | 2 +- data/models/projects/Python_Complex/info.json | 2 +- data/models/projects/QuranIPFS/info.json | 2 +- .../React_Glassmorphism_Components/info.json | 2 +- .../projects/React_Help_Create/info.json | 2 +- .../React_Native_Currency_Converter/info.json | 2 +- .../React_Native_Help_Create/info.json | 2 +- .../projects/React_Project_Builder/info.json | 2 +- data/models/projects/STRM_Test/info.json | 2 +- data/models/projects/System_Monitor/info.json | 2 +- .../projects/Violence_Detection/info.json | 2 +- .../Voice_Translator_React_Native/info.json | 2 +- .../projects/Weather_Forecast/info.json | 2 +- .../projects/dzcode.io_website/info.json | 2 +- .../who_want_to_millionaire/info.json | 2 +- packages/models/src/project/index.spec.ts | 8 +- packages/models/src/repository/index.spec.ts | 4 +- packages/models/src/repository/index.ts | 12 +- 54 files changed, 273 insertions(+), 56 deletions(-) create mode 100644 api/db/migrations/0001_brave_sentry.sql create mode 100644 api/db/migrations/meta/0001_snapshot.json create mode 100644 api/src/repository/repository.ts create mode 100644 api/src/repository/table.ts diff --git a/api/db/migrations/0001_brave_sentry.sql b/api/db/migrations/0001_brave_sentry.sql new file mode 100644 index 000000000..9772a054a --- /dev/null +++ b/api/db/migrations/0001_brave_sentry.sql @@ -0,0 +1,10 @@ +CREATE TABLE `repositories` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `provider` text NOT NULL, + `owner` text NOT NULL, + `name` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `repositories_provider_owner_name_unique` ON `repositories` (`provider`,`owner`,`name`); \ No newline at end of file diff --git a/api/db/migrations/meta/0001_snapshot.json b/api/db/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..9472308b0 --- /dev/null +++ b/api/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,128 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2bcb5a42-64e3-41ba-b12c-58fecbeccb22", + "prevId": "a7c87785-bca5-4d27-981f-97be7c2428c6", + "tables": { + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "repositories_provider_owner_name_unique": { + "name": "repositories_provider_owner_name_unique", + "columns": ["provider", "owner", "name"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index baaecc362..b8e706c85 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1725398893540, "tag": "0000_vengeful_the_liberteens", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1725459545225, + "tag": "0001_brave_sentry", + "breakpoints": true } ] } diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index 2eda63401..9933d6d70 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -27,7 +27,7 @@ export class ContributionRepository { ...pV, ...repositories .filter(({ provider }) => provider === "github") - .map(async ({ owner, repository }) => { + .map(async ({ owner, name: repository }) => { try { const issuesIncludingPRs = await this.githubService.listRepositoryIssues({ owner, diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 569260bc6..e8a24e5d8 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -7,6 +7,7 @@ import { DataService } from "src/data/service"; import { GithubService } from "src/github/service"; import { LoggerService } from "src/logger/service"; import { ProjectRepository } from "src/project/repository"; +import { RepositoryRepository } from "src/repository/repository"; import { Service } from "typedi"; @Service() @@ -19,6 +20,7 @@ export class DigestCron { private readonly dataService: DataService, private readonly githubService: GithubService, private readonly projectsRepository: ProjectRepository, + private readonly repositoriesRepository: RepositoryRepository, ) { const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron"); new SentryCronJob( @@ -72,7 +74,7 @@ export class DigestCron { // eslint-disable-next-line @typescript-eslint/no-unused-vars const languages: string[] = await this.githubService.listRepositoryLanguages({ owner: repository.owner, - repository: repository.repository, + repository: repository.name, }); // do more stuff here @@ -82,13 +84,16 @@ export class DigestCron { } catch (error) { // @TODO-ZM: capture error this.logger.error({ - message: `Failed to fetch languages for repository: ${repository.owner}/${repository.repository}`, + message: `Failed to fetch languages for repository: ${repository.owner}/${repository.name}`, meta: { error }, }); } } if (repositories.length > 0) { + for (const repository of repositories) { + await this.repositoriesRepository.upsert(repository); + } projects.push({ ...project, runId }); } } catch (error) { diff --git a/api/src/repository/repository.ts b/api/src/repository/repository.ts new file mode 100644 index 000000000..44c4ae1c0 --- /dev/null +++ b/api/src/repository/repository.ts @@ -0,0 +1,28 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; +import { ne } from "drizzle-orm"; +import { SQLiteService } from "src/sqlite/service"; +import { Service } from "typedi"; + +import { repositoriesTable } from "./table"; + +@Service() +export class RepositoryRepository { + constructor(private readonly sqliteService: SQLiteService) {} + + public async upsert(repository: Model) { + return await this.sqliteService.db + .insert(repositoriesTable) + .values(repository) + .onConflictDoUpdate({ + target: [repositoriesTable.provider, repositoriesTable.owner, repositoriesTable.name], + set: repository, + }); + } + + public async deleteAllButWithRunId(runId: string) { + return await this.sqliteService.db + .delete(repositoriesTable) + .where(ne(repositoriesTable.runId, runId)); + } +} diff --git a/api/src/repository/table.ts b/api/src/repository/table.ts new file mode 100644 index 000000000..4ab24aef6 --- /dev/null +++ b/api/src/repository/table.ts @@ -0,0 +1,25 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; +import { sql } from "drizzle-orm"; +import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core"; + +export const repositoriesTable = sqliteTable( + "repositories", + { + id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + recordImportedAt: text("record_imported_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + provider: text("provider").notNull().$type(), + owner: text("owner").notNull(), + name: text("name").notNull(), + runId: text("run_id").notNull().default("initial-run-id"), + }, + (table) => { + return { + uniquePath: unique().on(table.provider, table.owner, table.name), + }; + }, +); + +repositoriesTable.$inferSelect satisfies Model; diff --git a/api/src/team/repository.ts b/api/src/team/repository.ts index 0a78f5531..2f50a2c1c 100644 --- a/api/src/team/repository.ts +++ b/api/src/team/repository.ts @@ -35,7 +35,7 @@ export class TeamRepository { // get contributors from all the repos we have await Promise.all( - repositories.map(async ({ provider, owner, repository }) => { + repositories.map(async ({ provider, owner, name: repository }) => { if (provider === "github") { try { const contributors = await this.githubService.listRepositoryContributors({ diff --git a/data/models/projects/AiPalettes/info.json b/data/models/projects/AiPalettes/info.json index fe6968344..c9fec86d6 100644 --- a/data/models/projects/AiPalettes/info.json +++ b/data/models/projects/AiPalettes/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "bondbenz", - "repository": "aipalettes" + "name": "aipalettes" } ] } diff --git a/data/models/projects/Algeria_Covid19_Tracker/info.json b/data/models/projects/Algeria_Covid19_Tracker/info.json index c3c2a0d81..751d926ec 100644 --- a/data/models/projects/Algeria_Covid19_Tracker/info.json +++ b/data/models/projects/Algeria_Covid19_Tracker/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "DGLcsGaming", - "repository": "dz-covid19.com" + "name": "dz-covid19.com" } ] } diff --git a/data/models/projects/Algeria_Logos/info.json b/data/models/projects/Algeria_Logos/info.json index e7086a577..754f75be6 100644 --- a/data/models/projects/Algeria_Logos/info.json +++ b/data/models/projects/Algeria_Logos/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "aimen08", - "repository": "algerialogos" + "name": "algerialogos" } ] } diff --git a/data/models/projects/Algeria_Startup_Jobs/info.json b/data/models/projects/Algeria_Startup_Jobs/info.json index 61fe3020c..ceaf14751 100644 --- a/data/models/projects/Algeria_Startup_Jobs/info.json +++ b/data/models/projects/Algeria_Startup_Jobs/info.json @@ -4,12 +4,12 @@ { "provider": "github", "owner": "algeriastartupjobs", - "repository": "algeriastartupjobs.com" + "name": "algeriastartupjobs.com" }, { "provider": "github", "owner": "algeriastartupjobs", - "repository": "diagrams" + "name": "diagrams" } ] } diff --git a/data/models/projects/Algeria_boukal/info.json b/data/models/projects/Algeria_boukal/info.json index a74f7b677..79b9d4bfa 100644 --- a/data/models/projects/Algeria_boukal/info.json +++ b/data/models/projects/Algeria_boukal/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "jusinamine", - "repository": "algeria-boukal" + "name": "algeria-boukal" } ] } diff --git a/data/models/projects/Algerian_Administrative_Division/info.json b/data/models/projects/Algerian_Administrative_Division/info.json index 515c72a99..bd2926018 100644 --- a/data/models/projects/Algerian_Administrative_Division/info.json +++ b/data/models/projects/Algerian_Administrative_Division/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "mohsenuss91", - "repository": "AlgerianAdministrativeDivision" + "name": "AlgerianAdministrativeDivision" } ] } diff --git a/data/models/projects/AlgeriansCoinsObjectDetection/info.json b/data/models/projects/AlgeriansCoinsObjectDetection/info.json index 91149b53a..47581aace 100644 --- a/data/models/projects/AlgeriansCoinsObjectDetection/info.json +++ b/data/models/projects/AlgeriansCoinsObjectDetection/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "lablack576", - "repository": "algerians_coins_object_detection" + "name": "algerians_coins_object_detection" } ] } diff --git a/data/models/projects/Archiy_Package/info.json b/data/models/projects/Archiy_Package/info.json index 8ce0e7cfe..9e28dabdb 100644 --- a/data/models/projects/Archiy_Package/info.json +++ b/data/models/projects/Archiy_Package/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "yassine-youcefi", - "repository": "Archy" + "name": "Archy" } ] } diff --git a/data/models/projects/Check_Hadith/info.json b/data/models/projects/Check_Hadith/info.json index b8bbf8921..d57bae68a 100644 --- a/data/models/projects/Check_Hadith/info.json +++ b/data/models/projects/Check_Hadith/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "adelpro", - "repository": "check-hadith" + "name": "check-hadith" } ] } diff --git a/data/models/projects/Dzair_Data_Usage/info.json b/data/models/projects/Dzair_Data_Usage/info.json index d7fea036a..1a7ed9dc2 100644 --- a/data/models/projects/Dzair_Data_Usage/info.json +++ b/data/models/projects/Dzair_Data_Usage/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "dfourcfive", - "repository": "dzair_data_usage" + "name": "dzair_data_usage" } ] } diff --git a/data/models/projects/Face_Mask_Detect/info.json b/data/models/projects/Face_Mask_Detect/info.json index 83908c753..0da4cb6a4 100644 --- a/data/models/projects/Face_Mask_Detect/info.json +++ b/data/models/projects/Face_Mask_Detect/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "Ghali-Benbernou", - "repository": "mask-detect" + "name": "mask-detect" } ] } diff --git a/data/models/projects/Flutter_Reaction_Button/info.json b/data/models/projects/Flutter_Reaction_Button/info.json index 7aa8adb8b..ce6fdbbab 100644 --- a/data/models/projects/Flutter_Reaction_Button/info.json +++ b/data/models/projects/Flutter_Reaction_Button/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "geekabdelouahed", - "repository": "flutter-reaction-button" + "name": "flutter-reaction-button" } ] } diff --git a/data/models/projects/Godaddy_Reseller/info.json b/data/models/projects/Godaddy_Reseller/info.json index cc4cb0e5f..ad67d6d88 100644 --- a/data/models/projects/Godaddy_Reseller/info.json +++ b/data/models/projects/Godaddy_Reseller/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "bychis", - "repository": "godaddy-reseller" + "name": "godaddy-reseller" } ] } diff --git a/data/models/projects/Godaddy_Reseller_Api_Client/info.json b/data/models/projects/Godaddy_Reseller_Api_Client/info.json index 9fdffe165..d8fc69432 100644 --- a/data/models/projects/Godaddy_Reseller_Api_Client/info.json +++ b/data/models/projects/Godaddy_Reseller_Api_Client/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "Omar-Belghaouti", - "repository": "godaddy-reseller-api-client" + "name": "godaddy-reseller-api-client" } ] } diff --git a/data/models/projects/Gold_Prices_Web/info.json b/data/models/projects/Gold_Prices_Web/info.json index 142aafb55..77f168971 100644 --- a/data/models/projects/Gold_Prices_Web/info.json +++ b/data/models/projects/Gold_Prices_Web/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "jusinamine", - "repository": "gold-prices-web" + "name": "gold-prices-web" } ] } diff --git a/data/models/projects/Kuliya/info.json b/data/models/projects/Kuliya/info.json index 917cc0c9b..ea7b3ff2f 100644 --- a/data/models/projects/Kuliya/info.json +++ b/data/models/projects/Kuliya/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "dzcode-io", - "repository": "kuliya" + "name": "kuliya" } ] } diff --git a/data/models/projects/Laravel_Algerian_Education_System/info.json b/data/models/projects/Laravel_Algerian_Education_System/info.json index 3847758e7..d2f972f04 100644 --- a/data/models/projects/Laravel_Algerian_Education_System/info.json +++ b/data/models/projects/Laravel_Algerian_Education_System/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "elaborate-code", - "repository": "laravel-algerian-education-system" + "name": "laravel-algerian-education-system" } ] } diff --git a/data/models/projects/Laravel_Algerian_Provinces/info.json b/data/models/projects/Laravel_Algerian_Provinces/info.json index aa6fb2a83..69a6e45d0 100644 --- a/data/models/projects/Laravel_Algerian_Provinces/info.json +++ b/data/models/projects/Laravel_Algerian_Provinces/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "elaborate-code", - "repository": "laravel-algerian-provinces" + "name": "laravel-algerian-provinces" } ] } diff --git a/data/models/projects/Leblad/info.json b/data/models/projects/Leblad/info.json index b50654c0e..144f20026 100644 --- a/data/models/projects/Leblad/info.json +++ b/data/models/projects/Leblad/info.json @@ -4,22 +4,22 @@ { "provider": "github", "owner": "dzcode-io", - "repository": "leblad" + "name": "leblad" }, { "provider": "github", "owner": "abderrahmaneMustapha", - "repository": "leblad-py" + "name": "leblad-py" }, { "provider": "github", "owner": "omdxp", - "repository": "leblad" + "name": "leblad" }, { "provider": "github", "owner": "omdxp", - "repository": "leblad-rs" + "name": "leblad-rs" } ] } diff --git a/data/models/projects/List_to_Tree_aka_l2t/info.json b/data/models/projects/List_to_Tree_aka_l2t/info.json index 02d7b88a4..dc1148a69 100644 --- a/data/models/projects/List_to_Tree_aka_l2t/info.json +++ b/data/models/projects/List_to_Tree_aka_l2t/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "ZibanPirate", - "repository": "l2t" + "name": "l2t" } ] } diff --git a/data/models/projects/MERN_Auth_Roles_Boilerplate/info.json b/data/models/projects/MERN_Auth_Roles_Boilerplate/info.json index 10a0e3c5f..e134b6986 100644 --- a/data/models/projects/MERN_Auth_Roles_Boilerplate/info.json +++ b/data/models/projects/MERN_Auth_Roles_Boilerplate/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "adelpro", - "repository": "MERN-auth-roles-boilerplate" + "name": "MERN-auth-roles-boilerplate" } ] } diff --git a/data/models/projects/Madinati_Meteo/info.json b/data/models/projects/Madinati_Meteo/info.json index c67d4d910..2ad669e1a 100644 --- a/data/models/projects/Madinati_Meteo/info.json +++ b/data/models/projects/Madinati_Meteo/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "khalilpreview", - "repository": "madinateo" + "name": "madinateo" } ] } diff --git a/data/models/projects/Melyon_Theme/info.json b/data/models/projects/Melyon_Theme/info.json index 00362d52f..477537edd 100644 --- a/data/models/projects/Melyon_Theme/info.json +++ b/data/models/projects/Melyon_Theme/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "CA1R7", - "repository": "melyon" + "name": "melyon" } ] } diff --git a/data/models/projects/Mishkal/info.json b/data/models/projects/Mishkal/info.json index f0f1fcbaf..9bfa2b7a8 100644 --- a/data/models/projects/Mishkal/info.json +++ b/data/models/projects/Mishkal/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "linuxscout", - "repository": "mishkal" + "name": "mishkal" } ] } diff --git a/data/models/projects/Moadaly/info.json b/data/models/projects/Moadaly/info.json index 6ea846d36..17e07ac00 100644 --- a/data/models/projects/Moadaly/info.json +++ b/data/models/projects/Moadaly/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "madjsmail", - "repository": "moadaly" + "name": "moadaly" } ] } diff --git a/data/models/projects/Mylinks_Space/info.json b/data/models/projects/Mylinks_Space/info.json index 8570d1bbe..37214857d 100644 --- a/data/models/projects/Mylinks_Space/info.json +++ b/data/models/projects/Mylinks_Space/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "bacloud23", - "repository": "mylinks" + "name": "mylinks" } ] } diff --git a/data/models/projects/Open-listings/info.json b/data/models/projects/Open-listings/info.json index 553e7ca05..e2837dfb8 100644 --- a/data/models/projects/Open-listings/info.json +++ b/data/models/projects/Open-listings/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "bacloud23", - "repository": "open-listings" + "name": "open-listings" } ] } diff --git a/data/models/projects/Openadhan/info.json b/data/models/projects/Openadhan/info.json index 85ae272f0..20eeea55f 100644 --- a/data/models/projects/Openadhan/info.json +++ b/data/models/projects/Openadhan/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "adelpro", - "repository": "Openadhan" + "name": "Openadhan" } ] } diff --git a/data/models/projects/PHP_JSON_Tongue/info.json b/data/models/projects/PHP_JSON_Tongue/info.json index 44165625b..02cfdde22 100644 --- a/data/models/projects/PHP_JSON_Tongue/info.json +++ b/data/models/projects/PHP_JSON_Tongue/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "elaborate-code", - "repository": "php-json-tongue" + "name": "php-json-tongue" } ] } diff --git a/data/models/projects/Python_Complex/info.json b/data/models/projects/Python_Complex/info.json index e4eefda9b..0dc742fe4 100644 --- a/data/models/projects/Python_Complex/info.json +++ b/data/models/projects/Python_Complex/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "Omar-Belghaouti", - "repository": "PythonComplex" + "name": "PythonComplex" } ] } diff --git a/data/models/projects/QuranIPFS/info.json b/data/models/projects/QuranIPFS/info.json index ebb5b14ca..5337dc30d 100644 --- a/data/models/projects/QuranIPFS/info.json +++ b/data/models/projects/QuranIPFS/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "adelpro", - "repository": "Quranipfs" + "name": "Quranipfs" } ] } diff --git a/data/models/projects/React_Glassmorphism_Components/info.json b/data/models/projects/React_Glassmorphism_Components/info.json index 91e9699f9..fb2414981 100644 --- a/data/models/projects/React_Glassmorphism_Components/info.json +++ b/data/models/projects/React_Glassmorphism_Components/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "jusinamine", - "repository": "react-glassmorphism-components" + "name": "react-glassmorphism-components" } ] } diff --git a/data/models/projects/React_Help_Create/info.json b/data/models/projects/React_Help_Create/info.json index d53cb41ca..c5495bd94 100644 --- a/data/models/projects/React_Help_Create/info.json +++ b/data/models/projects/React_Help_Create/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "Omar-Belghaouti", - "repository": "react-help-create" + "name": "react-help-create" } ] } diff --git a/data/models/projects/React_Native_Currency_Converter/info.json b/data/models/projects/React_Native_Currency_Converter/info.json index e1c913cbf..01575027a 100644 --- a/data/models/projects/React_Native_Currency_Converter/info.json +++ b/data/models/projects/React_Native_Currency_Converter/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "ilies-space", - "repository": "currency_converter_rn" + "name": "currency_converter_rn" } ] } diff --git a/data/models/projects/React_Native_Help_Create/info.json b/data/models/projects/React_Native_Help_Create/info.json index 11ff0a899..22a1bfc10 100644 --- a/data/models/projects/React_Native_Help_Create/info.json +++ b/data/models/projects/React_Native_Help_Create/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "Omar-Belghaouti", - "repository": "react-native-help-create" + "name": "react-native-help-create" } ] } diff --git a/data/models/projects/React_Project_Builder/info.json b/data/models/projects/React_Project_Builder/info.json index 48a97ee61..6c5556059 100644 --- a/data/models/projects/React_Project_Builder/info.json +++ b/data/models/projects/React_Project_Builder/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "AmineVolk", - "repository": "react-project-builder" + "name": "react-project-builder" } ] } diff --git a/data/models/projects/STRM_Test/info.json b/data/models/projects/STRM_Test/info.json index 4eeac6853..f97a6002d 100644 --- a/data/models/projects/STRM_Test/info.json +++ b/data/models/projects/STRM_Test/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "linuxscout", - "repository": "strm-tests" + "name": "strm-tests" } ] } diff --git a/data/models/projects/System_Monitor/info.json b/data/models/projects/System_Monitor/info.json index d56bcb93f..69a0df761 100644 --- a/data/models/projects/System_Monitor/info.json +++ b/data/models/projects/System_Monitor/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "ZibanPirate", - "repository": "sysmon" + "name": "sysmon" } ] } diff --git a/data/models/projects/Violence_Detection/info.json b/data/models/projects/Violence_Detection/info.json index 245c52be0..366227088 100644 --- a/data/models/projects/Violence_Detection/info.json +++ b/data/models/projects/Violence_Detection/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "jusinamine", - "repository": "violence_detection" + "name": "violence_detection" } ] } diff --git a/data/models/projects/Voice_Translator_React_Native/info.json b/data/models/projects/Voice_Translator_React_Native/info.json index 27ed7aea2..2a0e63499 100644 --- a/data/models/projects/Voice_Translator_React_Native/info.json +++ b/data/models/projects/Voice_Translator_React_Native/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "ilies-space", - "repository": "voiceTranslator-reactNative" + "name": "voiceTranslator-reactNative" } ] } diff --git a/data/models/projects/Weather_Forecast/info.json b/data/models/projects/Weather_Forecast/info.json index 143397d4e..92b670bda 100644 --- a/data/models/projects/Weather_Forecast/info.json +++ b/data/models/projects/Weather_Forecast/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "jidmaa", - "repository": "weather-forecast" + "name": "weather-forecast" } ] } diff --git a/data/models/projects/dzcode.io_website/info.json b/data/models/projects/dzcode.io_website/info.json index 6b8bcfe23..d2b9bf8cf 100644 --- a/data/models/projects/dzcode.io_website/info.json +++ b/data/models/projects/dzcode.io_website/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "dzcode-io", - "repository": "dzcode.io" + "name": "dzcode.io" } ] } diff --git a/data/models/projects/who_want_to_millionaire/info.json b/data/models/projects/who_want_to_millionaire/info.json index da8f90a5f..7ef5ca13d 100644 --- a/data/models/projects/who_want_to_millionaire/info.json +++ b/data/models/projects/who_want_to_millionaire/info.json @@ -4,7 +4,7 @@ { "provider": "github", "owner": "brahaim360", - "repository": "who-want-to-be-millionere" + "name": "who-want-to-be-millionere" } ] } diff --git a/packages/models/src/project/index.spec.ts b/packages/models/src/project/index.spec.ts index 792eb94ed..3f73b65a8 100644 --- a/packages/models/src/project/index.spec.ts +++ b/packages/models/src/project/index.spec.ts @@ -12,12 +12,16 @@ runDTOTestCases( { provider: "github", owner: "dzcode-io", - repository: "leblad", + name: "leblad", + id: 0, + runId: "initial-run-id", }, { provider: "github", owner: "abderrahmaneMustapha", - repository: "leblad-py", + name: "leblad-py", + id: 0, + runId: "initial-run-id", }, ], slug: "Leblad", diff --git a/packages/models/src/repository/index.spec.ts b/packages/models/src/repository/index.spec.ts index fe1140862..24cb83cc2 100644 --- a/packages/models/src/repository/index.spec.ts +++ b/packages/models/src/repository/index.spec.ts @@ -7,7 +7,9 @@ runDTOTestCases( { provider: "github", owner: "dzcode-io", - repository: "leblad", + name: "leblad", + id: 0, + runId: "initial-run-id", }, { contributions: [], diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index 6e46f6f69..ce1713a10 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -1,5 +1,5 @@ import { Type } from "class-transformer"; -import { IsIn, IsString, ValidateNested } from "class-validator"; +import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator"; import { BaseEntity, Model } from "src/_base"; import { AccountEntity } from "src/account"; import { ContributionEntity } from "src/contribution"; @@ -8,6 +8,14 @@ const RepositoryProviders = ["github", "gitlab"] as const; type RepositoryProvider = (typeof RepositoryProviders)[number]; export class RepositoryEntity extends BaseEntity { + // @TODO-ZM: move this to BaseEntity + @IsNumber() + id!: number; + + // @TODO-ZM: move this to BaseEntity + @IsString() + runId!: string; + @IsIn(RepositoryProviders) provider!: RepositoryProvider; @@ -15,7 +23,7 @@ export class RepositoryEntity extends BaseEntity { owner!: string; @IsString() - repository!: string; + name!: string; // TODO-ZM: add programming languages // @ValidateNested({ each: true }) From 3a9e6240d23999b00f8879fc88cef9c17eae50f9 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 4 Sep 2024 16:53:42 +0200 Subject: [PATCH 09/43] return id when upserting --- api/src/digest/cron.ts | 36 +++++++------------------------- api/src/project/repository.ts | 12 +++++++---- api/src/repository/repository.ts | 3 ++- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index e8a24e5d8..894356a61 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -1,6 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ProjectEntity } from "@dzcode.io/models/dist/project"; -import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { captureException, cron } from "@sentry/node"; import { CronJob } from "cron"; import { DataService } from "src/data/service"; @@ -62,53 +59,36 @@ export class DigestCron { const runId = Math.random().toString(36).slice(2); this.logger.info({ message: `Digest cron started, runId: ${runId}` }); - const projects: Model[] = []; const projectsFromDataFolder = await this.dataService.listProjects(); // @TODO-ZM: add relations for (const project of projectsFromDataFolder) { + const [{ id: projectId }] = await this.projectsRepository.upsert({ ...project, runId }); + try { - const repositories: Model[] = []; const repositoriesFromDataFolder = project.repositories; for (const repository of repositoriesFromDataFolder) { try { + // #@TODO-ZM: call repo-exist api instead of fetching languages // eslint-disable-next-line @typescript-eslint/no-unused-vars const languages: string[] = await this.githubService.listRepositoryLanguages({ owner: repository.owner, repository: repository.name, }); - - // do more stuff here - repositories.push({ - ...repository, - }); + const [{ id: repositoryId }] = await this.repositoriesRepository.upsert(repository); } catch (error) { // @TODO-ZM: capture error - this.logger.error({ - message: `Failed to fetch languages for repository: ${repository.owner}/${repository.name}`, - meta: { error }, - }); + console.error(error); } } - - if (repositories.length > 0) { - for (const repository of repositories) { - await this.repositoriesRepository.upsert(repository); - } - projects.push({ ...project, runId }); - } } catch (error) { // @TODO-ZM: capture error - this.logger.error({ - message: `Failed to fetch repositories for project: ${project.name}`, - meta: { error }, - }); + console.error(error); } } - for (const project of projects) { - await this.projectsRepository.upsert(project); - } await this.projectsRepository.deleteAllButWithRunId(runId); + await this.repositoriesRepository.deleteAllButWithRunId(runId); + this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); } } diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index cdc096aac..741d64b6c 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -15,10 +15,14 @@ export class ProjectRepository { } public async upsert(project: Model) { - return await this.sqliteService.db.insert(projectsTable).values(project).onConflictDoUpdate({ - target: projectsTable.slug, - set: project, - }); + return await this.sqliteService.db + .insert(projectsTable) + .values(project) + .onConflictDoUpdate({ + target: projectsTable.slug, + set: project, + }) + .returning({ id: projectsTable.id }); } public async deleteAllButWithRunId(runId: string) { diff --git a/api/src/repository/repository.ts b/api/src/repository/repository.ts index 44c4ae1c0..e87ff2860 100644 --- a/api/src/repository/repository.ts +++ b/api/src/repository/repository.ts @@ -17,7 +17,8 @@ export class RepositoryRepository { .onConflictDoUpdate({ target: [repositoriesTable.provider, repositoriesTable.owner, repositoriesTable.name], set: repository, - }); + }) + .returning({ id: repositoriesTable.id }); } public async deleteAllButWithRunId(runId: string) { From 9a2462dc9489b5f6802ca676a3f0f382da79f613 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 4 Sep 2024 17:59:02 +0200 Subject: [PATCH 10/43] validate github response --- .../validator/is-map-of-string-number.ts | 22 +++++++++++++++++++ .../_utils/validator/validate-plain-object.ts | 21 ++++++++++++++++++ api/src/contribution/repository.ts | 2 +- api/src/github/dto.ts | 7 ++++++ api/src/github/service.ts | 6 +++-- 5 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 api/src/_utils/validator/is-map-of-string-number.ts create mode 100644 api/src/_utils/validator/validate-plain-object.ts create mode 100644 api/src/github/dto.ts diff --git a/api/src/_utils/validator/is-map-of-string-number.ts b/api/src/_utils/validator/is-map-of-string-number.ts new file mode 100644 index 000000000..62225b06c --- /dev/null +++ b/api/src/_utils/validator/is-map-of-string-number.ts @@ -0,0 +1,22 @@ +import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; + +@ValidatorConstraint({ name: "isMapOfStringNumber", async: false }) +export class IsMapOfStringNumber implements ValidatorConstraintInterface { + validate(map: Record) { + if (!map) return false; + + for (const key in map) { + if (Object.prototype.hasOwnProperty.call(map, key)) { + const value = map[key]; + if (typeof key !== "string") return false; + if (typeof value !== "number") return false; + } + } + + return true; + } + + defaultMessage() { + return "Map must be of string keys and number values"; + } +} diff --git a/api/src/_utils/validator/validate-plain-object.ts b/api/src/_utils/validator/validate-plain-object.ts new file mode 100644 index 000000000..cc8e34335 --- /dev/null +++ b/api/src/_utils/validator/validate-plain-object.ts @@ -0,0 +1,21 @@ +import { ClassConstructor, plainToClass, plainToInstance } from "class-transformer"; +import { validateSync } from "class-validator"; + +export function validatePlainObject>( + cls: T, + obj: object, +): InstanceType { + const output = plainToClass(cls, obj); + + const errors = validateSync(output as T); + + if (errors.length > 0) + throw new Error( + `Errors in object in the following keys:${errors.reduce( + (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), + "", + )}`, + ); + + return plainToInstance(cls, obj) as InstanceType; +} diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index 9933d6d70..bb0dff878 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -52,7 +52,7 @@ export class ContributionRepository { }) => ({ id: `${number}`, labels: gLabels.map(({ name }) => name), - languages, + languages: Object.keys(languages), project: { slug, name, diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts new file mode 100644 index 000000000..5d5152d90 --- /dev/null +++ b/api/src/github/dto.ts @@ -0,0 +1,7 @@ +import { Validate } from "class-validator"; +import { IsMapOfStringNumber } from "src/_utils/validator/is-map-of-string-number"; + +export class GitHubListRepositoryLanguagesResponse { + @Validate(IsMapOfStringNumber) + languages!: Map; +} diff --git a/api/src/github/service.ts b/api/src/github/service.ts index 4982c424f..e64fdd520 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -1,9 +1,11 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { AccountEntity } from "@dzcode.io/models/dist/account"; +import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; import { ConfigService } from "src/config/service"; import { FetchService } from "src/fetch/service"; import { Service } from "typedi"; +import { GitHubListRepositoryLanguagesResponse } from "./dto"; import { GeneralGithubQuery, GetUserInput, @@ -72,12 +74,12 @@ export class GithubService { public listRepositoryLanguages = async ({ owner, repository, - }: GitHubListRepositoryLanguagesInput): Promise => { + }: GitHubListRepositoryLanguagesInput): Promise => { const languages = await this.fetchService.get>( `${this.apiURL}/repos/${owner}/${repository}/languages`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, ); - return Object.keys(languages); + return validatePlainObject(GitHubListRepositoryLanguagesResponse, { languages }); }; public listRepositoryContributors = async ({ From 4bb4c522a364a47297e87812114927e3282cd2bc Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 4 Sep 2024 18:00:59 +0200 Subject: [PATCH 11/43] added repo proj relation --- api/db/migrations/0000_new_lake.sql | 21 +++ .../0000_vengeful_the_liberteens.sql | 9 -- api/db/migrations/0001_brave_sentry.sql | 10 -- api/db/migrations/meta/0000_snapshot.json | 78 ++++++++++- api/db/migrations/meta/0001_snapshot.json | 128 ------------------ api/db/migrations/meta/_journal.json | 11 +- api/src/digest/cron.ts | 8 +- api/src/project/repository.ts | 6 +- api/src/project/table.ts | 2 + api/src/repository/repository.ts | 6 +- api/src/repository/table.ts | 6 + 11 files changed, 118 insertions(+), 167 deletions(-) create mode 100644 api/db/migrations/0000_new_lake.sql delete mode 100644 api/db/migrations/0000_vengeful_the_liberteens.sql delete mode 100644 api/db/migrations/0001_brave_sentry.sql delete mode 100644 api/db/migrations/meta/0001_snapshot.json diff --git a/api/db/migrations/0000_new_lake.sql b/api/db/migrations/0000_new_lake.sql new file mode 100644 index 000000000..caf956a56 --- /dev/null +++ b/api/db/migrations/0000_new_lake.sql @@ -0,0 +1,21 @@ +CREATE TABLE `projects` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL +); +--> statement-breakpoint +CREATE TABLE `repositories` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `provider` text NOT NULL, + `owner` text NOT NULL, + `name` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL, + `project_id` integer NOT NULL, + FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `projects_slug_unique` ON `projects` (`slug`);--> statement-breakpoint +CREATE UNIQUE INDEX `repositories_provider_owner_name_unique` ON `repositories` (`provider`,`owner`,`name`); \ No newline at end of file diff --git a/api/db/migrations/0000_vengeful_the_liberteens.sql b/api/db/migrations/0000_vengeful_the_liberteens.sql deleted file mode 100644 index 414fb0717..000000000 --- a/api/db/migrations/0000_vengeful_the_liberteens.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE `projects` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `name` text NOT NULL, - `slug` text NOT NULL, - `run_id` text DEFAULT 'initial-run-id' NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX `projects_slug_unique` ON `projects` (`slug`); \ No newline at end of file diff --git a/api/db/migrations/0001_brave_sentry.sql b/api/db/migrations/0001_brave_sentry.sql deleted file mode 100644 index 9772a054a..000000000 --- a/api/db/migrations/0001_brave_sentry.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE `repositories` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `provider` text NOT NULL, - `owner` text NOT NULL, - `name` text NOT NULL, - `run_id` text DEFAULT 'initial-run-id' NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX `repositories_provider_owner_name_unique` ON `repositories` (`provider`,`owner`,`name`); \ No newline at end of file diff --git a/api/db/migrations/meta/0000_snapshot.json b/api/db/migrations/meta/0000_snapshot.json index 9faf74bdd..743b04b73 100644 --- a/api/db/migrations/meta/0000_snapshot.json +++ b/api/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "a7c87785-bca5-4d27-981f-97be7c2428c6", + "id": "ab1b512a-8ca8-44e1-b497-445465117250", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "projects": { @@ -55,6 +55,82 @@ "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "repositories_provider_owner_name_unique": { + "name": "repositories_provider_owner_name_unique", + "columns": ["provider", "owner", "name"], + "isUnique": true + } + }, + "foreignKeys": { + "repositories_project_id_projects_id_fk": { + "name": "repositories_project_id_projects_id_fk", + "tableFrom": "repositories", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} } }, "enums": {}, diff --git a/api/db/migrations/meta/0001_snapshot.json b/api/db/migrations/meta/0001_snapshot.json deleted file mode 100644 index 9472308b0..000000000 --- a/api/db/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "2bcb5a42-64e3-41ba-b12c-58fecbeccb22", - "prevId": "a7c87785-bca5-4d27-981f-97be7c2428c6", - "tables": { - "projects": { - "name": "projects", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - } - }, - "indexes": { - "projects_slug_unique": { - "name": "projects_slug_unique", - "columns": ["slug"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "repositories": { - "name": "repositories", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "owner": { - "name": "owner", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - } - }, - "indexes": { - "repositories_provider_owner_name_unique": { - "name": "repositories_provider_owner_name_unique", - "columns": ["provider", "owner", "name"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index b8e706c85..a87ceb45e 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -5,15 +5,8 @@ { "idx": 0, "version": "6", - "when": 1725398893540, - "tag": "0000_vengeful_the_liberteens", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1725459545225, - "tag": "0001_brave_sentry", + "when": 1725462108723, + "tag": "0000_new_lake", "breakpoints": true } ] diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 894356a61..b58c54411 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -70,11 +70,15 @@ export class DigestCron { try { // #@TODO-ZM: call repo-exist api instead of fetching languages // eslint-disable-next-line @typescript-eslint/no-unused-vars - const languages: string[] = await this.githubService.listRepositoryLanguages({ + const { languages } = await this.githubService.listRepositoryLanguages({ owner: repository.owner, repository: repository.name, }); - const [{ id: repositoryId }] = await this.repositoriesRepository.upsert(repository); + const [{ id: repositoryId }] = await this.repositoriesRepository.upsert({ + ...repository, + runId, + projectId, + }); } catch (error) { // @TODO-ZM: capture error console.error(error); diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index 741d64b6c..668e7e39f 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,10 +1,8 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { ne } from "drizzle-orm"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; -import { projectsTable } from "./table"; +import { ProjectRow, projectsTable } from "./table"; @Service() export class ProjectRepository { @@ -14,7 +12,7 @@ export class ProjectRepository { return this.sqliteService.db.select().from(projectsTable); } - public async upsert(project: Model) { + public async upsert(project: ProjectRow) { return await this.sqliteService.db .insert(projectsTable) .values(project) diff --git a/api/src/project/table.ts b/api/src/project/table.ts index faf41b369..14176a04a 100644 --- a/api/src/project/table.ts +++ b/api/src/project/table.ts @@ -14,3 +14,5 @@ export const projectsTable = sqliteTable("projects", { }); projectsTable.$inferSelect satisfies Model; + +export type ProjectRow = typeof projectsTable.$inferInsert; diff --git a/api/src/repository/repository.ts b/api/src/repository/repository.ts index e87ff2860..b98625859 100644 --- a/api/src/repository/repository.ts +++ b/api/src/repository/repository.ts @@ -1,16 +1,14 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { ne } from "drizzle-orm"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; -import { repositoriesTable } from "./table"; +import { repositoriesTable, RepositoryRow } from "./table"; @Service() export class RepositoryRepository { constructor(private readonly sqliteService: SQLiteService) {} - public async upsert(repository: Model) { + public async upsert(repository: RepositoryRow) { return await this.sqliteService.db .insert(repositoriesTable) .values(repository) diff --git a/api/src/repository/table.ts b/api/src/repository/table.ts index 4ab24aef6..e2ec1ec4a 100644 --- a/api/src/repository/table.ts +++ b/api/src/repository/table.ts @@ -2,6 +2,7 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { sql } from "drizzle-orm"; import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core"; +import { projectsTable } from "src/project/table"; export const repositoriesTable = sqliteTable( "repositories", @@ -14,6 +15,9 @@ export const repositoriesTable = sqliteTable( owner: text("owner").notNull(), name: text("name").notNull(), runId: text("run_id").notNull().default("initial-run-id"), + projectId: integer("project_id") + .notNull() + .references(() => projectsTable.id), }, (table) => { return { @@ -23,3 +27,5 @@ export const repositoriesTable = sqliteTable( ); repositoriesTable.$inferSelect satisfies Model; + +export type RepositoryRow = typeof repositoriesTable.$inferInsert; From bd0cffafda5f16c121b597d29ada69b4e92791fc Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 4 Sep 2024 20:57:40 +0200 Subject: [PATCH 12/43] better modeled project repo relation --- api/package.json | 2 ++ .../_utils/validator/validate-plain-object.ts | 23 ++++++++----- api/src/data/service.ts | 2 +- api/src/data/types.ts | 12 +++++-- api/src/project/controller.ts | 2 +- api/src/project/repository.ts | 34 +++++++++++++++++-- api/src/project/types.ts | 9 ++--- api/src/team/repository.ts | 4 +-- packages/models/src/project/index.spec.ts | 30 ---------------- packages/models/src/project/index.ts | 21 +++++++----- packages/models/src/repository/index.ts | 27 ++++++++------- web/src/pages/projects/index.tsx | 16 +++------ web/src/utils/repository.ts | 19 ++++++----- yarn.lock | 5 +++ 14 files changed, 113 insertions(+), 93 deletions(-) delete mode 100644 packages/models/src/project/index.spec.ts diff --git a/api/package.json b/api/package.json index 537884909..15babf0e8 100644 --- a/api/package.json +++ b/api/package.json @@ -25,6 +25,7 @@ "express-robots-txt": "^1.0.0", "fs-extra": "^10.0.0", "helmet": "^4.4.1", + "lodash": "^4.17.21", "make-fetch-happen": "^9.0.2", "morgan": "^1.10.0", "reflect-metadata": "^0.1.13", @@ -44,6 +45,7 @@ "@types/faker": "^5.5.6", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.4", + "@types/lodash": "^4.17.7", "@types/make-fetch-happen": "^9.0.1", "@types/morgan": "^1.9.2", "@types/swagger-ui-express": "^4.1.2", diff --git a/api/src/_utils/validator/validate-plain-object.ts b/api/src/_utils/validator/validate-plain-object.ts index cc8e34335..f991daa30 100644 --- a/api/src/_utils/validator/validate-plain-object.ts +++ b/api/src/_utils/validator/validate-plain-object.ts @@ -1,21 +1,28 @@ import { ClassConstructor, plainToClass, plainToInstance } from "class-transformer"; import { validateSync } from "class-validator"; +import camelCase from "lodash/camelCase"; +import mapKeys from "lodash/mapKeys"; export function validatePlainObject>( cls: T, - obj: object, + obj: Record, + exposeValues = false, ): InstanceType { - const output = plainToClass(cls, obj); + const camelCasedObj = mapKeys(obj, (value, key) => camelCase(key)); - const errors = validateSync(output as T); + const output = plainToClass(cls, camelCasedObj); + + const errors = validateSync(output as T, { whitelist: true, forbidNonWhitelisted: true }); if (errors.length > 0) throw new Error( - `Errors in object in the following keys:${errors.reduce( - (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), - "", - )}`, + exposeValues + ? JSON.stringify(errors, null, 2) + : `Errors in object in the following keys:${errors.reduce( + (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), + "", + )}`, ); - return plainToInstance(cls, obj) as InstanceType; + return plainToInstance(cls, camelCasedObj) as InstanceType; } diff --git a/api/src/data/service.ts b/api/src/data/service.ts index 82a3da263..7b58806d5 100644 --- a/api/src/data/service.ts +++ b/api/src/data/service.ts @@ -7,7 +7,7 @@ import { DataArticleEntity, DataDocumentationEntity, DataProjectEntity } from ". @Service() export class DataService { - public listProjects = async (): Promise[]> => { + public listProjects = async (): Promise => { const projects = getCollection(this.dataModelsPath, "projects", "list.json"); if (projects === 404) throw new Error("Projects list not found"); diff --git a/api/src/data/types.ts b/api/src/data/types.ts index c47b3b993..a910859cc 100644 --- a/api/src/data/types.ts +++ b/api/src/data/types.ts @@ -1,9 +1,17 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { ArticleInfoEntity } from "@dzcode.io/models/dist/article"; import { DocumentationInfoEntity } from "@dzcode.io/models/dist/documentation"; -import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -export type DataProjectEntity = Model; +export type DataProjectEntity = { + name: string; + slug: string; + repositories: Array<{ + provider: RepositoryEntity["provider"]; + owner: string; + name: string; + }>; +}; export type DataArticleEntity = Model & { authors: string[]; diff --git a/api/src/project/controller.ts b/api/src/project/controller.ts index 629926d67..16daf1a5c 100644 --- a/api/src/project/controller.ts +++ b/api/src/project/controller.ts @@ -16,7 +16,7 @@ export class ProjectController { }) @ResponseSchema(GetProjectsResponseDto) public async getProjects(): Promise { - const projects = await this.projectRepository.find(); + const projects = await this.projectRepository.findForList(); return { projects, diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index 668e7e39f..ffb6da8ed 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,4 +1,7 @@ -import { ne } from "drizzle-orm"; +import { ProjectEntityForList } from "@dzcode.io/models/dist/project"; +import { ne, sql } from "drizzle-orm"; +import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; +import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -8,8 +11,33 @@ import { ProjectRow, projectsTable } from "./table"; export class ProjectRepository { constructor(private readonly sqliteService: SQLiteService) {} - public async find() { - return this.sqliteService.db.select().from(projectsTable); + public async findForList() { + const statement = sql` + SELECT + p.id as id, + p.name as name, + p.slug as slug, + json_group_array( + json_object('id', r.id, 'owner', r.owner, 'name', r.name) + ) AS repositories + FROM + ${projectsTable} p + JOIN + ${repositoriesTable} r ON p.id = r.project_id + GROUP BY + p.id; + `; + const raw = this.sqliteService.db.all(statement) as Array< + // the SQL query above returns a stringified JSON for the `repositories` column + Omit & { repositories: string } + >; + const projectsForList: ProjectEntityForList[] = raw.map((row) => { + const notYetValid = { ...row, repositories: JSON.parse(row.repositories) }; + const validated = validatePlainObject(ProjectEntityForList, notYetValid, true); + return validated; + }); + + return projectsForList; } public async upsert(project: ProjectRow) { diff --git a/api/src/project/types.ts b/api/src/project/types.ts index 70d57738e..426fb6a38 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -1,11 +1,12 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { ProjectEntityForList } from "@dzcode.io/models/dist/project"; import { Type } from "class-transformer"; import { ValidateNested } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; +// @TODO-ZM: remove Model<> from existence + export class GetProjectsResponseDto extends GeneralResponseDto { @ValidateNested({ each: true }) - @Type(() => ProjectEntity) - projects!: Array>; + @Type(() => ProjectEntityForList) + projects!: Array; } diff --git a/api/src/team/repository.ts b/api/src/team/repository.ts index 2f50a2c1c..d6aeafde8 100644 --- a/api/src/team/repository.ts +++ b/api/src/team/repository.ts @@ -1,7 +1,7 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { DataService } from "src/data/service"; +import { DataProjectEntity } from "src/data/types"; import { GithubService } from "src/github/service"; import { GithubRepositoryContributor } from "src/github/types"; import { LoggerService } from "src/logger/service"; @@ -19,7 +19,7 @@ export class TeamRepository { const projects = await this.dataService.listProjects(); // flatten repositories into one array - const repositories = projects.reduce( + const repositories = projects.reduce( (repositories, project) => [...repositories, ...project.repositories], [], ); diff --git a/packages/models/src/project/index.spec.ts b/packages/models/src/project/index.spec.ts deleted file mode 100644 index 3f73b65a8..000000000 --- a/packages/models/src/project/index.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { ProjectEntity } from "."; - -runDTOTestCases( - ProjectEntity, - { - id: 0, - runId: "initial-run-id", - name: "Leblad", - repositories: [ - { - provider: "github", - owner: "dzcode-io", - name: "leblad", - id: 0, - runId: "initial-run-id", - }, - { - provider: "github", - owner: "abderrahmaneMustapha", - name: "leblad-py", - id: 0, - runId: "initial-run-id", - }, - ], - slug: "Leblad", - }, - {}, -); diff --git a/packages/models/src/project/index.ts b/packages/models/src/project/index.ts index 811b41ff1..fb2d2c401 100644 --- a/packages/models/src/project/index.ts +++ b/packages/models/src/project/index.ts @@ -1,24 +1,27 @@ import { Type } from "class-transformer"; import { IsNumber, IsString, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { RepositoryEntity } from "src/repository"; +import { BaseEntity } from "src/_base"; +import { RepositoryEntityCompact } from "src/repository"; -export class ProjectEntity extends BaseEntity { +export class ProjectEntityCompact extends BaseEntity { // @TODO-ZM: move this to BaseEntity @IsNumber() id!: number; - // @TODO-ZM: move this to BaseEntity - @IsString() - runId!: string; - @IsString() slug!: string; @IsString() name!: string; +} + +export class ProjectEntity extends ProjectEntityCompact { + @IsString() + runId!: string; +} +export class ProjectEntityForList extends ProjectEntityCompact { @ValidateNested({ each: true }) - @Type(() => RepositoryEntity) - declare repositories: Model[]; + @Type(() => RepositoryEntityCompact) + repositories!: RepositoryEntityCompact[]; } diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index ce1713a10..be27e6895 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -1,40 +1,43 @@ import { Type } from "class-transformer"; import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; +import { BaseEntity } from "src/_base"; import { AccountEntity } from "src/account"; import { ContributionEntity } from "src/contribution"; const RepositoryProviders = ["github", "gitlab"] as const; type RepositoryProvider = (typeof RepositoryProviders)[number]; -export class RepositoryEntity extends BaseEntity { +export class RepositoryEntityCompact extends BaseEntity { // @TODO-ZM: move this to BaseEntity @IsNumber() id!: number; - // @TODO-ZM: move this to BaseEntity - @IsString() - runId!: string; - - @IsIn(RepositoryProviders) - provider!: RepositoryProvider; - @IsString() owner!: string; @IsString() name!: string; +} + +export class RepositoryEntity extends RepositoryEntityCompact { + @IsString() + runId!: string; + + @IsIn(RepositoryProviders) + provider!: RepositoryProvider; +} +export class RepositoryEntityForList extends RepositoryEntity { // TODO-ZM: add programming languages // @ValidateNested({ each: true }) // @Type(() => ProgrammingLanguageEntity) - // programmingLanguages?: Model[]; + // programmingLanguages!: ProgrammingLanguageEntityCompact[]; @ValidateNested({ each: true }) @Type(() => AccountEntity) - contributors?: Model[]; + contributors!: AccountEntity[]; @ValidateNested({ each: true }) @Type(() => ContributionEntity) - contributions?: Model[]; + contributions!: ContributionEntity[]; } diff --git a/web/src/pages/projects/index.tsx b/web/src/pages/projects/index.tsx index 60e69263c..2ceb198bd 100644 --- a/web/src/pages/projects/index.tsx +++ b/web/src/pages/projects/index.tsx @@ -1,12 +1,11 @@ import { useEffect } from "react"; import { Helmet } from "react-helmet-async"; -import { Link } from "src/components/link"; import { Loading } from "src/components/loading"; import { Locale, useLocale } from "src/components/locale"; import { TryAgain } from "src/components/try-again"; import { fetchProjectsListAction } from "src/redux/actions/projects"; import { useAppDispatch, useAppSelector } from "src/redux/store"; -import { getRepositoryName, getRepositoryURL } from "src/utils/repository"; +import { getRepositoryName } from "src/utils/repository"; // ts-prune-ignore-next export default function Page(): JSX.Element { @@ -45,16 +44,9 @@ export default function Page(): JSX.Element {

{project.name}

-
    - {/* @TODO-ZM: put this back */} - {/* {project.repositories.map((repository, repositoryIndex) => ( -
  • - - {getRepositoryName(repository)} - -
  • - ))} */} -
+ {project.repositories.map((repository, repositoryIndex) => ( + {getRepositoryName(repository)} + ))}
))} diff --git a/web/src/utils/repository.ts b/web/src/utils/repository.ts index 6a2a986df..264e4c247 100644 --- a/web/src/utils/repository.ts +++ b/web/src/utils/repository.ts @@ -1,18 +1,19 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -export function getRepositoryName( - repository: Pick, "owner" | "repository">, -): string { - return `${repository.owner}/${repository.repository}`; +export function getRepositoryName(repository: Pick): string { + return `${repository.owner}/${repository.name}`; } -export const getRepositoryURL = (repository: Model): string => { - switch (repository.provider) { +export const getRepositoryURL = ({ + provider, + owner, + name, +}: Pick): string => { + switch (provider) { case "github": - return `https://www.github.com/${repository.owner}/${repository.repository}`; + return `https://www.github.com/${owner}/${name}`; case "gitlab": - return `https://www.gitlab.com/${repository.owner}/${repository.repository}`; + return `https://www.gitlab.com/${owner}/${name}`; default: return ""; } diff --git a/yarn.lock b/yarn.lock index 308f1cb5b..ba7270b00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3131,6 +3131,11 @@ dependencies: "@types/react" "*" +"@types/lodash@^4.17.7": + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.7.tgz#2f776bcb53adc9e13b2c0dfd493dfcbd7de43612" + integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA== + "@types/long@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" From add5926e15684006d359c917359bb3e6ed0e73e7 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 17:42:01 +0200 Subject: [PATCH 13/43] fixed linting error --- api/src/team/repository.ts | 12 ++++++------ packages/models/src/project-reference/index.spec.ts | 4 ++-- .../models/src/repository-reference/index.spec.ts | 2 +- packages/models/src/repository-reference/index.ts | 2 +- packages/models/src/repository-reference/utils.ts | 12 ------------ web/src/utils/repository.test.ts | 4 ++-- 6 files changed, 12 insertions(+), 24 deletions(-) delete mode 100644 packages/models/src/repository-reference/utils.ts diff --git a/api/src/team/repository.ts b/api/src/team/repository.ts index d6aeafde8..c72388575 100644 --- a/api/src/team/repository.ts +++ b/api/src/team/repository.ts @@ -35,12 +35,12 @@ export class TeamRepository { // get contributors from all the repos we have await Promise.all( - repositories.map(async ({ provider, owner, name: repository }) => { + repositories.map(async ({ provider, owner, name }) => { if (provider === "github") { try { const contributors = await this.githubService.listRepositoryContributors({ owner, - repository, + repository: name, }); contributors.forEach((contributor) => { const uuid = this.githubService.githubUserToAccountEntity({ @@ -53,7 +53,7 @@ export class TeamRepository { contributorsUsernameRankedRecord[uuid] = { login, contributions, - repositories: [{ provider, owner, repository }], + repositories: [{ provider, owner, name }], }; } else { // contributor already exists in the list, and is a contributor to another repository @@ -63,14 +63,14 @@ export class TeamRepository { contributorsUsernameRankedRecord[uuid].repositories.push({ provider, owner, - repository, + name, }); } }); } catch (error) { this.loggerService.warn({ - message: `Failed to fetch contributors for ${owner}/${repository}: ${error}`, - meta: { owner, repository }, + message: `Failed to fetch contributors for ${owner}/${name}: ${error}`, + meta: { owner, name }, }); } } else throw new Error(`Provider ${provider} is not supported yet`); diff --git a/packages/models/src/project-reference/index.spec.ts b/packages/models/src/project-reference/index.spec.ts index d22d5e496..585118374 100644 --- a/packages/models/src/project-reference/index.spec.ts +++ b/packages/models/src/project-reference/index.spec.ts @@ -10,12 +10,12 @@ runDTOTestCases( { provider: "github", owner: "dzcode-io", - repository: "leblad", + name: "leblad", }, { provider: "github", owner: "abderrahmaneMustapha", - repository: "leblad-py", + name: "leblad-py", }, ], slug: "Leblad", diff --git a/packages/models/src/repository-reference/index.spec.ts b/packages/models/src/repository-reference/index.spec.ts index 3b690189c..68215db67 100644 --- a/packages/models/src/repository-reference/index.spec.ts +++ b/packages/models/src/repository-reference/index.spec.ts @@ -7,7 +7,7 @@ runDTOTestCases( { provider: "github", owner: "dzcode-io", - repository: "leblad", + name: "leblad", }, { contributions: [], diff --git a/packages/models/src/repository-reference/index.ts b/packages/models/src/repository-reference/index.ts index bc614e4d8..532a3a1a7 100644 --- a/packages/models/src/repository-reference/index.ts +++ b/packages/models/src/repository-reference/index.ts @@ -10,5 +10,5 @@ export class RepositoryReferenceEntity extends BaseEntity { owner!: string; @IsString() - repository!: string; + name!: string; } diff --git a/packages/models/src/repository-reference/utils.ts b/packages/models/src/repository-reference/utils.ts deleted file mode 100644 index c3c2f7a8e..000000000 --- a/packages/models/src/repository-reference/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Model } from "src/_base"; - -import { RepositoryReferenceEntity } from "."; - -export const getRepositoryURL = (repository: Model): string => { - switch (repository.provider) { - case "github": - return `https://www.github.com/${repository.owner}/${repository.repository}`; - case "gitlab": - return `https://www.gitlab.com/${repository.owner}/${repository.repository}`; - } -}; diff --git a/web/src/utils/repository.test.ts b/web/src/utils/repository.test.ts index da85f33d9..cf61d31e2 100644 --- a/web/src/utils/repository.test.ts +++ b/web/src/utils/repository.test.ts @@ -4,7 +4,7 @@ describe("getRepositoryName", () => { it("should return the repository name", () => { const repository = { owner: "dzcode.io", - repository: "dzcode.io", + name: "dzcode.io", } as const; expect(getRepositoryName(repository)).toBe("dzcode.io/dzcode.io"); }); @@ -14,7 +14,7 @@ describe("getRepositoryURL", () => { it("should return the repository URL", () => { const repository = { owner: "dzcode.io", - repository: "dzcode.io", + name: "dzcode.io", provider: "github", } as const; expect(getRepositoryURL(repository)).toBe("https://www.github.com/dzcode.io/dzcode.io"); From 18d07fa5282926654816d508c613657ee2205c86 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 18:36:23 +0200 Subject: [PATCH 14/43] call get repo for repo health check instead of get langs --- .../_utils/validator/validate-plain-object.ts | 6 +++- api/src/digest/cron.ts | 17 ++++++---- api/src/fetch/service.ts | 28 +++++++++++++-- api/src/github/dto.ts | 15 +++++++- api/src/github/service.ts | 34 +++++++++++++------ api/src/github/types.ts | 5 +++ 6 files changed, 84 insertions(+), 21 deletions(-) diff --git a/api/src/_utils/validator/validate-plain-object.ts b/api/src/_utils/validator/validate-plain-object.ts index f991daa30..cdc31e2e8 100644 --- a/api/src/_utils/validator/validate-plain-object.ts +++ b/api/src/_utils/validator/validate-plain-object.ts @@ -7,12 +7,16 @@ export function validatePlainObject>( cls: T, obj: Record, exposeValues = false, + allowExtraneousKeys = false, ): InstanceType { const camelCasedObj = mapKeys(obj, (value, key) => camelCase(key)); const output = plainToClass(cls, camelCasedObj); - const errors = validateSync(output as T, { whitelist: true, forbidNonWhitelisted: true }); + const errors = validateSync(output as T, { + whitelist: !allowExtraneousKeys, + forbidNonWhitelisted: !allowExtraneousKeys, + }); if (errors.length > 0) throw new Error( diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index b58c54411..344122540 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -33,6 +33,7 @@ export class DigestCron { await this.run(); } catch (error) { this.isRunning = false; + console.error(error); captureException(error, { tags: { type: "CRON" } }); logger.error({ message: `Digest cron failed: ${error}`, @@ -60,7 +61,7 @@ export class DigestCron { this.logger.info({ message: `Digest cron started, runId: ${runId}` }); const projectsFromDataFolder = await this.dataService.listProjects(); - // @TODO-ZM: add relations + for (const project of projectsFromDataFolder) { const [{ id: projectId }] = await this.projectsRepository.upsert({ ...project, runId }); @@ -68,14 +69,16 @@ export class DigestCron { const repositoriesFromDataFolder = project.repositories; for (const repository of repositoriesFromDataFolder) { try { - // #@TODO-ZM: call repo-exist api instead of fetching languages - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { languages } = await this.githubService.listRepositoryLanguages({ + const repoInfo = await this.githubService.getRepository({ owner: repository.owner, - repository: repository.name, + repo: repository.name, }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [{ id: repositoryId }] = await this.repositoriesRepository.upsert({ - ...repository, + provider: "github", + name: repoInfo.name, + owner: repoInfo.owner.login, runId, projectId, }); @@ -90,8 +93,8 @@ export class DigestCron { } } - await this.projectsRepository.deleteAllButWithRunId(runId); await this.repositoriesRepository.deleteAllButWithRunId(runId); + await this.projectsRepository.deleteAllButWithRunId(runId); this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); } diff --git a/api/src/fetch/service.ts b/api/src/fetch/service.ts index 6d1a2fc7b..5c8871ae6 100644 --- a/api/src/fetch/service.ts +++ b/api/src/fetch/service.ts @@ -1,13 +1,19 @@ import { lockFactory } from "@dzcode.io/utils/dist/concurrency"; +import { ClassConstructor } from "class-transformer"; import { defaults } from "make-fetch-happen"; +import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; import { ConfigService } from "src/config/service"; +import { LoggerService } from "src/logger/service"; import { Service } from "typedi"; import { FetchConfig } from "./types"; @Service() export class FetchService { - constructor(private readonly configService: ConfigService) { + constructor( + private readonly configService: ConfigService, + private readonly logger: LoggerService, + ) { const { FETCH_CACHE_PATH } = this.configService.env(); this.makeFetchHappenInstance = defaults({ @@ -15,8 +21,24 @@ export class FetchService { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public get = async >( + url: string, + { params = {}, headers = {} }: FetchConfig = {}, + cls: T, + rootKey?: string, + ) => { + const _url = new URL(url); + Object.keys(params).forEach((key) => _url.searchParams.append(key, String(params[key]))); + + const response = await this.fetch>(_url.toString(), { headers }); + const mappedResponse = rootKey ? { [rootKey]: response } : response; + + return validatePlainObject(cls, mappedResponse, undefined, true); + }; + // @TODO-ZM: using DTO, validate response and DRY the types - public get = async ( + public getUnsafe = async ( url: string, { params = {}, headers = {} }: FetchConfig = {}, ) => { @@ -28,8 +50,10 @@ export class FetchService { }; private makeFetchHappenInstance; + // @TODO-ZM: make sure lockFactory works as expected private fetch = lockFactory( async (url: string, { headers }: Omit = {}) => { + this.logger.info({ message: `Fetching ${url}` }); const response = await this.makeFetchHappenInstance(url, { headers }); const jsonResponse = (await response.json()) as T; return jsonResponse; diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts index 5d5152d90..543c1b7ee 100644 --- a/api/src/github/dto.ts +++ b/api/src/github/dto.ts @@ -1,7 +1,20 @@ -import { Validate } from "class-validator"; +import { IsString, Validate, ValidateNested } from "class-validator"; import { IsMapOfStringNumber } from "src/_utils/validator/is-map-of-string-number"; export class GitHubListRepositoryLanguagesResponse { @Validate(IsMapOfStringNumber) languages!: Map; } + +export class GithubAccount { + @IsString() + login!: string; +} + +export class GetRepositoryResponse { + @IsString() + name!: string; + + @ValidateNested() + owner!: GithubAccount; +} diff --git a/api/src/github/service.ts b/api/src/github/service.ts index e64fdd520..d0455fdbe 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -1,13 +1,13 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; import { ConfigService } from "src/config/service"; import { FetchService } from "src/fetch/service"; import { Service } from "typedi"; -import { GitHubListRepositoryLanguagesResponse } from "./dto"; +import { GetRepositoryResponse, GitHubListRepositoryLanguagesResponse } from "./dto"; import { GeneralGithubQuery, + GetRepositoryInput, GetUserInput, GithubIssue, GitHubListRepositoryIssuesInput, @@ -33,7 +33,7 @@ export class GithubService { repository, path, }: GeneralGithubQuery): Promise => { - const commits = await this.fetchService.get( + const commits = await this.fetchService.getUnsafe( `${this.apiURL}/repos/${owner}/${repository}/commits`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, @@ -49,7 +49,7 @@ export class GithubService { }; public getUser = async ({ username }: GetUserInput): Promise => { - const user = await this.fetchService.get( + const user = await this.fetchService.getUnsafe( `${this.apiURL}/users/${username}`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, ); @@ -60,7 +60,7 @@ export class GithubService { owner, repository, }: GitHubListRepositoryIssuesInput): Promise => { - const issues = await this.fetchService.get( + const issues = await this.fetchService.getUnsafe( `${this.apiURL}/repos/${owner}/${repository}/issues`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, @@ -75,18 +75,32 @@ export class GithubService { owner, repository, }: GitHubListRepositoryLanguagesInput): Promise => { - const languages = await this.fetchService.get>( + const languages = await this.fetchService.get( `${this.apiURL}/repos/${owner}/${repository}/languages`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, + GitHubListRepositoryLanguagesResponse, + "languages", ); - return validatePlainObject(GitHubListRepositoryLanguagesResponse, { languages }); + return languages; + }; + + public getRepository = async ({ + owner, + repo, + }: GetRepositoryInput): Promise => { + const repoInfo = await this.fetchService.get( + `${this.apiURL}/repos/${owner}/${repo}`, + { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, + GetRepositoryResponse, + ); + return repoInfo; }; public listRepositoryContributors = async ({ owner, repository, }: Omit): Promise => { - const contributors = await this.fetchService.get( + const contributors = await this.fetchService.getUnsafe( `${this.apiURL}/repos/${owner}/${repository}/contributors`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, @@ -106,7 +120,7 @@ export class GithubService { }; public getRateLimit = async (): Promise<{ limit: number; used: number; ratio: number }> => { - const rateLimitInfo = await this.fetchService.get( + const rateLimitInfo = await this.fetchService.getUnsafe( `${this.apiURL}/rate_limit`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, ); @@ -122,7 +136,7 @@ export class GithubService { owner, repository, }: GitHubListRepositoryMilestonesInput): Promise => { - const milestones = await this.fetchService.get( + const milestones = await this.fetchService.getUnsafe( `${this.apiURL}/repos/${owner}/${repository}/milestones`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, diff --git a/api/src/github/types.ts b/api/src/github/types.ts index a54858519..05f197077 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -80,6 +80,11 @@ export interface GetUserInput { export type GitHubUserApiResponse = GithubUser; +export interface GetRepositoryInput { + owner: string; + repo: string; +} + export interface GitHubListRepositoryIssuesInput { owner: string; repository: string; From 1e966ac75f11770036e700322f07664d6e5d83de Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 18:49:16 +0200 Subject: [PATCH 15/43] replace forward slashes with hyphens sentry version --- packages/tooling/sentry-release.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/tooling/sentry-release.ts b/packages/tooling/sentry-release.ts index 993543472..26acfc352 100644 --- a/packages/tooling/sentry-release.ts +++ b/packages/tooling/sentry-release.ts @@ -6,8 +6,9 @@ if (!scope) throw new Error("Please provide a scope"); const uploadPath = process.argv[3]; if (!uploadPath) throw new Error("Please provide a uploadPath"); -const version = process.argv[4]; +let version = process.argv[4]; if (!version) throw new Error("Please provide a version"); +version = version.replace(/\//g, "-"); const environment = process.argv[5]; if (!environment) throw new Error("Please provide a environment"); From d621c6c0824ce3464b8f68b429f0130d7708a15a Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 18:50:54 +0200 Subject: [PATCH 16/43] chore: don't export GithubAccount class --- api/src/github/dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts index 543c1b7ee..3c5b64377 100644 --- a/api/src/github/dto.ts +++ b/api/src/github/dto.ts @@ -6,7 +6,7 @@ export class GitHubListRepositoryLanguagesResponse { languages!: Map; } -export class GithubAccount { +class GithubAccount { @IsString() login!: string; } From f30db4306e31358681d01e4bd73beb07033ad26a Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 19:12:26 +0200 Subject: [PATCH 17/43] Add db folder to oracle-cloud build --- api/oracle-cloud/deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/oracle-cloud/deploy.ts b/api/oracle-cloud/deploy.ts index 271c8f602..cf1353a12 100644 --- a/api/oracle-cloud/deploy.ts +++ b/api/oracle-cloud/deploy.ts @@ -10,7 +10,7 @@ const stdout = execSync( "lerna list --include-dependencies --json --all --loglevel silent --scope @dzcode.io/api", ); const dependencies = JSON.parse(stdout.toString()) as Array<{ name: string; location: string }>; -const subPaths = ["dist", "package.json", "models"]; +const subPaths = ["dist", "package.json", "models", "db"]; const workspaceRoot = join(__dirname, "../.."); const fromToRecords = dependencies .map<{ from: string; to: string }>(({ location }) => ({ From 67f05859aafc2fbed711ef4c8e66dee23bcc222d Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 5 Sep 2024 21:39:35 +0200 Subject: [PATCH 18/43] added contributions to database --- api/db/migrations/0001_warm_lockheed.sql | 14 ++ api/db/migrations/meta/0001_snapshot.json | 234 ++++++++++++++++++ api/db/migrations/meta/_journal.json | 7 + api/src/contribution/repository.ts | 4 + api/src/digest/cron.ts | 24 +- api/src/github/dto.ts | 41 ++- api/src/github/service.ts | 21 +- .../models/src/contribution/index.spec.ts | 21 +- packages/models/src/contribution/index.ts | 50 ++-- 9 files changed, 366 insertions(+), 50 deletions(-) create mode 100644 api/db/migrations/0001_warm_lockheed.sql create mode 100644 api/db/migrations/meta/0001_snapshot.json diff --git a/api/db/migrations/0001_warm_lockheed.sql b/api/db/migrations/0001_warm_lockheed.sql new file mode 100644 index 000000000..9df3ca887 --- /dev/null +++ b/api/db/migrations/0001_warm_lockheed.sql @@ -0,0 +1,14 @@ +CREATE TABLE `contributions` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `name` text NOT NULL, + `updated_at` text NOT NULL, + `url` text NOT NULL, + `type` text NOT NULL, + `run_id` text NOT NULL, + `activity_count` integer NOT NULL, + `repository_id` integer NOT NULL, + FOREIGN KEY (`repository_id`) REFERENCES `repositories`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `contributions_url_unique` ON `contributions` (`url`); \ No newline at end of file diff --git a/api/db/migrations/meta/0001_snapshot.json b/api/db/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..7930c5c7e --- /dev/null +++ b/api/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,234 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "d333591a-11be-4b30-89f8-4da0aa5bd906", + "prevId": "ab1b512a-8ca8-44e1-b497-445465117250", + "tables": { + "contributions": { + "name": "contributions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity_count": { + "name": "activity_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributions_url_unique": { + "name": "contributions_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": { + "contributions_repository_id_repositories_id_fk": { + "name": "contributions_repository_id_repositories_id_fk", + "tableFrom": "contributions", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "repositories_provider_owner_name_unique": { + "name": "repositories_provider_owner_name_unique", + "columns": ["provider", "owner", "name"], + "isUnique": true + } + }, + "foreignKeys": { + "repositories_project_id_projects_id_fk": { + "name": "repositories_project_id_projects_id_fk", + "tableFrom": "repositories", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index a87ceb45e..68fd83398 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1725462108723, "tag": "0000_new_lake", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1725564569182, + "tag": "0001_warm_lockheed", + "breakpoints": true } ] } diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index bb0dff878..6f19d9da3 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -1,3 +1,7 @@ +// @TODO-ZM: remove this +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + import { Model } from "@dzcode.io/models/dist/_base"; import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; import { DataService } from "src/data/service"; diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 344122540..646dfea98 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -1,5 +1,6 @@ import { captureException, cron } from "@sentry/node"; import { CronJob } from "cron"; +import { ContributionRepository } from "src/contribution/repository"; import { DataService } from "src/data/service"; import { GithubService } from "src/github/service"; import { LoggerService } from "src/logger/service"; @@ -18,6 +19,7 @@ export class DigestCron { private readonly githubService: GithubService, private readonly projectsRepository: ProjectRepository, private readonly repositoriesRepository: RepositoryRepository, + private readonly contributionsRepository: ContributionRepository, ) { const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron"); new SentryCronJob( @@ -74,7 +76,6 @@ export class DigestCron { repo: repository.name, }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [{ id: repositoryId }] = await this.repositoriesRepository.upsert({ provider: "github", name: repoInfo.name, @@ -82,6 +83,26 @@ export class DigestCron { runId, projectId, }); + + const issues = await this.githubService.listRepositoryIssues({ + owner: repository.owner, + repo: repository.name, + }); + + for (const issue of issues.issues) { + const type = issue.pull_request ? "PULL_REQUEST" : "ISSUE"; + const [{ id: contributionId }] = await this.contributionsRepository.upsert({ + title: issue.title, + type, + updatedAt: issue.updated_at, + activityCount: issue.comments, + runId, + repositoryId, + url: type === "PULL_REQUEST" ? issue.pull_request.html_url : issue.html_url, + }); + + console.log("contributionId", contributionId); + } } catch (error) { // @TODO-ZM: capture error console.error(error); @@ -93,6 +114,7 @@ export class DigestCron { } } + await this.contributionsRepository.deleteAllButWithRunId(runId); await this.repositoriesRepository.deleteAllButWithRunId(runId); await this.projectsRepository.deleteAllButWithRunId(runId); diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts index 3c5b64377..e190897d4 100644 --- a/api/src/github/dto.ts +++ b/api/src/github/dto.ts @@ -1,4 +1,4 @@ -import { IsString, Validate, ValidateNested } from "class-validator"; +import { IsIn, IsNumber, IsString, Validate, ValidateNested } from "class-validator"; import { IsMapOfStringNumber } from "src/_utils/validator/is-map-of-string-number"; export class GitHubListRepositoryLanguagesResponse { @@ -18,3 +18,42 @@ export class GetRepositoryResponse { @ValidateNested() owner!: GithubAccount; } + +class GetRepositoryIssuesPullRequestResponse { + @IsString() + html_url!: string; // eslint-disable-line camelcase +} + +class GetRepositoryIssuesResponse { + @IsString() + title!: string; + + @ValidateNested() + user!: GithubAccount; + + @IsString({ each: true }) + labels!: string[]; + + @IsIn(["closed", "open"]) + state!: "closed" | "open"; + + @ValidateNested({ each: true }) + assignees!: GithubAccount[]; + + @IsString() + updated_at!: string; // eslint-disable-line camelcase + + @IsString() + html_url!: string; // eslint-disable-line camelcase + + @ValidateNested() + pull_request!: GetRepositoryIssuesPullRequestResponse; // eslint-disable-line camelcase + + @IsNumber() + comments!: number; +} + +export class GetRepositoryIssuesResponseArray { + @ValidateNested({ each: true }) + issues!: GetRepositoryIssuesResponse[]; +} diff --git a/api/src/github/service.ts b/api/src/github/service.ts index d0455fdbe..45b8fb8cb 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -4,13 +4,15 @@ import { ConfigService } from "src/config/service"; import { FetchService } from "src/fetch/service"; import { Service } from "typedi"; -import { GetRepositoryResponse, GitHubListRepositoryLanguagesResponse } from "./dto"; +import { + GetRepositoryIssuesResponseArray, + GetRepositoryResponse, + GitHubListRepositoryLanguagesResponse, +} from "./dto"; import { GeneralGithubQuery, GetRepositoryInput, GetUserInput, - GithubIssue, - GitHubListRepositoryIssuesInput, GitHubListRepositoryLanguagesInput, GitHubListRepositoryMilestonesInput, GithubMilestone, @@ -58,17 +60,20 @@ export class GithubService { public listRepositoryIssues = async ({ owner, - repository, - }: GitHubListRepositoryIssuesInput): Promise => { - const issues = await this.fetchService.getUnsafe( - `${this.apiURL}/repos/${owner}/${repository}/issues`, + repo, + }: GetRepositoryInput): Promise => { + const repoIssues = await this.fetchService.get( + `${this.apiURL}/repos/${owner}/${repo}/issues`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, + // @TODO-ZM: add pagination params: { sort: "updated", per_page: 100 }, // eslint-disable-line camelcase }, + GetRepositoryIssuesResponseArray, + "issues", ); - return issues; + return repoIssues; }; public listRepositoryLanguages = async ({ diff --git a/packages/models/src/contribution/index.spec.ts b/packages/models/src/contribution/index.spec.ts index 1a25692fb..3e90685be 100644 --- a/packages/models/src/contribution/index.spec.ts +++ b/packages/models/src/contribution/index.spec.ts @@ -5,26 +5,13 @@ import { ContributionEntity } from "."; runDTOTestCases( ContributionEntity, { - commentsCount: 0, - id: "71", - labels: ["discussion", "good first issue"], - languages: ["JavaScript", "Shell"], - project: { - name: "Leblad", - slug: "Leblad", - }, + activityCount: 0, title: "Update the data set", - type: "issue", - createdAt: "2020-02-02T20:22:02.000Z", + type: "ISSUE", updatedAt: "2020-02-02T20:22:02.000Z", url: "https://github.com/dzcode-io/leblad/issues/71", - createdBy: { - id: "github/20110076", - username: "ZibanPirate", - name: "Zakaria Mansouri", - profileUrl: "/Account/github/20110076", - avatarUrl: "https://avatars.githubusercontent.com/u/20110076?v=4", - }, + id: 0, + runId: "test-run-id", }, {}, ); diff --git a/packages/models/src/contribution/index.ts b/packages/models/src/contribution/index.ts index da22a1118..78602e086 100644 --- a/packages/models/src/contribution/index.ts +++ b/packages/models/src/contribution/index.ts @@ -1,42 +1,46 @@ import { Type } from "class-transformer"; -import { IsDateString, IsNumber, IsString, IsUrl, ValidateNested } from "class-validator"; +import { IsDateString, IsIn, IsNumber, IsString, IsUrl, ValidateNested } from "class-validator"; import { BaseEntity, Model } from "src/_base"; import { AccountEntity } from "src/account"; import { ProjectReferenceEntity } from "src/project-reference"; -export class ContributionEntity extends BaseEntity { +export class ContributionEntityCompact extends BaseEntity { + // @TODO-ZM: move this to BaseEntity @IsString() - id!: string; + id!: number; @IsString() title!: string; - @ValidateNested() - @Type(() => ProjectReferenceEntity) - project!: Model; - - @ValidateNested() - @Type(() => AccountEntity) - createdBy!: Model; - - @IsString() - type!: "issue" | "pullRequest"; + @IsIn(["ISSUE", "PULL_REQUEST"]) + type!: "ISSUE" | "PULL_REQUEST"; @IsUrl() url!: string; - @IsString({ each: true }) - languages!: string[]; - - @IsString({ each: true }) - labels!: string[]; - - @IsDateString() - createdAt!: string; - @IsDateString() updatedAt!: string; @IsNumber() - commentsCount!: number; + activityCount!: number; +} +export class ContributionEntity extends ContributionEntityCompact { + @IsString() + runId!: string; +} + +export class ContributionEntityForList extends BaseEntity { + @ValidateNested() + @Type(() => AccountEntity) + createdBy!: Model; // Compact + + @ValidateNested() + @Type(() => ProjectReferenceEntity) + project!: Model; // Compact + + @IsString({ each: true }) + languages!: string[]; // Compact + + @IsString({ each: true }) + labels!: string[]; // Compact } From 9f57d53483d02e2d39278d7238c918df48e933d0 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Fri, 6 Sep 2024 21:24:18 +0200 Subject: [PATCH 19/43] CRUD contributions --- api/db/migrations/0002_mighty_may_parker.sql | 2 + api/db/migrations/meta/0002_snapshot.json | 234 ++++++++++++++++++ api/db/migrations/meta/_journal.json | 7 + api/src/_utils/case.ts | 11 + api/src/_utils/reverse-hierarchy.ts | 46 ++++ api/src/_utils/unstringify-deep.ts | 34 +++ api/src/app/types/index.ts | 2 - api/src/contribution/repository.ts | 174 +++++-------- api/src/contribution/table.ts | 25 ++ .../__snapshots__/index.spec.ts.snap | 155 ------------ .../models/src/contribution/index.spec.ts | 17 -- packages/models/src/contribution/index.ts | 53 +--- packages/models/src/project/index.ts | 30 +-- .../__snapshots__/index.spec.ts.snap | 64 ----- packages/models/src/repository/index.spec.ts | 18 -- packages/models/src/repository/index.ts | 44 +--- 16 files changed, 438 insertions(+), 478 deletions(-) create mode 100644 api/db/migrations/0002_mighty_may_parker.sql create mode 100644 api/db/migrations/meta/0002_snapshot.json create mode 100644 api/src/_utils/case.ts create mode 100644 api/src/_utils/reverse-hierarchy.ts create mode 100644 api/src/_utils/unstringify-deep.ts create mode 100644 api/src/contribution/table.ts delete mode 100644 packages/models/src/contribution/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/contribution/index.spec.ts delete mode 100644 packages/models/src/repository/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/repository/index.spec.ts diff --git a/api/db/migrations/0002_mighty_may_parker.sql b/api/db/migrations/0002_mighty_may_parker.sql new file mode 100644 index 000000000..7095e1b0f --- /dev/null +++ b/api/db/migrations/0002_mighty_may_parker.sql @@ -0,0 +1,2 @@ +ALTER TABLE `contributions` ADD `title` text NOT NULL;--> statement-breakpoint +ALTER TABLE `contributions` DROP COLUMN `name`; \ No newline at end of file diff --git a/api/db/migrations/meta/0002_snapshot.json b/api/db/migrations/meta/0002_snapshot.json new file mode 100644 index 000000000..29b79923b --- /dev/null +++ b/api/db/migrations/meta/0002_snapshot.json @@ -0,0 +1,234 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "4c5b2a9b-5f9e-496c-a73e-e260a0513b86", + "prevId": "d333591a-11be-4b30-89f8-4da0aa5bd906", + "tables": { + "contributions": { + "name": "contributions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity_count": { + "name": "activity_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributions_url_unique": { + "name": "contributions_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": { + "contributions_repository_id_repositories_id_fk": { + "name": "contributions_repository_id_repositories_id_fk", + "tableFrom": "contributions", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "repositories_provider_owner_name_unique": { + "name": "repositories_provider_owner_name_unique", + "columns": ["provider", "owner", "name"], + "isUnique": true + } + }, + "foreignKeys": { + "repositories_project_id_projects_id_fk": { + "name": "repositories_project_id_projects_id_fk", + "tableFrom": "repositories", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index 68fd83398..a3a5cb484 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1725564569182, "tag": "0001_warm_lockheed", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1725568393657, + "tag": "0002_mighty_may_parker", + "breakpoints": true } ] } diff --git a/api/src/_utils/case.ts b/api/src/_utils/case.ts new file mode 100644 index 000000000..9870d38ad --- /dev/null +++ b/api/src/_utils/case.ts @@ -0,0 +1,11 @@ +import camelCase from "lodash/camelCase"; +import mapKeys from "lodash/mapKeys"; + +export function camelCaseObject>(obj: T): T { + const array = !Array.isArray(obj) ? [obj] : obj; + const camelCasedArray = array.map((item) => { + return mapKeys(item, (value, key) => camelCase(key)); + }); + + return (!Array.isArray(obj) ? camelCasedArray[0] : camelCasedArray) as T; +} diff --git a/api/src/_utils/reverse-hierarchy.ts b/api/src/_utils/reverse-hierarchy.ts new file mode 100644 index 000000000..e1d08e0e8 --- /dev/null +++ b/api/src/_utils/reverse-hierarchy.ts @@ -0,0 +1,46 @@ +type ForeignKeyParentKeyRecord = { from: string; setParentAs: string }; + +export function reverseHierarchy( + _obj: unknown, + foreignKeyParentKeyRecord: ForeignKeyParentKeyRecord[], + parentWithItsKey: Record = {}, +): any { + if (Array.isArray(_obj)) { + return _obj + .map((item) => reverseHierarchy(item, foreignKeyParentKeyRecord, parentWithItsKey)) + .reduce((pV, cV) => [...pV, ...cV], []); + } + + if (typeof _obj !== "object") { + return _obj; + } + + const obj = { ..._obj, ...parentWithItsKey }; + + const objWithRecognizedKeys: Record = {}; + const objWithoutRecognizedKeys: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const mappedKey = foreignKeyParentKeyRecord.find(({ from: mk }) => mk === key)?.from; + if (mappedKey) objWithRecognizedKeys[mappedKey] = value; + else objWithoutRecognizedKeys[key] = value; + } + + if (Object.keys(objWithRecognizedKeys).length > 0) { + let res: unknown[] = []; + for (const recognizedKey in objWithRecognizedKeys) { + if (Object.prototype.hasOwnProperty.call(objWithRecognizedKeys, recognizedKey)) { + const recognizedObj = objWithRecognizedKeys[recognizedKey]; + const { setParentAs } = foreignKeyParentKeyRecord.find( + ({ from }) => from === recognizedKey, + ) as ForeignKeyParentKeyRecord; + const reversedPredecessor = reverseHierarchy(recognizedObj, foreignKeyParentKeyRecord, { + [setParentAs]: objWithoutRecognizedKeys, + }); + res = res.concat(reversedPredecessor); + } + } + return res; + } + + return [objWithoutRecognizedKeys]; +} diff --git a/api/src/_utils/unstringify-deep.ts b/api/src/_utils/unstringify-deep.ts new file mode 100644 index 000000000..61f5bbe36 --- /dev/null +++ b/api/src/_utils/unstringify-deep.ts @@ -0,0 +1,34 @@ +/** + * Recursively look for any string field that starts with `[{"` or `{"` and parse it + unStringify + * its children. + */ + +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint +export function unStringifyDeep(obj: T): T { + if (Array.isArray(obj)) { + return obj.map((item) => unStringifyDeep(item)) as T; + } + + if (typeof obj !== "object" || obj === null) { + return obj; + } + + const result = { ...obj }; + + for (const key in result) { + if (typeof result[key] === "string") { + try { + const value = JSON.parse(result[key]); + if (typeof value === "object") { + result[key] = unStringifyDeep(value); + } else { + result[key] = value; + } + } catch (error) { + // ignore + } + } + } + + return result as T; +} diff --git a/api/src/app/types/index.ts b/api/src/app/types/index.ts index 868ed103e..2ad143283 100644 --- a/api/src/app/types/index.ts +++ b/api/src/app/types/index.ts @@ -1,5 +1,3 @@ -import "reflect-metadata"; - import { IsNumber, IsObject, IsOptional, IsString } from "class-validator"; export class GeneralResponseDto { diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index 6f19d9da3..a98e450bf 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -1,128 +1,74 @@ -// @TODO-ZM: remove this -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import { Model } from "@dzcode.io/models/dist/_base"; -import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; -import { DataService } from "src/data/service"; -import { GithubService } from "src/github/service"; -import { LoggerService } from "src/logger/service"; +import { ne, sql } from "drizzle-orm"; +import { camelCaseObject } from "src/_utils/case"; +import { reverseHierarchy } from "src/_utils/reverse-hierarchy"; +import { unStringifyDeep } from "src/_utils/unstringify-deep"; +import { projectsTable } from "src/project/table"; +import { repositoriesTable } from "src/repository/table"; +import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; -import { allFilterNames, FilterDto, GetContributionsResponseDto, OptionDto } from "./types"; +import { ContributionRow, contributionsTable } from "./table"; @Service() export class ContributionRepository { - constructor( - private readonly githubService: GithubService, - private readonly dataService: DataService, - private readonly loggerService: LoggerService, - ) {} - - public async find( - filterFn?: (value: ContributionEntity, index: number, array: ContributionEntity[]) => boolean, - ): Promise> { - const projects = await this.dataService.listProjects(); + constructor(private readonly sqliteService: SQLiteService) { + this.findForList(); + } - let contributions = ( - await Promise.all( - projects.reduce[]>[]>( - (pV, { repositories, name, slug }) => [ - ...pV, - ...repositories - .filter(({ provider }) => provider === "github") - .map(async ({ owner, name: repository }) => { - try { - const issuesIncludingPRs = await this.githubService.listRepositoryIssues({ - owner, - repository, - }); + public async upsert(contribution: ContributionRow) { + return await this.sqliteService.db + .insert(contributionsTable) + .values(contribution) + .onConflictDoUpdate({ + target: [contributionsTable.url], + set: contribution, + }) + .returning({ id: contributionsTable.id }); + } - const languages = await this.githubService.listRepositoryLanguages({ - owner, - repository, - }); - return issuesIncludingPRs.map>( - ({ - number, - labels: gLabels, - title, - html_url, // eslint-disable-line camelcase - pull_request, // eslint-disable-line camelcase - created_at, // eslint-disable-line camelcase - updated_at, // eslint-disable-line camelcase - comments, - user, - }) => ({ - id: `${number}`, - labels: gLabels.map(({ name }) => name), - languages: Object.keys(languages), - project: { - slug, - name, - }, - title, - type: pull_request ? "pullRequest" : "issue", // eslint-disable-line camelcase - url: html_url, // eslint-disable-line camelcase - createdAt: created_at, // eslint-disable-line camelcase - updatedAt: updated_at, // eslint-disable-line camelcase - commentsCount: comments, - /* eslint-enable camelcase */ - createdBy: this.githubService.githubUserToAccountEntity(user), - }), - ); - } catch (error) { - this.loggerService.warn({ - message: `Failed to fetch contributions for ${owner}/${repository}: ${error}`, - meta: { owner, repository }, - }); - return []; - } - }), - ], - [], - ), - ) - ).reduce((pV, cV) => [...pV, ...cV], []); - if (filterFn) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - contributions = contributions.filter(filterFn); - } - contributions = contributions.sort( - (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), - ); + public async deleteAllButWithRunId(runId: string) { + return await this.sqliteService.db + .delete(contributionsTable) + .where(ne(contributionsTable.runId, runId)); + } - const filters: FilterDto[] = allFilterNames.map((name) => ({ name, options: [] })); + public async findForList() { + const statement = sql` + SELECT + p.id as id, + p.name as name, + json_group_array( + json_object('id', r.id, 'name', r.name, 'owner', r.owner, 'contributions', r.contributions) + ) AS repositories + FROM + (SELECT + r.id as id, + r.owner as owner, + r.name as name, + r.project_id as project_id, + json_group_array( + json_object('id', c.id, 'title', c.title, 'type', c.type, 'url', c.url, 'updated_at', c.updated_at, 'activity_count', c.activity_count) + ) AS contributions + FROM + ${contributionsTable} c + RIGHT JOIN + ${repositoriesTable} r ON c.id = r.project_id + GROUP BY + c.id) AS r + RIGHT JOIN + ${projectsTable} p ON r.project_id = p.id + `; - contributions.forEach(({ project, languages, labels }) => { - this.pushUniqueOption([{ name: project.slug, label: project.name }], filters[0].options); + const raw = this.sqliteService.db.all(statement); + const unStringifiedRaw = unStringifyDeep(raw); - this.pushUniqueOption( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - languages.map((language) => ({ name: language })), - filters[1].options, - ); + const reversed = reverseHierarchy(unStringifiedRaw, [ + { from: "repositories", setParentAs: "project" }, + { from: "contributions", setParentAs: "repository" }, + ]); - this.pushUniqueOption( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - labels.map((label) => ({ name: label })), - filters[2].options, - ); - }); + const camelCased = camelCaseObject(reversed); - return { - contributions, - filters, - }; + return camelCased; } - - private pushUniqueOption = (options: OptionDto[], filterOptions: OptionDto[]) => { - const uniqueOptions = options.filter( - (_option) => !filterOptions.some(({ name }) => _option.name === name), - ); - filterOptions.push(...uniqueOptions); - }; } diff --git a/api/src/contribution/table.ts b/api/src/contribution/table.ts new file mode 100644 index 000000000..52f4d5ad9 --- /dev/null +++ b/api/src/contribution/table.ts @@ -0,0 +1,25 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; +import { sql } from "drizzle-orm"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { repositoriesTable } from "src/repository/table"; + +export const contributionsTable = sqliteTable("contributions", { + id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + recordImportedAt: text("record_imported_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + title: text("title").notNull(), + updatedAt: text("updated_at").notNull(), + url: text("url").notNull().unique(), + type: text("type").notNull().$type(), + runId: text("run_id").notNull(), + activityCount: integer("activity_count").notNull(), + repositoryId: integer("repository_id") + .notNull() + .references(() => repositoriesTable.id), +}); + +contributionsTable.$inferSelect satisfies Model; + +export type ContributionRow = typeof contributionsTable.$inferInsert; diff --git a/packages/models/src/contribution/__snapshots__/index.spec.ts.snap b/packages/models/src/contribution/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 750d74aed..000000000 --- a/packages/models/src/contribution/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -ContributionEntity { - "commentsCount": 0, - "createdAt": "2020-02-02T20:22:02.000Z", - "createdBy": AccountEntity { - "avatarUrl": "https://avatars.githubusercontent.com/u/20110076?v=4", - "id": "github/20110076", - "name": "Zakaria Mansouri", - "profileUrl": "/Account/github/20110076", - "username": "ZibanPirate", - }, - "id": "71", - "labels": [ - "discussion", - "good first issue", - ], - "languages": [ - "JavaScript", - "Shell", - ], - "project": ProjectReferenceEntity { - "name": "Leblad", - "slug": "Leblad", - }, - "title": "Update the data set", - "type": "issue", - "updatedAt": "2020-02-02T20:22:02.000Z", - "url": "https://github.com/dzcode-io/leblad/issues/71", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -ContributionEntity { - "commentsCount": 0, - "createdAt": "2020-02-02T20:22:02.000Z", - "createdBy": AccountEntity { - "avatarUrl": "https://avatars.githubusercontent.com/u/20110076?v=4", - "id": "github/20110076", - "name": "Zakaria Mansouri", - "profileUrl": "/Account/github/20110076", - "username": "ZibanPirate", - }, - "id": "71", - "labels": [ - "discussion", - "good first issue", - ], - "languages": [ - "JavaScript", - "Shell", - ], - "project": ProjectReferenceEntity { - "name": "Leblad", - "slug": "Leblad", - }, - "title": "Update the data set", - "type": "issue", - "updatedAt": "2020-02-02T20:22:02.000Z", - "url": "https://github.com/dzcode-io/leblad/issues/71", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "id must be a string", - }, - "property": "id", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "title must be a string", - }, - "property": "title", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "type must be a string", - }, - "property": "type", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isUrl": "url must be an URL address", - }, - "property": "url", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "each value in languages must be a string", - }, - "property": "languages", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "each value in labels must be a string", - }, - "property": "labels", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isDateString": "createdAt must be a valid ISO 8601 date string", - }, - "property": "createdAt", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isDateString": "updatedAt must be a valid ISO 8601 date string", - }, - "property": "updatedAt", - "target": ContributionEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isNumber": "commentsCount must be a number conforming to the specified constraints", - }, - "property": "commentsCount", - "target": ContributionEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `ContributionEntity {}`; diff --git a/packages/models/src/contribution/index.spec.ts b/packages/models/src/contribution/index.spec.ts deleted file mode 100644 index 3e90685be..000000000 --- a/packages/models/src/contribution/index.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { ContributionEntity } from "."; - -runDTOTestCases( - ContributionEntity, - { - activityCount: 0, - title: "Update the data set", - type: "ISSUE", - updatedAt: "2020-02-02T20:22:02.000Z", - url: "https://github.com/dzcode-io/leblad/issues/71", - id: 0, - runId: "test-run-id", - }, - {}, -); diff --git a/packages/models/src/contribution/index.ts b/packages/models/src/contribution/index.ts index 78602e086..7dbcb0de7 100644 --- a/packages/models/src/contribution/index.ts +++ b/packages/models/src/contribution/index.ts @@ -1,46 +1,9 @@ -import { Type } from "class-transformer"; -import { IsDateString, IsIn, IsNumber, IsString, IsUrl, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { AccountEntity } from "src/account"; -import { ProjectReferenceEntity } from "src/project-reference"; - -export class ContributionEntityCompact extends BaseEntity { - // @TODO-ZM: move this to BaseEntity - @IsString() - id!: number; - - @IsString() - title!: string; - - @IsIn(["ISSUE", "PULL_REQUEST"]) - type!: "ISSUE" | "PULL_REQUEST"; - - @IsUrl() - url!: string; - - @IsDateString() - updatedAt!: string; - - @IsNumber() - activityCount!: number; -} -export class ContributionEntity extends ContributionEntityCompact { - @IsString() - runId!: string; -} - -export class ContributionEntityForList extends BaseEntity { - @ValidateNested() - @Type(() => AccountEntity) - createdBy!: Model; // Compact - - @ValidateNested() - @Type(() => ProjectReferenceEntity) - project!: Model; // Compact - - @IsString({ each: true }) - languages!: string[]; // Compact - - @IsString({ each: true }) - labels!: string[]; // Compact +export interface ContributionEntity { + id: number; + title: string; + type: "ISSUE" | "PULL_REQUEST"; + url: string; + updatedAt: string; + activityCount: number; + runId: string; } diff --git a/packages/models/src/project/index.ts b/packages/models/src/project/index.ts index fb2d2c401..744bb62a2 100644 --- a/packages/models/src/project/index.ts +++ b/packages/models/src/project/index.ts @@ -1,27 +1,7 @@ -import { Type } from "class-transformer"; -import { IsNumber, IsString, ValidateNested } from "class-validator"; -import { BaseEntity } from "src/_base"; -import { RepositoryEntityCompact } from "src/repository"; - -export class ProjectEntityCompact extends BaseEntity { +export interface ProjectEntity { // @TODO-ZM: move this to BaseEntity - @IsNumber() - id!: number; - - @IsString() - slug!: string; - - @IsString() - name!: string; -} - -export class ProjectEntity extends ProjectEntityCompact { - @IsString() - runId!: string; -} - -export class ProjectEntityForList extends ProjectEntityCompact { - @ValidateNested({ each: true }) - @Type(() => RepositoryEntityCompact) - repositories!: RepositoryEntityCompact[]; + id: number; + slug: string; + name: string; + runId: string; } diff --git a/packages/models/src/repository/__snapshots__/index.spec.ts.snap b/packages/models/src/repository/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 72edf3836..000000000 --- a/packages/models/src/repository/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,64 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -RepositoryEntity { - "contributions": [], - "contributors": [], - "owner": "dzcode-io", - "provider": "github", - "repository": "leblad", - "stats": RepositoryStatsEntity { - "contributionCount": 10, - "languages": [ - "TypeScript", - "Rust", - ], - }, -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -RepositoryEntity { - "owner": "dzcode-io", - "provider": "github", - "repository": "leblad", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isIn": "provider must be one of the following values: github, gitlab", - }, - "property": "provider", - "target": RepositoryEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "owner must be a string", - }, - "property": "owner", - "target": RepositoryEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "repository must be a string", - }, - "property": "repository", - "target": RepositoryEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `RepositoryEntity {}`; diff --git a/packages/models/src/repository/index.spec.ts b/packages/models/src/repository/index.spec.ts deleted file mode 100644 index 24cb83cc2..000000000 --- a/packages/models/src/repository/index.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { RepositoryEntity } from "."; - -runDTOTestCases( - RepositoryEntity, - { - provider: "github", - owner: "dzcode-io", - name: "leblad", - id: 0, - runId: "initial-run-id", - }, - { - contributions: [], - contributors: [], - }, -); diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index be27e6895..6fd42de19 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -1,43 +1,11 @@ -import { Type } from "class-transformer"; -import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator"; -import { BaseEntity } from "src/_base"; -import { AccountEntity } from "src/account"; -import { ContributionEntity } from "src/contribution"; - const RepositoryProviders = ["github", "gitlab"] as const; type RepositoryProvider = (typeof RepositoryProviders)[number]; -export class RepositoryEntityCompact extends BaseEntity { +export interface RepositoryEntity { // @TODO-ZM: move this to BaseEntity - @IsNumber() - id!: number; - - @IsString() - owner!: string; - - @IsString() - name!: string; -} - -export class RepositoryEntity extends RepositoryEntityCompact { - @IsString() - runId!: string; - - @IsIn(RepositoryProviders) - provider!: RepositoryProvider; -} - -export class RepositoryEntityForList extends RepositoryEntity { - // TODO-ZM: add programming languages - // @ValidateNested({ each: true }) - // @Type(() => ProgrammingLanguageEntity) - // programmingLanguages!: ProgrammingLanguageEntityCompact[]; - - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - contributors!: AccountEntity[]; - - @ValidateNested({ each: true }) - @Type(() => ContributionEntity) - contributions!: ContributionEntity[]; + id: number; + owner: string; + name: string; + runId: string; + provider: RepositoryProvider; } From cdcbf7b800c4e6876de191c65dd8ebfbebe6ce77 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Fri, 6 Sep 2024 21:27:04 +0200 Subject: [PATCH 20/43] refactored projects and contribs endpoints --- api/src/contribution/controller.ts | 26 ++++---------------------- api/src/contribution/types.ts | 19 ++++++++++--------- api/src/project/controller.ts | 5 ----- api/src/project/repository.ts | 12 ++++++------ api/src/project/types.ts | 12 +++--------- web/src/pages/contribute/index.tsx | 27 ++++++++++++++++----------- 6 files changed, 39 insertions(+), 62 deletions(-) diff --git a/api/src/contribution/controller.ts b/api/src/contribution/controller.ts index 7bfbea048..be91fd1d1 100644 --- a/api/src/contribution/controller.ts +++ b/api/src/contribution/controller.ts @@ -1,9 +1,8 @@ -import { Controller, Get, QueryParams } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; +import { Controller, Get } from "routing-controllers"; import { Service } from "typedi"; import { ContributionRepository } from "./repository"; -import { GetContributionsQueryDto, GetContributionsResponseDto } from "./types"; +import { GetContributionsResponseDto } from "./types"; @Service() @Controller("/Contributions") @@ -11,28 +10,11 @@ export class ContributionController { constructor(private readonly contributionRepository: ContributionRepository) {} @Get("/") - @OpenAPI({ - summary: "Return a list of contributions for all projects listed in dzcode.io", - }) - @ResponseSchema(GetContributionsResponseDto) - public async getContributions( - @QueryParams() { labels, languages, projects }: GetContributionsQueryDto, - ): Promise { - const { contributions, filters } = await this.contributionRepository.find( - (contribution) => - !contribution.createdBy.username.includes("[bot]") && - (labels.length === 0 || labels.some((label) => contribution.labels.includes(label))) && - (languages.length === 0 || - languages.some((language) => contribution.languages.includes(language))) && - (projects.length === 0 || - projects.some((project) => { - return contribution.project.slug === project; - })), - ); + public async getContributions(): Promise { + const contributions = await this.contributionRepository.findForList(); return { contributions, - filters, }; } } diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index 77c154aaf..c6562ccc8 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -1,5 +1,6 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { Transform, TransformFnParams, Type } from "class-transformer"; import { IsBoolean, IsIn, IsOptional, IsString, ValidateNested } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; @@ -28,14 +29,14 @@ export class FilterDto { options!: OptionDto[]; } -export class GetContributionsResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => ContributionEntity) - contributions!: Model[]; - - @ValidateNested({ each: true }) - @Type(() => FilterDto) - filters!: FilterDto[]; +export interface GetContributionsResponseDto extends GeneralResponseDto { + contributions: Array< + Pick & { + repository: Pick & { + project: Pick; + }; + } + >; } const transformFilterOptions = ({ value }: TransformFnParams) => { diff --git a/api/src/project/controller.ts b/api/src/project/controller.ts index 16daf1a5c..a0fde7753 100644 --- a/api/src/project/controller.ts +++ b/api/src/project/controller.ts @@ -1,5 +1,4 @@ import { Controller, Get } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; import { Service } from "typedi"; import { ProjectRepository } from "./repository"; @@ -11,10 +10,6 @@ export class ProjectController { constructor(private readonly projectRepository: ProjectRepository) {} @Get("/") - @OpenAPI({ - summary: "Return all projects listed in dzcode.io website", - }) - @ResponseSchema(GetProjectsResponseDto) public async getProjects(): Promise { const projects = await this.projectRepository.findForList(); diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index ffb6da8ed..272ee40bd 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,6 +1,5 @@ -import { ProjectEntityForList } from "@dzcode.io/models/dist/project"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { ne, sql } from "drizzle-orm"; -import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -12,6 +11,7 @@ export class ProjectRepository { constructor(private readonly sqliteService: SQLiteService) {} public async findForList() { + // @TODO-ZM: reverse hierarchy instead here const statement = sql` SELECT p.id as id, @@ -29,12 +29,12 @@ export class ProjectRepository { `; const raw = this.sqliteService.db.all(statement) as Array< // the SQL query above returns a stringified JSON for the `repositories` column - Omit & { repositories: string } + Omit & { repositories: string } >; - const projectsForList: ProjectEntityForList[] = raw.map((row) => { + const projectsForList: ProjectEntity[] = raw.map((row) => { const notYetValid = { ...row, repositories: JSON.parse(row.repositories) }; - const validated = validatePlainObject(ProjectEntityForList, notYetValid, true); - return validated; + + return notYetValid; }); return projectsForList; diff --git a/api/src/project/types.ts b/api/src/project/types.ts index 426fb6a38..cf2cc84fa 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -1,12 +1,6 @@ -import { ProjectEntityForList } from "@dzcode.io/models/dist/project"; -import { Type } from "class-transformer"; -import { ValidateNested } from "class-validator"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { GeneralResponseDto } from "src/app/types"; -// @TODO-ZM: remove Model<> from existence - -export class GetProjectsResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => ProjectEntityForList) - projects!: Array; +export interface GetProjectsResponseDto extends GeneralResponseDto { + projects: ProjectEntity[]; } diff --git a/web/src/pages/contribute/index.tsx b/web/src/pages/contribute/index.tsx index fa7aa6a95..ed7762943 100644 --- a/web/src/pages/contribute/index.tsx +++ b/web/src/pages/contribute/index.tsx @@ -54,8 +54,13 @@ export default function Page(): JSX.Element {

- {contribution.project.name} -
+ + {contribution.repository.project.name} + + + {contribution.repository.owner}/{contribution.repository.name} + + {/*
{contribution.labels.map((label, labelIndex) => ( {label} @@ -66,17 +71,14 @@ export default function Page(): JSX.Element { {language} ))} -
+
*/}
- + /> */}
-
- {getElapsedTime(contribution.updatedAt, localize("elapsed-time-suffixes"))} -
- {contribution.commentsCount > 0 && ( + {contribution.activityCount > 0 && (
- {contribution.commentsCount} + {contribution.activityCount}
)} +
+ {getElapsedTime(contribution.updatedAt, localize("elapsed-time-suffixes"))} +
- {contribution.type === "issue" + {contribution.type === "ISSUE" ? localize("contribute-read-issue") : localize("contribute-review-changes")} From 17e0707ad69e15596b0f868ada7e5c7168290108 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Fri, 6 Sep 2024 22:38:49 +0200 Subject: [PATCH 21/43] added contributors to database --- api/db/migrations/0000_new_lake.sql | 21 -- api/db/migrations/0000_sudden_doctor_doom.sql | 48 ++++ api/db/migrations/0001_warm_lockheed.sql | 14 -- api/db/migrations/0002_mighty_may_parker.sql | 2 - api/db/migrations/meta/0000_snapshot.json | 173 ++++++++++++- api/db/migrations/meta/0001_snapshot.json | 234 ------------------ api/db/migrations/meta/0002_snapshot.json | 234 ------------------ api/db/migrations/meta/_journal.json | 18 +- api/src/contribution/table.ts | 4 + api/src/contributor/repository.ts | 27 ++ api/src/contributor/table.ts | 20 ++ api/src/digest/cron.ts | 17 +- packages/models/src/contributor/index.ts | 9 + .../project/__snapshots__/index.spec.ts.snap | 68 ----- 14 files changed, 298 insertions(+), 591 deletions(-) delete mode 100644 api/db/migrations/0000_new_lake.sql create mode 100644 api/db/migrations/0000_sudden_doctor_doom.sql delete mode 100644 api/db/migrations/0001_warm_lockheed.sql delete mode 100644 api/db/migrations/0002_mighty_may_parker.sql delete mode 100644 api/db/migrations/meta/0001_snapshot.json delete mode 100644 api/db/migrations/meta/0002_snapshot.json create mode 100644 api/src/contributor/repository.ts create mode 100644 api/src/contributor/table.ts create mode 100644 packages/models/src/contributor/index.ts delete mode 100644 packages/models/src/project/__snapshots__/index.spec.ts.snap diff --git a/api/db/migrations/0000_new_lake.sql b/api/db/migrations/0000_new_lake.sql deleted file mode 100644 index caf956a56..000000000 --- a/api/db/migrations/0000_new_lake.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TABLE `projects` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `name` text NOT NULL, - `slug` text NOT NULL, - `run_id` text DEFAULT 'initial-run-id' NOT NULL -); ---> statement-breakpoint -CREATE TABLE `repositories` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `provider` text NOT NULL, - `owner` text NOT NULL, - `name` text NOT NULL, - `run_id` text DEFAULT 'initial-run-id' NOT NULL, - `project_id` integer NOT NULL, - FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE UNIQUE INDEX `projects_slug_unique` ON `projects` (`slug`);--> statement-breakpoint -CREATE UNIQUE INDEX `repositories_provider_owner_name_unique` ON `repositories` (`provider`,`owner`,`name`); \ No newline at end of file diff --git a/api/db/migrations/0000_sudden_doctor_doom.sql b/api/db/migrations/0000_sudden_doctor_doom.sql new file mode 100644 index 000000000..9bc405bec --- /dev/null +++ b/api/db/migrations/0000_sudden_doctor_doom.sql @@ -0,0 +1,48 @@ +CREATE TABLE `contributions` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `title` text NOT NULL, + `updated_at` text NOT NULL, + `url` text NOT NULL, + `type` text NOT NULL, + `run_id` text NOT NULL, + `activity_count` integer NOT NULL, + `repository_id` integer NOT NULL, + `contributor_id` integer NOT NULL, + FOREIGN KEY (`repository_id`) REFERENCES `repositories`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`contributor_id`) REFERENCES `contributors`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `contributors` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL, + `name` text NOT NULL, + `username` text NOT NULL, + `url` text NOT NULL, + `avatar_url` text NOT NULL +); +--> statement-breakpoint +CREATE TABLE `projects` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL +); +--> statement-breakpoint +CREATE TABLE `repositories` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `provider` text NOT NULL, + `owner` text NOT NULL, + `name` text NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL, + `project_id` integer NOT NULL, + FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `contributions_url_unique` ON `contributions` (`url`);--> statement-breakpoint +CREATE UNIQUE INDEX `contributors_url_unique` ON `contributors` (`url`);--> statement-breakpoint +CREATE UNIQUE INDEX `projects_slug_unique` ON `projects` (`slug`);--> statement-breakpoint +CREATE UNIQUE INDEX `repositories_provider_owner_name_unique` ON `repositories` (`provider`,`owner`,`name`); \ No newline at end of file diff --git a/api/db/migrations/0001_warm_lockheed.sql b/api/db/migrations/0001_warm_lockheed.sql deleted file mode 100644 index 9df3ca887..000000000 --- a/api/db/migrations/0001_warm_lockheed.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE `contributions` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, - `name` text NOT NULL, - `updated_at` text NOT NULL, - `url` text NOT NULL, - `type` text NOT NULL, - `run_id` text NOT NULL, - `activity_count` integer NOT NULL, - `repository_id` integer NOT NULL, - FOREIGN KEY (`repository_id`) REFERENCES `repositories`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE UNIQUE INDEX `contributions_url_unique` ON `contributions` (`url`); \ No newline at end of file diff --git a/api/db/migrations/0002_mighty_may_parker.sql b/api/db/migrations/0002_mighty_may_parker.sql deleted file mode 100644 index 7095e1b0f..000000000 --- a/api/db/migrations/0002_mighty_may_parker.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `contributions` ADD `title` text NOT NULL;--> statement-breakpoint -ALTER TABLE `contributions` DROP COLUMN `name`; \ No newline at end of file diff --git a/api/db/migrations/meta/0000_snapshot.json b/api/db/migrations/meta/0000_snapshot.json index 743b04b73..04bb43a03 100644 --- a/api/db/migrations/meta/0000_snapshot.json +++ b/api/db/migrations/meta/0000_snapshot.json @@ -1,9 +1,180 @@ { "version": "6", "dialect": "sqlite", - "id": "ab1b512a-8ca8-44e1-b497-445465117250", + "id": "ba41012f-4495-42ff-ace1-61bd7eaef476", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { + "contributions": { + "name": "contributions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity_count": { + "name": "activity_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contributor_id": { + "name": "contributor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributions_url_unique": { + "name": "contributions_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": { + "contributions_repository_id_repositories_id_fk": { + "name": "contributions_repository_id_repositories_id_fk", + "tableFrom": "contributions", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "contributions_contributor_id_contributors_id_fk": { + "name": "contributions_contributor_id_contributors_id_fk", + "tableFrom": "contributions", + "tableTo": "contributors", + "columnsFrom": ["contributor_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "contributors": { + "name": "contributors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributors_url_unique": { + "name": "contributors_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "projects": { "name": "projects", "columns": { diff --git a/api/db/migrations/meta/0001_snapshot.json b/api/db/migrations/meta/0001_snapshot.json deleted file mode 100644 index 7930c5c7e..000000000 --- a/api/db/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "d333591a-11be-4b30-89f8-4da0aa5bd906", - "prevId": "ab1b512a-8ca8-44e1-b497-445465117250", - "tables": { - "contributions": { - "name": "contributions", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "activity_count": { - "name": "activity_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "repository_id": { - "name": "repository_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "contributions_url_unique": { - "name": "contributions_url_unique", - "columns": ["url"], - "isUnique": true - } - }, - "foreignKeys": { - "contributions_repository_id_repositories_id_fk": { - "name": "contributions_repository_id_repositories_id_fk", - "tableFrom": "contributions", - "tableTo": "repositories", - "columnsFrom": ["repository_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "projects": { - "name": "projects", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - } - }, - "indexes": { - "projects_slug_unique": { - "name": "projects_slug_unique", - "columns": ["slug"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "repositories": { - "name": "repositories", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "owner": { - "name": "owner", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - }, - "project_id": { - "name": "project_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "repositories_provider_owner_name_unique": { - "name": "repositories_provider_owner_name_unique", - "columns": ["provider", "owner", "name"], - "isUnique": true - } - }, - "foreignKeys": { - "repositories_project_id_projects_id_fk": { - "name": "repositories_project_id_projects_id_fk", - "tableFrom": "repositories", - "tableTo": "projects", - "columnsFrom": ["project_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} diff --git a/api/db/migrations/meta/0002_snapshot.json b/api/db/migrations/meta/0002_snapshot.json deleted file mode 100644 index 29b79923b..000000000 --- a/api/db/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "4c5b2a9b-5f9e-496c-a73e-e260a0513b86", - "prevId": "d333591a-11be-4b30-89f8-4da0aa5bd906", - "tables": { - "contributions": { - "name": "contributions", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "activity_count": { - "name": "activity_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "repository_id": { - "name": "repository_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "contributions_url_unique": { - "name": "contributions_url_unique", - "columns": ["url"], - "isUnique": true - } - }, - "foreignKeys": { - "contributions_repository_id_repositories_id_fk": { - "name": "contributions_repository_id_repositories_id_fk", - "tableFrom": "contributions", - "tableTo": "repositories", - "columnsFrom": ["repository_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "projects": { - "name": "projects", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - } - }, - "indexes": { - "projects_slug_unique": { - "name": "projects_slug_unique", - "columns": ["slug"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "repositories": { - "name": "repositories", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "record_imported_at": { - "name": "record_imported_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "CURRENT_TIMESTAMP" - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "owner": { - "name": "owner", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "run_id": { - "name": "run_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'initial-run-id'" - }, - "project_id": { - "name": "project_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "repositories_provider_owner_name_unique": { - "name": "repositories_provider_owner_name_unique", - "columns": ["provider", "owner", "name"], - "isUnique": true - } - }, - "foreignKeys": { - "repositories_project_id_projects_id_fk": { - "name": "repositories_project_id_projects_id_fk", - "tableFrom": "repositories", - "tableTo": "projects", - "columnsFrom": ["project_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index a3a5cb484..434d06b88 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -5,22 +5,8 @@ { "idx": 0, "version": "6", - "when": 1725462108723, - "tag": "0000_new_lake", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1725564569182, - "tag": "0001_warm_lockheed", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1725568393657, - "tag": "0002_mighty_may_parker", + "when": 1725654548149, + "tag": "0000_sudden_doctor_doom", "breakpoints": true } ] diff --git a/api/src/contribution/table.ts b/api/src/contribution/table.ts index 52f4d5ad9..03d715cfe 100644 --- a/api/src/contribution/table.ts +++ b/api/src/contribution/table.ts @@ -2,6 +2,7 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; import { sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { contributorsTable } from "src/contributor/table"; import { repositoriesTable } from "src/repository/table"; export const contributionsTable = sqliteTable("contributions", { @@ -18,6 +19,9 @@ export const contributionsTable = sqliteTable("contributions", { repositoryId: integer("repository_id") .notNull() .references(() => repositoriesTable.id), + contributorId: integer("contributor_id") + .notNull() + .references(() => contributorsTable.id), }); contributionsTable.$inferSelect satisfies Model; diff --git a/api/src/contributor/repository.ts b/api/src/contributor/repository.ts new file mode 100644 index 000000000..45ae3d382 --- /dev/null +++ b/api/src/contributor/repository.ts @@ -0,0 +1,27 @@ +import { ne } from "drizzle-orm"; +import { SQLiteService } from "src/sqlite/service"; +import { Service } from "typedi"; + +import { ContributorRow, contributorsTable } from "./table"; + +@Service() +export class ContributorRepository { + constructor(private readonly sqliteService: SQLiteService) {} + + public async upsert(contributor: ContributorRow) { + return await this.sqliteService.db + .insert(contributorsTable) + .values(contributor) + .onConflictDoUpdate({ + target: contributorsTable.url, + set: contributor, + }) + .returning({ id: contributorsTable.id }); + } + + public async deleteAllButWithRunId(runId: string) { + return await this.sqliteService.db + .delete(contributorsTable) + .where(ne(contributorsTable.runId, runId)); + } +} diff --git a/api/src/contributor/table.ts b/api/src/contributor/table.ts new file mode 100644 index 000000000..72be1d5e1 --- /dev/null +++ b/api/src/contributor/table.ts @@ -0,0 +1,20 @@ +import { Model } from "@dzcode.io/models/dist/_base"; +import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; +import { sql } from "drizzle-orm"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const contributorsTable = sqliteTable("contributors", { + id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + recordImportedAt: text("record_imported_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + runId: text("run_id").notNull().default("initial-run-id"), + name: text("name").notNull(), + username: text("username").notNull(), + url: text("url").notNull().unique(), + avatarUrl: text("avatar_url").notNull(), +}); + +contributorsTable.$inferSelect satisfies Model; + +export type ContributorRow = typeof contributorsTable.$inferInsert; diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 646dfea98..cda12c1c8 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -1,6 +1,7 @@ import { captureException, cron } from "@sentry/node"; import { CronJob } from "cron"; import { ContributionRepository } from "src/contribution/repository"; +import { ContributorRepository } from "src/contributor/repository"; import { DataService } from "src/data/service"; import { GithubService } from "src/github/service"; import { LoggerService } from "src/logger/service"; @@ -20,6 +21,7 @@ export class DigestCron { private readonly projectsRepository: ProjectRepository, private readonly repositoriesRepository: RepositoryRepository, private readonly contributionsRepository: ContributionRepository, + private readonly contributorsRepository: ContributorRepository, ) { const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron"); new SentryCronJob( @@ -64,6 +66,8 @@ export class DigestCron { const projectsFromDataFolder = await this.dataService.listProjects(); + // @TODO-ZM: add data with recordStatus="draft", delete, then update to recordStatus="ok" + // @TODO-ZM: in all repos, filter by recordStatus="ok" for (const project of projectsFromDataFolder) { const [{ id: projectId }] = await this.projectsRepository.upsert({ ...project, runId }); @@ -90,6 +94,15 @@ export class DigestCron { }); for (const issue of issues.issues) { + const githubUser = await this.githubService.getUser({ username: issue.user.login }); + const [{ id: contributorId }] = await this.contributorsRepository.upsert({ + name: githubUser.name || githubUser.login, + username: githubUser.login, + url: githubUser.html_url, + avatarUrl: githubUser.avatar_url, + runId, + }); + const type = issue.pull_request ? "PULL_REQUEST" : "ISSUE"; const [{ id: contributionId }] = await this.contributionsRepository.upsert({ title: issue.title, @@ -97,8 +110,9 @@ export class DigestCron { updatedAt: issue.updated_at, activityCount: issue.comments, runId, - repositoryId, url: type === "PULL_REQUEST" ? issue.pull_request.html_url : issue.html_url, + repositoryId, + contributorId, }); console.log("contributionId", contributionId); @@ -114,6 +128,7 @@ export class DigestCron { } } + await this.contributorsRepository.deleteAllButWithRunId(runId); await this.contributionsRepository.deleteAllButWithRunId(runId); await this.repositoriesRepository.deleteAllButWithRunId(runId); await this.projectsRepository.deleteAllButWithRunId(runId); diff --git a/packages/models/src/contributor/index.ts b/packages/models/src/contributor/index.ts new file mode 100644 index 000000000..08bce9e5b --- /dev/null +++ b/packages/models/src/contributor/index.ts @@ -0,0 +1,9 @@ +export interface ContributorEntity { + // @TODO-ZM: move this to BaseEntity + id: number; + runId: string; + name: string; + username: string; + url: string; + avatarUrl: string; +} diff --git a/packages/models/src/project/__snapshots__/index.spec.ts.snap b/packages/models/src/project/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 4cbb6de2b..000000000 --- a/packages/models/src/project/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,68 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -ProjectEntity { - "name": "Leblad", - "repositories": [ - RepositoryEntity { - "owner": "dzcode-io", - "provider": "github", - "repository": "leblad", - }, - RepositoryEntity { - "owner": "abderrahmaneMustapha", - "provider": "github", - "repository": "leblad-py", - }, - ], - "slug": "Leblad", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -ProjectEntity { - "name": "Leblad", - "repositories": [ - RepositoryEntity { - "owner": "dzcode-io", - "provider": "github", - "repository": "leblad", - }, - RepositoryEntity { - "owner": "abderrahmaneMustapha", - "provider": "github", - "repository": "leblad-py", - }, - ], - "slug": "Leblad", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "slug must be a string", - }, - "property": "slug", - "target": ProjectEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "name must be a string", - }, - "property": "name", - "target": ProjectEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `ProjectEntity {}`; From fbfc9a377d2ef2ce845e4fb2bffc37a306b1c89c Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sat, 7 Sep 2024 19:40:21 +0200 Subject: [PATCH 22/43] populate contribution with project --- api/src/_utils/case.ts | 21 +++++++++++---- api/src/contribution/repository.ts | 43 ++++++++++++++++++++++++++---- api/src/contribution/types.ts | 2 ++ api/src/digest/cron.ts | 9 +++++++ api/src/project/repository.ts | 6 ++++- web/src/pages/contribute/index.tsx | 13 +++++---- 6 files changed, 76 insertions(+), 18 deletions(-) diff --git a/api/src/_utils/case.ts b/api/src/_utils/case.ts index 9870d38ad..709c38e00 100644 --- a/api/src/_utils/case.ts +++ b/api/src/_utils/case.ts @@ -2,10 +2,21 @@ import camelCase from "lodash/camelCase"; import mapKeys from "lodash/mapKeys"; export function camelCaseObject>(obj: T): T { - const array = !Array.isArray(obj) ? [obj] : obj; - const camelCasedArray = array.map((item) => { - return mapKeys(item, (value, key) => camelCase(key)); - }); + if (typeof obj !== "object") { + return obj; + } - return (!Array.isArray(obj) ? camelCasedArray[0] : camelCasedArray) as T; + if (Array.isArray(obj)) { + return obj.map((item) => camelCaseObject(item)) as unknown as T; + } + + const mappedRootKeys = mapKeys(obj, (value, key) => camelCase(key)) as T; + + for (const key in mappedRootKeys) { + if (typeof mappedRootKeys[key] === "object") { + (mappedRootKeys[key] as unknown) = camelCaseObject(mappedRootKeys[key] as any); // eslint-disable-line @typescript-eslint/no-explicit-any + } + } + + return mappedRootKeys; } diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index a98e450bf..e2d171e93 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -2,6 +2,7 @@ import { ne, sql } from "drizzle-orm"; import { camelCaseObject } from "src/_utils/case"; import { reverseHierarchy } from "src/_utils/reverse-hierarchy"; import { unStringifyDeep } from "src/_utils/unstringify-deep"; +import { contributorsTable } from "src/contributor/table"; import { projectsTable } from "src/project/table"; import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; @@ -47,16 +48,44 @@ export class ContributionRepository { r.name as name, r.project_id as project_id, json_group_array( - json_object('id', c.id, 'title', c.title, 'type', c.type, 'url', c.url, 'updated_at', c.updated_at, 'activity_count', c.activity_count) + json_object( + 'id', + c.id, + 'title', + c.title, + 'type', + c.type, + 'url', + c.url, + 'updated_at', + c.updated_at, + 'activity_count', + c.activity_count, + 'contributor', + json_object( + 'id', + cr.id, + 'name', + cr.name, + 'username', + cr.username, + 'avatar_url', + cr.avatar_url + ) + ) ) AS contributions FROM ${contributionsTable} c - RIGHT JOIN - ${repositoriesTable} r ON c.id = r.project_id + INNER JOIN + ${repositoriesTable} r ON c.repository_id = r.id + INNER JOIN + ${contributorsTable} cr ON c.contributor_id = cr.id GROUP BY c.id) AS r - RIGHT JOIN + INNER JOIN ${projectsTable} p ON r.project_id = p.id + GROUP BY + p.id `; const raw = this.sqliteService.db.all(statement); @@ -69,6 +98,10 @@ export class ContributionRepository { const camelCased = camelCaseObject(reversed); - return camelCased; + const sortedUpdatedAt = camelCased.sort((a: any, b: any) => { + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }); + + return sortedUpdatedAt; } } diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index c6562ccc8..1061f8dd6 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -1,4 +1,5 @@ import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; +import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { Transform, TransformFnParams, Type } from "class-transformer"; @@ -35,6 +36,7 @@ export interface GetContributionsResponseDto extends GeneralResponseDto { repository: Pick & { project: Pick; }; + contributor: Pick; } >; } diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index cda12c1c8..39aa75f40 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -61,6 +61,8 @@ export class DigestCron { * Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId. */ private async run() { + if (Math.random() >= 0) return; + const runId = Math.random().toString(36).slice(2); this.logger.info({ message: `Digest cron started, runId: ${runId}` }); @@ -71,6 +73,7 @@ export class DigestCron { for (const project of projectsFromDataFolder) { const [{ id: projectId }] = await this.projectsRepository.upsert({ ...project, runId }); + let addedRepositoryCount = 0; try { const repositoriesFromDataFolder = project.repositories; for (const repository of repositoriesFromDataFolder) { @@ -87,6 +90,7 @@ export class DigestCron { runId, projectId, }); + addedRepositoryCount++; const issues = await this.githubService.listRepositoryIssues({ owner: repository.owner, @@ -126,6 +130,11 @@ export class DigestCron { // @TODO-ZM: capture error console.error(error); } + + if (addedRepositoryCount === 0) { + captureException(new Error("Empty project"), { extra: { project } }); + await this.projectsRepository.deleteById(projectId); + } } await this.contributorsRepository.deleteAllButWithRunId(runId); diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index 272ee40bd..7e0618502 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,5 +1,5 @@ import { ProjectEntity } from "@dzcode.io/models/dist/project"; -import { ne, sql } from "drizzle-orm"; +import { eq, ne, sql } from "drizzle-orm"; import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -51,6 +51,10 @@ export class ProjectRepository { .returning({ id: projectsTable.id }); } + public async deleteById(id: number) { + return await this.sqliteService.db.delete(projectsTable).where(eq(projectsTable.id, id)); + } + public async deleteAllButWithRunId(runId: string) { return await this.sqliteService.db.delete(projectsTable).where(ne(projectsTable.runId, runId)); } diff --git a/web/src/pages/contribute/index.tsx b/web/src/pages/contribute/index.tsx index ed7762943..abf8924aa 100644 --- a/web/src/pages/contribute/index.tsx +++ b/web/src/pages/contribute/index.tsx @@ -54,10 +54,9 @@ export default function Page(): JSX.Element {

- - {contribution.repository.project.name} - - + + {contribution.repository.project.name} + {contribution.repository.owner}/{contribution.repository.name} {/*
@@ -73,10 +72,10 @@ export default function Page(): JSX.Element { ))}
*/}
- {/* */} + src={contribution.contributor.avatarUrl} + />
{contribution.activityCount > 0 && (
From 79c2c842c770c03b11783fca653853a862f57196 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sat, 7 Sep 2024 19:59:30 +0200 Subject: [PATCH 23/43] projects endpoint tweaks --- api/src/_utils/case.ts | 2 +- api/src/_utils/unstringify-deep.ts | 8 ++++---- api/src/digest/cron.ts | 2 -- api/src/project/repository.ts | 18 ++++++------------ api/src/project/types.ts | 7 ++++++- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/api/src/_utils/case.ts b/api/src/_utils/case.ts index 709c38e00..212f42055 100644 --- a/api/src/_utils/case.ts +++ b/api/src/_utils/case.ts @@ -1,7 +1,7 @@ import camelCase from "lodash/camelCase"; import mapKeys from "lodash/mapKeys"; -export function camelCaseObject>(obj: T): T { +export function camelCaseObject(obj: T): T { if (typeof obj !== "object") { return obj; } diff --git a/api/src/_utils/unstringify-deep.ts b/api/src/_utils/unstringify-deep.ts index 61f5bbe36..8ac0490b0 100644 --- a/api/src/_utils/unstringify-deep.ts +++ b/api/src/_utils/unstringify-deep.ts @@ -3,10 +3,10 @@ * its children. */ -// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint -export function unStringifyDeep(obj: T): T { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function unStringifyDeep(obj: any): any { if (Array.isArray(obj)) { - return obj.map((item) => unStringifyDeep(item)) as T; + return obj.map((item) => unStringifyDeep(item)) as unknown as typeof obj; } if (typeof obj !== "object" || obj === null) { @@ -30,5 +30,5 @@ export function unStringifyDeep(obj: T): T { } } - return result as T; + return result as typeof obj; } diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 39aa75f40..037999264 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -61,8 +61,6 @@ export class DigestCron { * Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId. */ private async run() { - if (Math.random() >= 0) return; - const runId = Math.random().toString(36).slice(2); this.logger.info({ message: `Digest cron started, runId: ${runId}` }); diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index 7e0618502..3bff57ccd 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,5 +1,6 @@ -import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { eq, ne, sql } from "drizzle-orm"; +import { camelCaseObject } from "src/_utils/case"; +import { unStringifyDeep } from "src/_utils/unstringify-deep"; import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -27,17 +28,10 @@ export class ProjectRepository { GROUP BY p.id; `; - const raw = this.sqliteService.db.all(statement) as Array< - // the SQL query above returns a stringified JSON for the `repositories` column - Omit & { repositories: string } - >; - const projectsForList: ProjectEntity[] = raw.map((row) => { - const notYetValid = { ...row, repositories: JSON.parse(row.repositories) }; - - return notYetValid; - }); - - return projectsForList; + const raw = this.sqliteService.db.all(statement); + const unStringifiedRaw = unStringifyDeep(raw); + const camelCased = camelCaseObject(unStringifiedRaw); + return camelCased; } public async upsert(project: ProjectRow) { diff --git a/api/src/project/types.ts b/api/src/project/types.ts index cf2cc84fa..ccd5d703f 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -1,6 +1,11 @@ import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { GeneralResponseDto } from "src/app/types"; export interface GetProjectsResponseDto extends GeneralResponseDto { - projects: ProjectEntity[]; + projects: Array< + Pick & { + repositories: Array>; + } + >; } From cd4598fe3c5dfa294dc9c82ad85156a543eb9959 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sat, 7 Sep 2024 20:03:29 +0200 Subject: [PATCH 24/43] removed unused exports --- api/src/_utils/reverse-hierarchy.ts | 1 + api/src/contribution/repository.ts | 1 + api/src/contribution/types.ts | 51 ----------------------------- api/src/github/types.ts | 22 +------------ 4 files changed, 3 insertions(+), 72 deletions(-) diff --git a/api/src/_utils/reverse-hierarchy.ts b/api/src/_utils/reverse-hierarchy.ts index e1d08e0e8..eb9673e7d 100644 --- a/api/src/_utils/reverse-hierarchy.ts +++ b/api/src/_utils/reverse-hierarchy.ts @@ -4,6 +4,7 @@ export function reverseHierarchy( _obj: unknown, foreignKeyParentKeyRecord: ForeignKeyParentKeyRecord[], parentWithItsKey: Record = {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any { if (Array.isArray(_obj)) { return _obj diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index e2d171e93..5010475c7 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -98,6 +98,7 @@ export class ContributionRepository { const camelCased = camelCaseObject(reversed); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const sortedUpdatedAt = camelCased.sort((a: any, b: any) => { return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); }); diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index 1061f8dd6..bf80b2292 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -2,34 +2,8 @@ import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -import { Transform, TransformFnParams, Type } from "class-transformer"; -import { IsBoolean, IsIn, IsOptional, IsString, ValidateNested } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; -export class OptionDto { - @IsString() - @IsOptional() - label?: string; - - @IsString() - name!: string; - - @IsBoolean() - @IsOptional() - checked?: boolean; -} - -export const allFilterNames = ["projects", "languages", "labels"] as const; - -export class FilterDto { - @IsIn(allFilterNames) - name!: (typeof allFilterNames)[number]; - - @ValidateNested({ each: true }) - @Type(() => OptionDto) - options!: OptionDto[]; -} - export interface GetContributionsResponseDto extends GeneralResponseDto { contributions: Array< Pick & { @@ -40,28 +14,3 @@ export interface GetContributionsResponseDto extends GeneralResponseDto { } >; } - -const transformFilterOptions = ({ value }: TransformFnParams) => { - let filterOptions: string[] = []; - if (typeof value === "string" && value.length > 0) { - filterOptions = value.split(","); - } - if (Array.isArray(value)) { - filterOptions = value; - } - return filterOptions; -}; - -export class GetContributionsQueryDto { - @Transform(transformFilterOptions) - @Reflect.metadata("design:type", { name: "string" }) - projects: string[] = []; - - @Transform(transformFilterOptions) - @Reflect.metadata("design:type", { name: "string" }) - languages: string[] = []; - - @Transform(transformFilterOptions) - @Reflect.metadata("design:type", { name: "string" }) - labels: string[] = []; -} diff --git a/api/src/github/types.ts b/api/src/github/types.ts index 05f197077..d3e5fc77c 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -85,31 +85,11 @@ export interface GetRepositoryInput { repo: string; } -export interface GitHubListRepositoryIssuesInput { +interface GitHubListRepositoryIssuesInput { owner: string; repository: string; } -export interface GithubIssue { - html_url: string; - number: number; - title: string; - user: GithubUser; - body: string; - labels: Array<{ - name: string; - }>; - state: "closed" | "open"; - assignees: GithubUser[]; - comments: number; - created_at: string; - updated_at: string; - closed_at: string | null; - pull_request?: { - html_url: string; - }; -} - export type GitHubListRepositoryLanguagesInput = GitHubListRepositoryIssuesInput; export type GitHubListRepositoryMilestonesInput = GitHubListRepositoryIssuesInput; From 1406be132d6e26314bc843f08af385f4f0cd69db Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 12:13:08 +0200 Subject: [PATCH 25/43] model and create rel between contor and contion --- api/db/migrations/0001_black_eternals.sql | 10 + api/db/migrations/meta/0001_snapshot.json | 386 ++++++++++++++++++++++ api/db/migrations/meta/_journal.json | 7 + api/src/contribution/repository.ts | 4 +- api/src/contribution/types.ts | 1 + api/src/contributor/repository.ts | 105 +++++- api/src/contributor/table.ts | 29 +- api/src/contributor/types.ts | 17 + api/src/digest/cron.ts | 21 +- 9 files changed, 570 insertions(+), 10 deletions(-) create mode 100644 api/db/migrations/0001_black_eternals.sql create mode 100644 api/db/migrations/meta/0001_snapshot.json create mode 100644 api/src/contributor/types.ts diff --git a/api/db/migrations/0001_black_eternals.sql b/api/db/migrations/0001_black_eternals.sql new file mode 100644 index 000000000..c3109ac28 --- /dev/null +++ b/api/db/migrations/0001_black_eternals.sql @@ -0,0 +1,10 @@ +CREATE TABLE `contributor_repository_relation` ( + `contributor_id` integer NOT NULL, + `repository_id` integer NOT NULL, + `record_imported_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `run_id` text DEFAULT 'initial-run-id' NOT NULL, + `score` integer NOT NULL, + PRIMARY KEY(`contributor_id`, `repository_id`), + FOREIGN KEY (`contributor_id`) REFERENCES `contributors`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`repository_id`) REFERENCES `repositories`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/api/db/migrations/meta/0001_snapshot.json b/api/db/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..67f027972 --- /dev/null +++ b/api/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,386 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "d99b3c61-9212-4dde-814c-d4cbac5ea54d", + "prevId": "ba41012f-4495-42ff-ace1-61bd7eaef476", + "tables": { + "contributions": { + "name": "contributions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity_count": { + "name": "activity_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contributor_id": { + "name": "contributor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributions_url_unique": { + "name": "contributions_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": { + "contributions_repository_id_repositories_id_fk": { + "name": "contributions_repository_id_repositories_id_fk", + "tableFrom": "contributions", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "contributions_contributor_id_contributors_id_fk": { + "name": "contributions_contributor_id_contributors_id_fk", + "tableFrom": "contributions", + "tableTo": "contributors", + "columnsFrom": ["contributor_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "contributor_repository_relation": { + "name": "contributor_repository_relation", + "columns": { + "contributor_id": { + "name": "contributor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "contributor_repository_relation_contributor_id_contributors_id_fk": { + "name": "contributor_repository_relation_contributor_id_contributors_id_fk", + "tableFrom": "contributor_repository_relation", + "tableTo": "contributors", + "columnsFrom": ["contributor_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "contributor_repository_relation_repository_id_repositories_id_fk": { + "name": "contributor_repository_relation_repository_id_repositories_id_fk", + "tableFrom": "contributor_repository_relation", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "contributor_repository_relation_pk": { + "columns": ["contributor_id", "repository_id"], + "name": "contributor_repository_relation_pk" + } + }, + "uniqueConstraints": {} + }, + "contributors": { + "name": "contributors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contributors_url_unique": { + "name": "contributors_url_unique", + "columns": ["url"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + } + }, + "indexes": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "record_imported_at": { + "name": "record_imported_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'initial-run-id'" + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "repositories_provider_owner_name_unique": { + "name": "repositories_provider_owner_name_unique", + "columns": ["provider", "owner", "name"], + "isUnique": true + } + }, + "foreignKeys": { + "repositories_project_id_projects_id_fk": { + "name": "repositories_project_id_projects_id_fk", + "tableFrom": "repositories", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/api/db/migrations/meta/_journal.json b/api/db/migrations/meta/_journal.json index 434d06b88..7da2d65ac 100644 --- a/api/db/migrations/meta/_journal.json +++ b/api/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1725654548149, "tag": "0000_sudden_doctor_doom", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1725790243766, + "tag": "0001_black_eternals", + "breakpoints": true } ] } diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index 5010475c7..c0d7891c4 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -12,9 +12,7 @@ import { ContributionRow, contributionsTable } from "./table"; @Service() export class ContributionRepository { - constructor(private readonly sqliteService: SQLiteService) { - this.findForList(); - } + constructor(private readonly sqliteService: SQLiteService) {} public async upsert(contribution: ContributionRow) { return await this.sqliteService.db diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index bf80b2292..a2e3c5190 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -4,6 +4,7 @@ import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { GeneralResponseDto } from "src/app/types"; +// @TODO-ZM: remove "dto" from all interfaces export interface GetContributionsResponseDto extends GeneralResponseDto { contributions: Array< Pick & { diff --git a/api/src/contributor/repository.ts b/api/src/contributor/repository.ts index 45ae3d382..adebca4c3 100644 --- a/api/src/contributor/repository.ts +++ b/api/src/contributor/repository.ts @@ -1,13 +1,89 @@ -import { ne } from "drizzle-orm"; +import { ne, sql } from "drizzle-orm"; +import { camelCaseObject } from "src/_utils/case"; +import { unStringifyDeep } from "src/_utils/unstringify-deep"; +import { projectsTable } from "src/project/table"; +import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; -import { ContributorRow, contributorsTable } from "./table"; +import { + ContributorRepositoryRelationRow, + contributorRepositoryRelationTable, + ContributorRow, + contributorsTable, +} from "./table"; @Service() export class ContributorRepository { constructor(private readonly sqliteService: SQLiteService) {} + public async findForList() { + const statement = sql` + SELECT + sum(c.score) as score, + cr.id as id, + cr.name as name, + cr.username as username, + cr.url as url, + cr.avatar_url as avatar_url, + json_group_array( + json_object( + 'id', + p.id, + 'name', + p.name, + 'repositories', + c.repositories + ) + ) AS projects + FROM + (SELECT + sum(crr.score) as score, + crr.contributor_id as contributor_id, + crr.project_id as project_id, + json_group_array( + json_object( + 'id', + r.id, + 'owner', + r.owner, + 'name', + r.name + ) + ) AS repositories + FROM + (SELECT + contributor_id, + repository_id, + score, + r.project_id as project_id + FROM + ${contributorRepositoryRelationTable} crr + INNER JOIN + ${repositoriesTable} r ON crr.repository_id = r.id + ) as crr + INNER JOIN + ${repositoriesTable} r ON crr.repository_id = r.id + GROUP BY + crr.contributor_id, crr.project_id) as c + INNER JOIN + ${contributorsTable} cr ON c.contributor_id = cr.id + INNER JOIN + ${projectsTable} p ON c.project_id = p.id + GROUP BY + c.contributor_id + ORDER BY + score DESC + `; + + const raw = this.sqliteService.db.all(statement); + const unStringifiedRaw = unStringifyDeep(raw); + + const camelCased = camelCaseObject(unStringifiedRaw); + + return camelCased; + } + public async upsert(contributor: ContributorRow) { return await this.sqliteService.db .insert(contributorsTable) @@ -19,6 +95,31 @@ export class ContributorRepository { .returning({ id: contributorsTable.id }); } + public async upsertRelationWithRepository( + contributorRelationWithRepository: ContributorRepositoryRelationRow, + ) { + return await this.sqliteService.db + .insert(contributorRepositoryRelationTable) + .values(contributorRelationWithRepository) + .onConflictDoUpdate({ + target: [ + contributorRepositoryRelationTable.contributorId, + contributorRepositoryRelationTable.repositoryId, + ], + set: contributorRelationWithRepository, + }) + .returning({ + contributorId: contributorRepositoryRelationTable.contributorId, + repositoryId: contributorRepositoryRelationTable.repositoryId, + }); + } + + public async deleteAllRelationWithRepositoryButWithRunId(runId: string) { + return await this.sqliteService.db + .delete(contributorRepositoryRelationTable) + .where(ne(contributorRepositoryRelationTable.runId, runId)); + } + public async deleteAllButWithRunId(runId: string) { return await this.sqliteService.db .delete(contributorsTable) diff --git a/api/src/contributor/table.ts b/api/src/contributor/table.ts index 72be1d5e1..571200fde 100644 --- a/api/src/contributor/table.ts +++ b/api/src/contributor/table.ts @@ -1,7 +1,8 @@ import { Model } from "@dzcode.io/models/dist/_base"; import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { sql } from "drizzle-orm"; -import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { repositoriesTable } from "src/repository/table"; export const contributorsTable = sqliteTable("contributors", { id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), @@ -18,3 +19,29 @@ export const contributorsTable = sqliteTable("contributors", { contributorsTable.$inferSelect satisfies Model; export type ContributorRow = typeof contributorsTable.$inferInsert; + +export const contributorRepositoryRelationTable = sqliteTable( + "contributor_repository_relation", + { + contributorId: integer("contributor_id") + .notNull() + .references(() => contributorsTable.id), + repositoryId: integer("repository_id") + .notNull() + .references(() => repositoriesTable.id), + recordImportedAt: text("record_imported_at") + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + runId: text("run_id").notNull().default("initial-run-id"), + score: integer("score").notNull(), + }, + (table) => ({ + pk: primaryKey({ + name: "contributor_repository_relation_pk", + columns: [table.contributorId, table.repositoryId], + }), + }), +); + +export type ContributorRepositoryRelationRow = + typeof contributorRepositoryRelationTable.$inferInsert; diff --git a/api/src/contributor/types.ts b/api/src/contributor/types.ts new file mode 100644 index 000000000..1048b0f71 --- /dev/null +++ b/api/src/contributor/types.ts @@ -0,0 +1,17 @@ +import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; +import { ProjectEntity } from "@dzcode.io/models/dist/project"; +import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; +import { GeneralResponseDto } from "src/app/types"; + +export interface GetContributorsResponseDto extends GeneralResponseDto { + contributors: Array< + Pick & { + projects: Array< + Pick & { + repositories: Array>; + } + >; + score: number; + } + >; +} diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 037999264..0849b2ee7 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -105,6 +105,13 @@ export class DigestCron { runId, }); + await this.contributorsRepository.upsertRelationWithRepository({ + contributorId, + repositoryId, + runId, + score: 1, + }); + const type = issue.pull_request ? "PULL_REQUEST" : "ISSUE"; const [{ id: contributionId }] = await this.contributionsRepository.upsert({ title: issue.title, @@ -135,10 +142,16 @@ export class DigestCron { } } - await this.contributorsRepository.deleteAllButWithRunId(runId); - await this.contributionsRepository.deleteAllButWithRunId(runId); - await this.repositoriesRepository.deleteAllButWithRunId(runId); - await this.projectsRepository.deleteAllButWithRunId(runId); + try { + await this.contributorsRepository.deleteAllRelationWithRepositoryButWithRunId(runId); + await this.contributorsRepository.deleteAllButWithRunId(runId); + await this.contributionsRepository.deleteAllButWithRunId(runId); + await this.repositoriesRepository.deleteAllButWithRunId(runId); + await this.projectsRepository.deleteAllButWithRunId(runId); + } catch (error) { + // @TODO-ZM: capture error + console.error(error); + } this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); } From 5d88230b522e0b007975e595eae385b8754d5d75 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 12:34:01 +0200 Subject: [PATCH 26/43] contributors endpoint --- api/src/app/endpoints.ts | 7 +++---- api/src/app/index.ts | 2 ++ api/src/contributor/controller.ts | 20 ++++++++++++++++++++ web/src/pages/team/index.tsx | 19 +++++++++++-------- web/src/redux/actions/contributions.ts | 2 +- web/src/redux/actions/contributors.ts | 2 +- web/src/redux/slices/contributors-page.ts | 4 ++-- 7 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 api/src/contributor/controller.ts diff --git a/api/src/app/endpoints.ts b/api/src/app/endpoints.ts index 0cf5e8fa6..0ced0b68c 100644 --- a/api/src/app/endpoints.ts +++ b/api/src/app/endpoints.ts @@ -1,9 +1,9 @@ import { GetArticleResponseDto, GetArticlesResponseDto } from "src/article/types"; import { GetContributionsResponseDto } from "src/contribution/types"; +import { GetContributorsResponseDto } from "src/contributor/types"; import { GetADocumentationResponseDto, GetDocumentationResponseDto } from "src/documentation/types"; import { GetMilestonesResponseDto } from "src/milestone/types"; import { GetProjectsResponseDto } from "src/project/types"; -import { GetTeamResponseDto } from "src/team/types"; // ts-prune-ignore-next export interface Endpoints { @@ -26,10 +26,9 @@ export interface Endpoints { }; "api:Contributions": { response: GetContributionsResponseDto; - query: [string, string][]; }; - "api:Team": { - response: GetTeamResponseDto; + "api:Contributors": { + response: GetContributorsResponseDto; }; "api:MileStones/dzcode": { response: GetMilestonesResponseDto; diff --git a/api/src/app/index.ts b/api/src/app/index.ts index b3ae1f3d9..b829808cc 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -9,6 +9,7 @@ import { createExpressServer, RoutingControllersOptions, useContainer } from "ro import { ArticleController } from "src/article/controller"; import { ConfigService } from "src/config/service"; import { ContributionController } from "src/contribution/controller"; +import { ContributorController } from "src/contributor/controller"; import { DigestCron } from "src/digest/cron"; import { DocumentationController } from "src/documentation/controller"; import { GithubController } from "src/github/controller"; @@ -47,6 +48,7 @@ export const routingControllersOptions: RoutingControllersOptions = { ProjectController, ArticleController, DocumentationController, + ContributorController, ], middlewares: [ SecurityMiddleware, diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts new file mode 100644 index 000000000..60427a964 --- /dev/null +++ b/api/src/contributor/controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get } from "routing-controllers"; +import { Service } from "typedi"; + +import { ContributorRepository } from "./repository"; +import { GetContributorsResponseDto } from "./types"; + +@Service() +@Controller("/Contributors") +export class ContributorController { + constructor(private readonly contributorRepository: ContributorRepository) {} + + @Get("/") + public async getContributors(): Promise { + const contributors = await this.contributorRepository.findForList(); + + return { + contributors, + }; + } +} diff --git a/web/src/pages/team/index.tsx b/web/src/pages/team/index.tsx index 134a69bcf..cc962423b 100644 --- a/web/src/pages/team/index.tsx +++ b/web/src/pages/team/index.tsx @@ -51,15 +51,18 @@ export default function Page(): JSX.Element { className="rounded-full w-20 h-20" />

{contributor.name}

-
    - {contributor.repositories.map((repository, repositoryIndex) => ( -
  • - - {getRepositoryName(repository)} - -
  • +
    + {contributor.projects.map((project, projectIndex) => ( +
    + {project.name} + {project.repositories.map((repository, repositoryIndex) => ( + + {getRepositoryName(repository)} + + ))} +
    ))} -
+
))} diff --git a/web/src/redux/actions/contributions.ts b/web/src/redux/actions/contributions.ts index 642530343..cf9ae31f3 100644 --- a/web/src/redux/actions/contributions.ts +++ b/web/src/redux/actions/contributions.ts @@ -8,7 +8,7 @@ export const fetchContributionsListAction = (): ThunkAction => async (dispatch) => { try { dispatch(contributionsPageSlice.actions.set({ contributionsList: null })); - const { contributions } = await fetchV2("api:Contributions", { query: [] }); + const { contributions } = await fetchV2("api:Contributions", {}); dispatch(contributionsPageSlice.actions.set({ contributionsList: contributions })); } catch (error) { diff --git a/web/src/redux/actions/contributors.ts b/web/src/redux/actions/contributors.ts index 9224adac2..f3e050c53 100644 --- a/web/src/redux/actions/contributors.ts +++ b/web/src/redux/actions/contributors.ts @@ -8,7 +8,7 @@ export const fetchContributorsListAction = (): ThunkAction => async (dispatch) => { try { dispatch(contributorsPageSlice.actions.set({ contributorsList: null })); - const { contributors } = await fetchV2("api:Team", {}); + const { contributors } = await fetchV2("api:Contributors", {}); dispatch(contributorsPageSlice.actions.set({ contributorsList: contributors })); } catch (error) { dispatch(contributorsPageSlice.actions.set({ contributorsList: "ERROR" })); diff --git a/web/src/redux/slices/contributors-page.ts b/web/src/redux/slices/contributors-page.ts index 47150399a..554642480 100644 --- a/web/src/redux/slices/contributors-page.ts +++ b/web/src/redux/slices/contributors-page.ts @@ -1,11 +1,11 @@ -import { GetTeamResponseDto } from "@dzcode.io/api/dist/team/types"; +import { GetContributorsResponseDto } from "@dzcode.io/api/dist/contributor/types"; import { createSlice } from "@reduxjs/toolkit"; import { setReducerFactory } from "src/redux/utils"; import { Loadable } from "src/utils/loadable"; // ts-prune-ignore-next export interface ContributorsPageState { - contributorsList: Loadable; + contributorsList: Loadable; } const initialState: ContributorsPageState = { From f85dad98bbec97e397be1ab0dfdb373b258ff3f5 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 13:31:30 +0200 Subject: [PATCH 27/43] list all contributors --- api/src/contributor/repository.ts | 13 ++++++-- api/src/github/service.ts | 11 +++---- api/src/github/types.ts | 55 ++----------------------------- web/src/pages/team/index.tsx | 2 +- 4 files changed, 20 insertions(+), 61 deletions(-) diff --git a/api/src/contributor/repository.ts b/api/src/contributor/repository.ts index adebca4c3..2c010633f 100644 --- a/api/src/contributor/repository.ts +++ b/api/src/contributor/repository.ts @@ -32,6 +32,8 @@ export class ContributorRepository { p.id, 'name', p.name, + 'score', + c.score, 'repositories', c.repositories ) @@ -48,7 +50,9 @@ export class ContributorRepository { 'owner', r.owner, 'name', - r.name + r.name, + 'score', + crr.score ) ) AS repositories FROM @@ -61,11 +65,16 @@ export class ContributorRepository { ${contributorRepositoryRelationTable} crr INNER JOIN ${repositoriesTable} r ON crr.repository_id = r.id + ORDER BY + crr.score DESC ) as crr INNER JOIN ${repositoriesTable} r ON crr.repository_id = r.id GROUP BY - crr.contributor_id, crr.project_id) as c + crr.contributor_id, crr.project_id + ORDER BY + crr.score DESC + ) as c INNER JOIN ${contributorsTable} cr ON c.contributor_id = cr.id INNER JOIN diff --git a/api/src/github/service.ts b/api/src/github/service.ts index 45b8fb8cb..fbaf173a2 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -45,6 +45,8 @@ export class GithubService { const contributors = commits // @TODO-ZM: dry to a user block-list // excluding github.com/web-flow user + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore .filter((item) => item.committer && item.committer.id !== 19864447) .map(({ committer }) => committer); return contributors; @@ -116,12 +118,7 @@ export class GithubService { // @TODO-ZM: validate responses using DTOs, for all fetchService methods if (!Array.isArray(contributors)) return []; - return ( - contributors - // @TODO-ZM: filter out bots - .filter(({ type }) => type === "User") - .sort((a, b) => b.contributions - a.contributions) - ); + return contributors; }; public getRateLimit = async (): Promise<{ limit: number; used: number; ratio: number }> => { @@ -152,6 +149,8 @@ export class GithubService { }; public githubUserToAccountEntity = ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore user: Pick, ): Model => ({ id: `github/${user.id}`, diff --git a/api/src/github/types.ts b/api/src/github/types.ts index d3e5fc77c..ba19eaea6 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -3,61 +3,12 @@ import { GeneralResponseDto } from "src/app/types"; export interface GithubUser { login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; name: string; - company: string; - blog: string; - location: string; - email: string; - hireable: boolean; - bio: string; - twitter_username: string; - public_repos: number; - public_gists: number; - followers: number; - following: number; - created_at: string; - updated_at: string; + html_url: string; + avatar_url: string; } -export interface GithubRepositoryContributor - extends Pick< - GithubUser, - | "login" - | "id" - | "node_id" - | "avatar_url" - | "gravatar_id" - | "url" - | "html_url" - | "followers_url" - | "following_url" - | "gists_url" - | "starred_url" - | "subscriptions_url" - | "organizations_url" - | "repos_url" - | "events_url" - | "received_events_url" - | "type" - | "site_admin" - > { +export interface GithubRepositoryContributor extends GithubUser { contributions: number; } diff --git a/web/src/pages/team/index.tsx b/web/src/pages/team/index.tsx index cc962423b..df339866d 100644 --- a/web/src/pages/team/index.tsx +++ b/web/src/pages/team/index.tsx @@ -51,7 +51,7 @@ export default function Page(): JSX.Element { className="rounded-full w-20 h-20" />

{contributor.name}

-
+
{contributor.projects.map((project, projectIndex) => (
{project.name} From 56a63e0ce09e2a7fb600ad1d996d11095dbf21f2 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 13:53:25 +0200 Subject: [PATCH 28/43] list all contributors that are actual users --- api/src/_test/mocks.ts | 31 ++--------------------------- api/src/article/controller.ts | 5 +++++ api/src/digest/cron.ts | 29 +++++++++++++++++++++++++++ api/src/documentation/controller.ts | 4 ++++ api/src/github/types.ts | 1 + api/src/team/repository.ts | 4 ++++ 6 files changed, 45 insertions(+), 29 deletions(-) diff --git a/api/src/_test/mocks.ts b/api/src/_test/mocks.ts index 34c50b2a5..a96d5fb81 100644 --- a/api/src/_test/mocks.ts +++ b/api/src/_test/mocks.ts @@ -3,35 +3,8 @@ import { GithubUser } from "src/github/types"; export const githubUserMock: GithubUser = { login: "ZibanPirate", - id: 20110076, - node_id: "MDQ6VXNlcjIwMTEwMDc2", - avatar_url: "https://avatars.githubusercontent.com/u/20110076?v=4", - gravatar_id: "", - url: "https://api.github.com/users/ZibanPirate", html_url: "https://github.com/ZibanPirate", - followers_url: "https://api.github.com/users/ZibanPirate/followers", - following_url: "https://api.github.com/users/ZibanPirate/following{/other_user}", - gists_url: "https://api.github.com/users/ZibanPirate/gists{/gist_id}", - starred_url: "https://api.github.com/users/ZibanPirate/starred{/owner}{/repo}", - subscriptions_url: "https://api.github.com/users/ZibanPirate/subscriptions", - organizations_url: "https://api.github.com/users/ZibanPirate/orgs", - repos_url: "https://api.github.com/users/ZibanPirate/repos", - events_url: "https://api.github.com/users/ZibanPirate/events{/privacy}", - received_events_url: "https://api.github.com/users/ZibanPirate/received_events", - type: "User", - site_admin: false, + avatar_url: "https://avatars.githubusercontent.com/u/20110076?v=4", name: "Zakaria Mansouri", - company: "@dzcode-io @avimedical", - blog: "zak.dzcode.io", - location: "Algeria", - email: "", - hireable: true, - bio: "One-man-army lone programmer", - twitter_username: "ZibanPirate", - public_repos: 18, - public_gists: 2, - followers: 130, - following: 92, - created_at: "2016-06-23T12:41:14Z", - updated_at: "2023-04-10T21:31:26Z", + type: "User", }; diff --git a/api/src/article/controller.ts b/api/src/article/controller.ts index 295f269fe..77b94bcb0 100644 --- a/api/src/article/controller.ts +++ b/api/src/article/controller.ts @@ -7,6 +7,7 @@ import { Service } from "typedi"; import { GetArticleResponseDto, GetArticlesResponseDto } from "./types"; +// @TODO-ZM: remove article and learn controllers @Service() @Controller("/Articles") export class ArticleController { @@ -42,6 +43,8 @@ export class ArticleController { const authors = await Promise.all( article.authors.map(async (author) => { const githubUser = await this.githubService.getUser({ username: author }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return this.githubService.githubUserToAccountEntity(githubUser); }), ); @@ -77,6 +80,8 @@ export class ArticleController { } }, []) .sort((a, b) => uniqUsernames[b.login] - uniqUsernames[a.login]) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore .map((committer) => this.githubService.githubUserToAccountEntity(committer)) .filter(({ id }) => !authors.find((author) => author.id === id)); diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 0849b2ee7..754bda438 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -97,6 +97,9 @@ export class DigestCron { for (const issue of issues.issues) { const githubUser = await this.githubService.getUser({ username: issue.user.login }); + + if (githubUser.type !== "User") continue; + const [{ id: contributorId }] = await this.contributorsRepository.upsert({ name: githubUser.name || githubUser.login, username: githubUser.login, @@ -126,6 +129,32 @@ export class DigestCron { console.log("contributionId", contributionId); } + + const repoContributors = await this.githubService.listRepositoryContributors({ + owner: repository.owner, + repository: repository.name, + }); + + const repoContributorsFiltered = repoContributors.filter( + (contributor) => contributor.type === "User", + ); + + for (const repoContributor of repoContributorsFiltered) { + const [{ id: contributorId }] = await this.contributorsRepository.upsert({ + name: repoContributor.name || repoContributor.login, + username: repoContributor.login, + url: repoContributor.html_url, + avatarUrl: repoContributor.avatar_url, + runId, + }); + + await this.contributorsRepository.upsertRelationWithRepository({ + contributorId, + repositoryId, + runId, + score: repoContributor.contributions, + }); + } } catch (error) { // @TODO-ZM: capture error console.error(error); diff --git a/api/src/documentation/controller.ts b/api/src/documentation/controller.ts index 06fcf3708..51f823396 100644 --- a/api/src/documentation/controller.ts +++ b/api/src/documentation/controller.ts @@ -44,6 +44,8 @@ export class DocumentationController { const authors = await Promise.all( documentation.authors.map(async (author) => { const githubUser = await this.githubService.getUser({ username: author }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return this.githubService.githubUserToAccountEntity(githubUser); }), ); @@ -79,6 +81,8 @@ export class DocumentationController { } }, []) .sort((a, b) => uniqUsernames[b.login] - uniqUsernames[a.login]) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore .map((contributor) => this.githubService.githubUserToAccountEntity(contributor)) .filter(({ id }) => !authors.find((author) => author.id === id)); diff --git a/api/src/github/types.ts b/api/src/github/types.ts index ba19eaea6..be2a28076 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -6,6 +6,7 @@ export interface GithubUser { name: string; html_url: string; avatar_url: string; + type: "User" | "_other"; } export interface GithubRepositoryContributor extends GithubUser { diff --git a/api/src/team/repository.ts b/api/src/team/repository.ts index c72388575..df250abf0 100644 --- a/api/src/team/repository.ts +++ b/api/src/team/repository.ts @@ -43,6 +43,8 @@ export class TeamRepository { repository: name, }); contributors.forEach((contributor) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const uuid = this.githubService.githubUserToAccountEntity({ ...contributor, name: "", @@ -89,6 +91,8 @@ export class TeamRepository { .map(async (uuid) => { const { repositories, login } = contributorsUsernameRankedRecord[uuid]; const githubUser = await this.githubService.getUser({ username: login }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const account = this.githubService.githubUserToAccountEntity(githubUser); return { ...account, repositories }; From 3f7d8e5ba6f0d8de2bba8c5d063686199f72e50d Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 14:59:54 +0200 Subject: [PATCH 29/43] updated snapshot tests --- .../project-reference/__snapshots__/index.spec.ts.snap | 8 ++++---- .../repository-reference/__snapshots__/index.spec.ts.snap | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap b/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap index 42c998511..7f3643bbd 100644 --- a/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap +++ b/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap @@ -7,14 +7,14 @@ ProjectReferenceEntity { "name": "Leblad", "repositories": [ RepositoryReferenceEntity { + "name": "leblad", "owner": "dzcode-io", "provider": "github", - "repository": "leblad", }, RepositoryReferenceEntity { + "name": "leblad-py", "owner": "abderrahmaneMustapha", "provider": "github", - "repository": "leblad-py", }, ], "slug": "Leblad", @@ -28,14 +28,14 @@ ProjectReferenceEntity { "name": "Leblad", "repositories": [ RepositoryReferenceEntity { + "name": "leblad", "owner": "dzcode-io", "provider": "github", - "repository": "leblad", }, RepositoryReferenceEntity { + "name": "leblad-py", "owner": "abderrahmaneMustapha", "provider": "github", - "repository": "leblad-py", }, ], "slug": "Leblad", diff --git a/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap b/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap index c9192cf93..15c39ca2f 100644 --- a/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap +++ b/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap @@ -6,9 +6,9 @@ exports[`should match snapshot when providing all fields: output 1`] = ` RepositoryReferenceEntity { "contributions": [], "contributors": [], + "name": "leblad", "owner": "dzcode-io", "provider": "github", - "repository": "leblad", } `; @@ -16,9 +16,9 @@ exports[`should match snapshot when providing required fields only: errors 1`] = exports[`should match snapshot when providing required fields only: output 1`] = ` RepositoryReferenceEntity { + "name": "leblad", "owner": "dzcode-io", "provider": "github", - "repository": "leblad", } `; @@ -45,9 +45,9 @@ exports[`should show an error that matches snapshot when passing empty object: e ValidationError { "children": [], "constraints": { - "isString": "repository must be a string", + "isString": "name must be a string", }, - "property": "repository", + "property": "name", "target": RepositoryReferenceEntity {}, "value": undefined, }, From 7ca03fb720b8ef58b5ee8cc472dd4601aa1c8cdd Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 15:12:14 +0200 Subject: [PATCH 30/43] remove old endpoints --- api/src/app/endpoints.ts | 16 ----- api/src/app/index.ts | 6 -- api/src/article/controller.ts | 96 ------------------------- api/src/article/types.ts | 16 ----- api/src/digest/cron.ts | 9 ++- api/src/documentation/controller.ts | 97 -------------------------- api/src/documentation/types.ts | 16 ----- api/src/github/types.ts | 2 +- api/src/team/controller.ts | 23 ------ api/src/team/repository.ts | 104 ---------------------------- api/src/team/types.ts | 11 --- 11 files changed, 9 insertions(+), 387 deletions(-) delete mode 100644 api/src/article/controller.ts delete mode 100644 api/src/article/types.ts delete mode 100644 api/src/documentation/controller.ts delete mode 100644 api/src/documentation/types.ts delete mode 100644 api/src/team/controller.ts delete mode 100644 api/src/team/repository.ts delete mode 100644 api/src/team/types.ts diff --git a/api/src/app/endpoints.ts b/api/src/app/endpoints.ts index 0ced0b68c..e2d1c6572 100644 --- a/api/src/app/endpoints.ts +++ b/api/src/app/endpoints.ts @@ -1,26 +1,10 @@ -import { GetArticleResponseDto, GetArticlesResponseDto } from "src/article/types"; import { GetContributionsResponseDto } from "src/contribution/types"; import { GetContributorsResponseDto } from "src/contributor/types"; -import { GetADocumentationResponseDto, GetDocumentationResponseDto } from "src/documentation/types"; import { GetMilestonesResponseDto } from "src/milestone/types"; import { GetProjectsResponseDto } from "src/project/types"; // ts-prune-ignore-next export interface Endpoints { - "api:Articles": { - response: GetArticlesResponseDto; - }; - "api:Articles/:slug": { - response: GetArticleResponseDto; - params: { slug: string }; - }; - "api:Documentation": { - response: GetDocumentationResponseDto; - }; - "api:Documentation/:slug": { - response: GetADocumentationResponseDto; - params: { slug: string }; - }; "api:Projects": { response: GetProjectsResponseDto; }; diff --git a/api/src/app/index.ts b/api/src/app/index.ts index b829808cc..a98ad9403 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -6,18 +6,15 @@ import { fsConfig } from "@dzcode.io/utils/dist/config"; import * as Sentry from "@sentry/node"; import { Application } from "express"; import { createExpressServer, RoutingControllersOptions, useContainer } from "routing-controllers"; -import { ArticleController } from "src/article/controller"; import { ConfigService } from "src/config/service"; import { ContributionController } from "src/contribution/controller"; import { ContributorController } from "src/contributor/controller"; import { DigestCron } from "src/digest/cron"; -import { DocumentationController } from "src/documentation/controller"; import { GithubController } from "src/github/controller"; import { LoggerService } from "src/logger/service"; import { MilestoneController } from "src/milestone/controller"; import { ProjectController } from "src/project/controller"; import { SQLiteService } from "src/sqlite/service"; -import { TeamController } from "src/team/controller"; import Container from "typedi"; import { DocsMiddleware } from "./middlewares/docs"; @@ -42,12 +39,9 @@ CronServices.forEach((service) => Container.get(service)); export const routingControllersOptions: RoutingControllersOptions = { controllers: [ ContributionController, - TeamController, GithubController, MilestoneController, ProjectController, - ArticleController, - DocumentationController, ContributorController, ], middlewares: [ diff --git a/api/src/article/controller.ts b/api/src/article/controller.ts deleted file mode 100644 index 77b94bcb0..000000000 --- a/api/src/article/controller.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Controller, Get, Param } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; -import { DataService } from "src/data/service"; -import { GithubService } from "src/github/service"; -import { GithubUser } from "src/github/types"; -import { Service } from "typedi"; - -import { GetArticleResponseDto, GetArticlesResponseDto } from "./types"; - -// @TODO-ZM: remove article and learn controllers -@Service() -@Controller("/Articles") -export class ArticleController { - constructor( - private readonly githubService: GithubService, - private readonly dataService: DataService, - ) {} - - @Get("/") - @OpenAPI({ - summary: "Return list of all articles", - }) - @ResponseSchema(GetArticlesResponseDto) - public async getArticles(): Promise { - // get articles from /data folder: - const articles = await this.dataService.listArticles(); - - return { - articles, - }; - } - - @Get("/:slug(*)") - @OpenAPI({ - summary: "Return info about a single article", - }) - @ResponseSchema(GetArticleResponseDto) - public async getArticle(@Param("slug") slug: string): Promise { - // get articles from /data folder: - const { ...article } = await this.dataService.getArticle(slug); - - // get authors and contributors info from github: - const authors = await Promise.all( - article.authors.map(async (author) => { - const githubUser = await this.githubService.getUser({ username: author }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.githubService.githubUserToAccountEntity(githubUser); - }), - ); - - const committersBatches = await Promise.all([ - // current place for data: - this.githubService.listPathCommitters({ - owner: "dzcode-io", - repository: "dzcode.io", - path: `data/models/articles/${slug}`, - }), - // also check old place for data, to not lose contribution effort: - this.githubService.listPathCommitters({ - owner: "dzcode-io", - repository: "dzcode.io", - path: `data/articles/${slug}`, - }), - ]); - - // filter and sort contributors: - const uniqUsernames: Record = {}; - const contributors: GetArticleResponseDto["article"]["contributors"] = [ - ...committersBatches[0], - ...committersBatches[1], - ] - .reduce((pV, cV) => { - if (uniqUsernames[cV.login]) { - uniqUsernames[cV.login]++; - return pV; - } else { - uniqUsernames[cV.login] = 1; - return [...pV, cV]; - } - }, []) - .sort((a, b) => uniqUsernames[b.login] - uniqUsernames[a.login]) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .map((committer) => this.githubService.githubUserToAccountEntity(committer)) - .filter(({ id }) => !authors.find((author) => author.id === id)); - - return { - article: { - ...article, - authors, - contributors, - }, - }; - } -} diff --git a/api/src/article/types.ts b/api/src/article/types.ts deleted file mode 100644 index 753535c20..000000000 --- a/api/src/article/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ArticleEntity, ArticleInfoEntity } from "@dzcode.io/models/dist/article"; -import { Type } from "class-transformer"; -import { ValidateNested } from "class-validator"; -import { GeneralResponseDto } from "src/app/types"; - -export class GetArticlesResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => ArticleInfoEntity) - articles!: Model[]; -} - -export class GetArticleResponseDto extends GeneralResponseDto { - @ValidateNested() - article!: Model; -} diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 754bda438..2db4d9fee 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -61,10 +61,17 @@ export class DigestCron { * Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId. */ private async run() { + // @TODO-ZM: remove it + if (Math.random() >= 0) return; + const runId = Math.random().toString(36).slice(2); this.logger.info({ message: `Digest cron started, runId: ${runId}` }); - const projectsFromDataFolder = await this.dataService.listProjects(); + const projectsFromDataFolder = (await this.dataService.listProjects()).filter( + // (project, index) => index < 5, + () => true, + ); + // // @TODO-ZM: add data with recordStatus="draft", delete, then update to recordStatus="ok" // @TODO-ZM: in all repos, filter by recordStatus="ok" diff --git a/api/src/documentation/controller.ts b/api/src/documentation/controller.ts deleted file mode 100644 index 51f823396..000000000 --- a/api/src/documentation/controller.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Controller, Get, Param } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; -import { DataService } from "src/data/service"; -import { GithubService } from "src/github/service"; -import { GithubUser } from "src/github/types"; -import { Service } from "typedi"; - -import { GetADocumentationResponseDto, GetDocumentationResponseDto } from "./types"; - -@Service() -@Controller("/Documentation") -export class DocumentationController { - constructor( - private readonly githubService: GithubService, - private readonly dataService: DataService, - ) {} - - @Get("/") - @OpenAPI({ - summary: "Return list of all documentation", - }) - @ResponseSchema(GetDocumentationResponseDto) - public async getDocumentation(): Promise { - // get documentation from /data folder: - const documentation = await this.dataService.listDocumentation(); - - return { - documentation, - }; - } - - @Get("/:slug(*)") - @OpenAPI({ - summary: "Return info about a single documentation", - }) - @ResponseSchema(GetADocumentationResponseDto) - public async getADocumentation( - @Param("slug") slug: string, - ): Promise { - // get documentation from /data folder: - const { ...documentation } = await this.dataService.getDocumentation(slug); - - // get authors and contributors info from github: - const authors = await Promise.all( - documentation.authors.map(async (author) => { - const githubUser = await this.githubService.getUser({ username: author }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.githubService.githubUserToAccountEntity(githubUser); - }), - ); - - const committersBatches = await Promise.all([ - // current place for data: - this.githubService.listPathCommitters({ - owner: "dzcode-io", - repository: "dzcode.io", - path: `data/models/documentation/${slug}`, - }), - // also check old place for data, to not lose contribution effort: - this.githubService.listPathCommitters({ - owner: "dzcode-io", - repository: "dzcode.io", - path: `data/documentation/${slug}`, - }), - ]); - - // filter and sort contributors: - const uniqUsernames: Record = {}; - const contributors: GetADocumentationResponseDto["documentation"]["contributors"] = [ - ...committersBatches[0], - ...committersBatches[1], - ] - .reduce((pV, cV) => { - if (uniqUsernames[cV.login]) { - uniqUsernames[cV.login]++; - return pV; - } else { - uniqUsernames[cV.login] = 1; - return [...pV, cV]; - } - }, []) - .sort((a, b) => uniqUsernames[b.login] - uniqUsernames[a.login]) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .map((contributor) => this.githubService.githubUserToAccountEntity(contributor)) - .filter(({ id }) => !authors.find((author) => author.id === id)); - - return { - documentation: { - ...documentation, - authors, - contributors, - }, - }; - } -} diff --git a/api/src/documentation/types.ts b/api/src/documentation/types.ts deleted file mode 100644 index 951f323c6..000000000 --- a/api/src/documentation/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { DocumentationEntity, DocumentationInfoEntity } from "@dzcode.io/models/dist/documentation"; -import { Type } from "class-transformer"; -import { ValidateNested } from "class-validator"; -import { GeneralResponseDto } from "src/app/types"; - -export class GetDocumentationResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => DocumentationInfoEntity) - documentation!: Model[]; -} - -export class GetADocumentationResponseDto extends GeneralResponseDto { - @ValidateNested() - documentation!: Model; -} diff --git a/api/src/github/types.ts b/api/src/github/types.ts index be2a28076..4a14bcc0d 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -9,7 +9,7 @@ export interface GithubUser { type: "User" | "_other"; } -export interface GithubRepositoryContributor extends GithubUser { +interface GithubRepositoryContributor extends GithubUser { contributions: number; } diff --git a/api/src/team/controller.ts b/api/src/team/controller.ts deleted file mode 100644 index ae75d171d..000000000 --- a/api/src/team/controller.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Controller, Get } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; -import { Service } from "typedi"; - -import { TeamRepository } from "./repository"; -import { GetTeamResponseDto } from "./types"; - -@Service() -@Controller("/Team") -export class TeamController { - constructor(private readonly teamRepository: TeamRepository) {} - - @Get("/") - @OpenAPI({ summary: "Return a list of contributors for all listed projects in dzcode.io" }) - @ResponseSchema(GetTeamResponseDto) - public async getContributions(): Promise { - const contributors = await this.teamRepository.find(); - - return { - contributors, - }; - } -} diff --git a/api/src/team/repository.ts b/api/src/team/repository.ts deleted file mode 100644 index df250abf0..000000000 --- a/api/src/team/repository.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { DataService } from "src/data/service"; -import { DataProjectEntity } from "src/data/types"; -import { GithubService } from "src/github/service"; -import { GithubRepositoryContributor } from "src/github/types"; -import { LoggerService } from "src/logger/service"; -import { Service } from "typedi"; - -@Service() -export class TeamRepository { - constructor( - private readonly githubService: GithubService, - private readonly dataService: DataService, - private readonly loggerService: LoggerService, - ) {} - - public async find(): Promise[]> { - const projects = await this.dataService.listProjects(); - - // flatten repositories into one array - const repositories = projects.reduce( - (repositories, project) => [...repositories, ...project.repositories], - [], - ); - - // we first store them in a Record (object with id as keys) so we can uniquify and rank them - const contributorsUsernameRankedRecord: Record< - string, - Pick< - GithubRepositoryContributor & Model, - "login" | "contributions" | "repositories" - > - > = {}; - - // get contributors from all the repos we have - await Promise.all( - repositories.map(async ({ provider, owner, name }) => { - if (provider === "github") { - try { - const contributors = await this.githubService.listRepositoryContributors({ - owner, - repository: name, - }); - contributors.forEach((contributor) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const uuid = this.githubService.githubUserToAccountEntity({ - ...contributor, - name: "", - }).id; - const { login, contributions } = contributor; - // contributor first time appearing in the list - if (!contributorsUsernameRankedRecord[uuid]) { - contributorsUsernameRankedRecord[uuid] = { - login, - contributions, - repositories: [{ provider, owner, name }], - }; - } else { - // contributor already exists in the list, and is a contributor to another repository - // - so we count additional contributions: - contributorsUsernameRankedRecord[uuid].contributions += contributor.contributions; - // - and add the other repository to the list of repositories he contributed to - contributorsUsernameRankedRecord[uuid].repositories.push({ - provider, - owner, - name, - }); - } - }); - } catch (error) { - this.loggerService.warn({ - message: `Failed to fetch contributors for ${owner}/${name}: ${error}`, - meta: { owner, name }, - }); - } - } else throw new Error(`Provider ${provider} is not supported yet`); - }), - ); - - const contributors: Model[] = await Promise.all( - Object.keys(contributorsUsernameRankedRecord) - // sort contributors by their commits count - .sort( - (a, b) => - contributorsUsernameRankedRecord[b].contributions - - contributorsUsernameRankedRecord[a].contributions, - ) - // get the github user data for each contributor - .map(async (uuid) => { - const { repositories, login } = contributorsUsernameRankedRecord[uuid]; - const githubUser = await this.githubService.getUser({ username: login }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const account = this.githubService.githubUserToAccountEntity(githubUser); - - return { ...account, repositories }; - }), - ); - - return contributors; - } -} diff --git a/api/src/team/types.ts b/api/src/team/types.ts deleted file mode 100644 index 96cf59212..000000000 --- a/api/src/team/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { Type } from "class-transformer"; -import { ValidateNested } from "class-validator"; -import { GeneralResponseDto } from "src/app/types"; - -export class GetTeamResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - contributors!: Model[]; -} From b9360976aa764075fe5ecc79cb1f95063feb8ad9 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 15:34:12 +0200 Subject: [PATCH 31/43] removed unused code --- api/src/_test/mocks.ts | 10 --- .../validator/is-map-of-string-number.ts | 22 ------ .../_utils/validator/validate-plain-object.ts | 32 --------- api/src/digest/cron.ts | 11 +-- api/src/fetch/service.ts | 15 ++-- api/src/github/dto.ts | 70 ++++--------------- api/src/github/service.spec.ts | 52 -------------- api/src/github/service.ts | 68 ++---------------- api/src/github/types.ts | 7 -- 9 files changed, 25 insertions(+), 262 deletions(-) delete mode 100644 api/src/_test/mocks.ts delete mode 100644 api/src/_utils/validator/is-map-of-string-number.ts delete mode 100644 api/src/_utils/validator/validate-plain-object.ts delete mode 100644 api/src/github/service.spec.ts diff --git a/api/src/_test/mocks.ts b/api/src/_test/mocks.ts deleted file mode 100644 index a96d5fb81..000000000 --- a/api/src/_test/mocks.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable camelcase */ -import { GithubUser } from "src/github/types"; - -export const githubUserMock: GithubUser = { - login: "ZibanPirate", - html_url: "https://github.com/ZibanPirate", - avatar_url: "https://avatars.githubusercontent.com/u/20110076?v=4", - name: "Zakaria Mansouri", - type: "User", -}; diff --git a/api/src/_utils/validator/is-map-of-string-number.ts b/api/src/_utils/validator/is-map-of-string-number.ts deleted file mode 100644 index 62225b06c..000000000 --- a/api/src/_utils/validator/is-map-of-string-number.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; - -@ValidatorConstraint({ name: "isMapOfStringNumber", async: false }) -export class IsMapOfStringNumber implements ValidatorConstraintInterface { - validate(map: Record) { - if (!map) return false; - - for (const key in map) { - if (Object.prototype.hasOwnProperty.call(map, key)) { - const value = map[key]; - if (typeof key !== "string") return false; - if (typeof value !== "number") return false; - } - } - - return true; - } - - defaultMessage() { - return "Map must be of string keys and number values"; - } -} diff --git a/api/src/_utils/validator/validate-plain-object.ts b/api/src/_utils/validator/validate-plain-object.ts deleted file mode 100644 index cdc31e2e8..000000000 --- a/api/src/_utils/validator/validate-plain-object.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ClassConstructor, plainToClass, plainToInstance } from "class-transformer"; -import { validateSync } from "class-validator"; -import camelCase from "lodash/camelCase"; -import mapKeys from "lodash/mapKeys"; - -export function validatePlainObject>( - cls: T, - obj: Record, - exposeValues = false, - allowExtraneousKeys = false, -): InstanceType { - const camelCasedObj = mapKeys(obj, (value, key) => camelCase(key)); - - const output = plainToClass(cls, camelCasedObj); - - const errors = validateSync(output as T, { - whitelist: !allowExtraneousKeys, - forbidNonWhitelisted: !allowExtraneousKeys, - }); - - if (errors.length > 0) - throw new Error( - exposeValues - ? JSON.stringify(errors, null, 2) - : `Errors in object in the following keys:${errors.reduce( - (pV, cV) => (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), - "", - )}`, - ); - - return plainToInstance(cls, camelCasedObj) as InstanceType; -} diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 2db4d9fee..d71b0e8bb 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -61,17 +61,10 @@ export class DigestCron { * Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId. */ private async run() { - // @TODO-ZM: remove it - if (Math.random() >= 0) return; - const runId = Math.random().toString(36).slice(2); this.logger.info({ message: `Digest cron started, runId: ${runId}` }); - const projectsFromDataFolder = (await this.dataService.listProjects()).filter( - // (project, index) => index < 5, - () => true, - ); - // + const projectsFromDataFolder = await this.dataService.listProjects(); // @TODO-ZM: add data with recordStatus="draft", delete, then update to recordStatus="ok" // @TODO-ZM: in all repos, filter by recordStatus="ok" @@ -102,7 +95,7 @@ export class DigestCron { repo: repository.name, }); - for (const issue of issues.issues) { + for (const issue of issues) { const githubUser = await this.githubService.getUser({ username: issue.user.login }); if (githubUser.type !== "User") continue; diff --git a/api/src/fetch/service.ts b/api/src/fetch/service.ts index 5c8871ae6..d23969e1f 100644 --- a/api/src/fetch/service.ts +++ b/api/src/fetch/service.ts @@ -1,7 +1,5 @@ import { lockFactory } from "@dzcode.io/utils/dist/concurrency"; -import { ClassConstructor } from "class-transformer"; import { defaults } from "make-fetch-happen"; -import { validatePlainObject } from "src/_utils/validator/validate-plain-object"; import { ConfigService } from "src/config/service"; import { LoggerService } from "src/logger/service"; import { Service } from "typedi"; @@ -21,20 +19,15 @@ export class FetchService { }); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public get = async >( + public get = async ( url: string, { params = {}, headers = {} }: FetchConfig = {}, - cls: T, - rootKey?: string, - ) => { + ): Promise> => { const _url = new URL(url); Object.keys(params).forEach((key) => _url.searchParams.append(key, String(params[key]))); - const response = await this.fetch>(_url.toString(), { headers }); - const mappedResponse = rootKey ? { [rootKey]: response } : response; - - return validatePlainObject(cls, mappedResponse, undefined, true); + const response = await this.fetch(_url.toString(), { headers }); + return response; }; // @TODO-ZM: using DTO, validate response and DRY the types diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts index e190897d4..4884b4511 100644 --- a/api/src/github/dto.ts +++ b/api/src/github/dto.ts @@ -1,59 +1,19 @@ -import { IsIn, IsNumber, IsString, Validate, ValidateNested } from "class-validator"; -import { IsMapOfStringNumber } from "src/_utils/validator/is-map-of-string-number"; +import { GithubUser } from "./types"; -export class GitHubListRepositoryLanguagesResponse { - @Validate(IsMapOfStringNumber) - languages!: Map; +export interface GetRepositoryResponse { + name: string; + owner: GithubUser; } -class GithubAccount { - @IsString() - login!: string; -} - -export class GetRepositoryResponse { - @IsString() - name!: string; - - @ValidateNested() - owner!: GithubAccount; -} - -class GetRepositoryIssuesPullRequestResponse { - @IsString() - html_url!: string; // eslint-disable-line camelcase -} - -class GetRepositoryIssuesResponse { - @IsString() - title!: string; - - @ValidateNested() - user!: GithubAccount; - - @IsString({ each: true }) - labels!: string[]; - - @IsIn(["closed", "open"]) - state!: "closed" | "open"; - - @ValidateNested({ each: true }) - assignees!: GithubAccount[]; - - @IsString() - updated_at!: string; // eslint-disable-line camelcase - - @IsString() - html_url!: string; // eslint-disable-line camelcase - - @ValidateNested() - pull_request!: GetRepositoryIssuesPullRequestResponse; // eslint-disable-line camelcase - - @IsNumber() - comments!: number; -} - -export class GetRepositoryIssuesResponseArray { - @ValidateNested({ each: true }) - issues!: GetRepositoryIssuesResponse[]; +interface GithubIssue { + title: string; + user: GithubUser; + labels: string[]; + state: "closed" | "open"; + assignees: GithubUser[]; + updated_at: string; + html_url: string; + pull_request: { html_url: string }; + comments: number; } +export type GetRepositoryIssuesResponse = GithubIssue[]; diff --git a/api/src/github/service.spec.ts b/api/src/github/service.spec.ts deleted file mode 100644 index 7be1f346e..000000000 --- a/api/src/github/service.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { mock } from "jest-mock-extended"; -import { githubUserMock } from "src/_test/mocks"; -import { ConfigService } from "src/config/service"; -import { FetchService } from "src/fetch/service"; - -import { GithubService } from "./service"; -import { GeneralGithubQuery, ListPathCommittersResponse } from "./types"; - -describe("GithubService", () => { - const githubQuery: GeneralGithubQuery = { - owner: "test-owner", - repository: "test-repo", - path: "test/path", - }; - const contributorsMock: ListPathCommittersResponse = [ - { - author: githubUserMock, - committer: githubUserMock, - }, - ]; - - const configService = mock({ - env: () => ({ - FETCH_CACHE_PATH: "", - SQLITE_DB_PATH: "", - NODE_ENV: "development", - PORT: 0, - BUNDLE_INFO: { version: "test" }, - }), - }); - const fetchService = mock(); - - it("should throw error when api call fails", async () => { - fetchService.get.mockRejectedValue({ meg: "service down" }); - const githubService = new GithubService(configService, fetchService); - let errorThrown: unknown; - try { - await githubService.listPathCommitters(githubQuery); - } catch (error) { - errorThrown = error; - } - expect(errorThrown).toMatchObject({ meg: "service down" }); - }); - - it("should return list of contributors when api call succeed", async () => { - fetchService.get.mockResolvedValue(contributorsMock); - const githubService = new GithubService(configService, fetchService); - const contributors = await githubService.listPathCommitters(githubQuery); - - expect(contributors).toMatchObject([githubUserMock]); - }); -}); diff --git a/api/src/github/service.ts b/api/src/github/service.ts index fbaf173a2..d75de76d9 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -1,25 +1,16 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { AccountEntity } from "@dzcode.io/models/dist/account"; import { ConfigService } from "src/config/service"; import { FetchService } from "src/fetch/service"; import { Service } from "typedi"; -import { - GetRepositoryIssuesResponseArray, - GetRepositoryResponse, - GitHubListRepositoryLanguagesResponse, -} from "./dto"; +import { GetRepositoryIssuesResponse, GetRepositoryResponse } from "./dto"; import { GeneralGithubQuery, GetRepositoryInput, GetUserInput, - GitHubListRepositoryLanguagesInput, GitHubListRepositoryMilestonesInput, GithubMilestone, GitHubRateLimitApiResponse, - GithubUser, GitHubUserApiResponse, - ListPathCommittersResponse, ListRepositoryContributorsResponse, } from "./types"; @@ -30,28 +21,6 @@ export class GithubService { private readonly fetchService: FetchService, ) {} - public listPathCommitters = async ({ - owner, - repository, - path, - }: GeneralGithubQuery): Promise => { - const commits = await this.fetchService.getUnsafe( - `${this.apiURL}/repos/${owner}/${repository}/commits`, - { - headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, - params: { path, state: "all", per_page: 100 }, // eslint-disable-line camelcase - }, - ); - const contributors = commits - // @TODO-ZM: dry to a user block-list - // excluding github.com/web-flow user - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .filter((item) => item.committer && item.committer.id !== 19864447) - .map(({ committer }) => committer); - return contributors; - }; - public getUser = async ({ username }: GetUserInput): Promise => { const user = await this.fetchService.getUnsafe( `${this.apiURL}/users/${username}`, @@ -63,42 +32,26 @@ export class GithubService { public listRepositoryIssues = async ({ owner, repo, - }: GetRepositoryInput): Promise => { - const repoIssues = await this.fetchService.get( + }: GetRepositoryInput): Promise => { + const repoIssues = await this.fetchService.get( `${this.apiURL}/repos/${owner}/${repo}/issues`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, // @TODO-ZM: add pagination params: { sort: "updated", per_page: 100 }, // eslint-disable-line camelcase }, - GetRepositoryIssuesResponseArray, - "issues", ); return repoIssues; }; - public listRepositoryLanguages = async ({ - owner, - repository, - }: GitHubListRepositoryLanguagesInput): Promise => { - const languages = await this.fetchService.get( - `${this.apiURL}/repos/${owner}/${repository}/languages`, - { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, - GitHubListRepositoryLanguagesResponse, - "languages", - ); - return languages; - }; - public getRepository = async ({ owner, repo, }: GetRepositoryInput): Promise => { - const repoInfo = await this.fetchService.get( + const repoInfo = await this.fetchService.get( `${this.apiURL}/repos/${owner}/${repo}`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, - GetRepositoryResponse, ); return repoInfo; }; @@ -148,19 +101,6 @@ export class GithubService { return milestones; }; - public githubUserToAccountEntity = ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - user: Pick, - ): Model => ({ - id: `github/${user.id}`, - username: user.login, - name: user.name || user.login, - // @TODO-ZM: change this to `/Account/github/${user.id}` once we have a /Accounts page - profileUrl: user.html_url, - avatarUrl: user.avatar_url, - }); - private githubToken = this.configService.env().GITHUB_TOKEN; private apiURL = "https://api.github.com"; } diff --git a/api/src/github/types.ts b/api/src/github/types.ts index 4a14bcc0d..9063b457c 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -13,11 +13,6 @@ interface GithubRepositoryContributor extends GithubUser { contributions: number; } -export type ListPathCommittersResponse = Array<{ - author: GithubUser; - committer: GithubUser; -}>; - export type ListRepositoryContributorsResponse = GithubRepositoryContributor[]; export interface GeneralGithubQuery { @@ -42,8 +37,6 @@ interface GitHubListRepositoryIssuesInput { repository: string; } -export type GitHubListRepositoryLanguagesInput = GitHubListRepositoryIssuesInput; - export type GitHubListRepositoryMilestonesInput = GitHubListRepositoryIssuesInput; export interface GithubMilestone { From b54f7fec6856dcbbcad8ef43258c8a840a3e4389 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 17:26:07 +0200 Subject: [PATCH 32/43] removed more unused code --- api/package.json | 8 +-- api/src/app/index.ts | 12 +--- api/src/app/middlewares/docs.ts | 40 -------------- api/src/app/types/index.ts | 12 +--- api/src/github/controller.ts | 5 -- api/src/github/types.ts | 14 ++--- api/src/milestone/controller.ts | 5 -- api/src/milestone/types.ts | 9 +-- yarn.lock | 97 +++++++++------------------------ 9 files changed, 37 insertions(+), 165 deletions(-) delete mode 100644 api/src/app/middlewares/docs.ts diff --git a/api/package.json b/api/package.json index 15babf0e8..59a976f90 100644 --- a/api/package.json +++ b/api/package.json @@ -14,8 +14,7 @@ "@sentry/profiling-node": "^8.28.0", "better-sqlite3": "^11.2.1", "class-transformer": "^0.5.1", - "class-validator": "^0.13.2", - "class-validator-jsonschema": "^3.1.0", + "class-validator": "^0.14.1", "cors": "^2.8.5", "cron": "^3.1.7", "dotenv": "^8.2.0", @@ -30,8 +29,6 @@ "morgan": "^1.10.0", "reflect-metadata": "^0.1.13", "routing-controllers": "^0.9.0", - "routing-controllers-openapi": "^3.1.0", - "swagger-ui-express": "^4.1.6", "typedi": "^0.10.0", "winston": "^3.3.3" }, @@ -48,13 +45,12 @@ "@types/lodash": "^4.17.7", "@types/make-fetch-happen": "^9.0.1", "@types/morgan": "^1.9.2", - "@types/swagger-ui-express": "^4.1.2", "drizzle-kit": "^0.24.2", "faker": "^5.5.3", "glob": "^7.1.7" }, "engines": { - "node": ">=16", + "node": ">=20", "yarn": ">=1.4.2" }, "license": "MIT", diff --git a/api/src/app/index.ts b/api/src/app/index.ts index a98ad9403..a3005201c 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -1,3 +1,4 @@ +// @TODO-ZM: remove the need for reflect-metadata // Import these two first! in this order import "reflect-metadata"; import "src/_utils/setup-sentry"; @@ -17,7 +18,6 @@ import { ProjectController } from "src/project/controller"; import { SQLiteService } from "src/sqlite/service"; import Container from "typedi"; -import { DocsMiddleware } from "./middlewares/docs"; import { ErrorMiddleware } from "./middlewares/error"; import { LoggerMiddleware } from "./middlewares/logger"; import { RobotsMiddleware } from "./middlewares/robots"; @@ -36,7 +36,7 @@ const CronServices = [DigestCron]; CronServices.forEach((service) => Container.get(service)); // Create the app: -export const routingControllersOptions: RoutingControllersOptions = { +const routingControllersOptions: RoutingControllersOptions = { controllers: [ ContributionController, GithubController, @@ -44,13 +44,7 @@ export const routingControllersOptions: RoutingControllersOptions = { ProjectController, ContributorController, ], - middlewares: [ - SecurityMiddleware, - ErrorMiddleware, - LoggerMiddleware, - DocsMiddleware, - RobotsMiddleware, - ], + middlewares: [SecurityMiddleware, ErrorMiddleware, LoggerMiddleware, RobotsMiddleware], defaultErrorHandler: false, cors: Container.get(SecurityMiddleware).cors(), }; diff --git a/api/src/app/middlewares/docs.ts b/api/src/app/middlewares/docs.ts deleted file mode 100644 index f2d5b91ea..000000000 --- a/api/src/app/middlewares/docs.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { RequestHandler, Router } from "express"; -import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; -import { ConfigService } from "src/config/service"; -import { serve, setup } from "swagger-ui-express"; -import { Service } from "typedi"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { defaultMetadataStorage } = require("class-transformer/cjs/storage"); -import { validationMetadatasToSchemas } from "class-validator-jsonschema"; -import { getMetadataArgsStorage } from "routing-controllers"; -import { routingControllersToSpec } from "routing-controllers-openapi"; - -import { routingControllersOptions } from ".."; - -@Service() -@Middleware({ type: "after" }) -export class DocsMiddleware implements ExpressMiddlewareInterface { - constructor(private configService: ConfigService) { - // Parse class-validator classes into JSON Schema: - const schemas = validationMetadatasToSchemas({ - refPointerPrefix: "#/components/schemas/", - classTransformerMetadataStorage: defaultMetadataStorage, - }); - // Parse routing-controllers classes into OpenAPI spec: - const storage = getMetadataArgsStorage(); - const spec = routingControllersToSpec(storage, routingControllersOptions, { - components: { schemas }, - info: { - description: "Swagger documentation for dzcode.io API", - title: "dzcode.io API", - version: configService.env().BUNDLE_INFO.version, - }, - }); - - this.router.use("/docs", serve, setup(spec)); - } - - private router = Router(); - - use: RequestHandler = this.router; -} diff --git a/api/src/app/types/index.ts b/api/src/app/types/index.ts index 2ad143283..2c99f8d76 100644 --- a/api/src/app/types/index.ts +++ b/api/src/app/types/index.ts @@ -1,15 +1,5 @@ -import { IsNumber, IsObject, IsOptional, IsString } from "class-validator"; - -export class GeneralResponseDto { - @IsNumber() - @IsOptional() +export interface GeneralResponseDto { code?: number; - - @IsString() - @IsOptional() msg?: string; - - @IsObject() - @IsOptional() debug?: Record; } diff --git a/api/src/github/controller.ts b/api/src/github/controller.ts index f0cd095f4..81a873370 100644 --- a/api/src/github/controller.ts +++ b/api/src/github/controller.ts @@ -1,5 +1,4 @@ import { Controller, Get } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; import { GithubService } from "src/github/service"; import { Service } from "typedi"; @@ -11,10 +10,6 @@ export class GithubController { constructor(private readonly githubService: GithubService) {} @Get("/RateLimit") - @OpenAPI({ - summary: "Return Info about Github Rate limit", - }) - @ResponseSchema(GetRateLimitResponseDto) public async getRateLimitInfo(): Promise { const { limit, used, ratio } = await this.githubService.getRateLimit(); diff --git a/api/src/github/types.ts b/api/src/github/types.ts index 9063b457c..96d6f4006 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -1,4 +1,3 @@ -import { IsNumber } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; export interface GithubUser { @@ -94,13 +93,8 @@ export interface GitHubRateLimitApiResponse { }; } -export class GetRateLimitResponseDto extends GeneralResponseDto { - @IsNumber() - limit!: number; - - @IsNumber() - used!: number; - - @IsNumber() - ratio!: number; +export interface GetRateLimitResponseDto extends GeneralResponseDto { + limit: number; + used: number; + ratio: number; } diff --git a/api/src/milestone/controller.ts b/api/src/milestone/controller.ts index 38e710571..92d0a5fa3 100644 --- a/api/src/milestone/controller.ts +++ b/api/src/milestone/controller.ts @@ -1,5 +1,4 @@ import { Controller, Get } from "routing-controllers"; -import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; import { GithubService } from "src/github/service"; import { Service } from "typedi"; @@ -11,10 +10,6 @@ export class MilestoneController { constructor(private readonly githubService: GithubService) {} @Get("/dzcode") - @OpenAPI({ - summary: "Return a list of milestones for dzcode.io repository", - }) - @ResponseSchema(GetMilestonesResponseDto) public async getMilestones(): Promise { const githubMilestones = await this.githubService.listRepositoryMilestones({ owner: "dzcode-io", diff --git a/api/src/milestone/types.ts b/api/src/milestone/types.ts index 5e7103c0c..628e43ab3 100644 --- a/api/src/milestone/types.ts +++ b/api/src/milestone/types.ts @@ -1,11 +1,6 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { MilestoneEntity } from "@dzcode.io/models/dist/milestone"; -import { Type } from "class-transformer"; -import { ValidateNested } from "class-validator"; import { GeneralResponseDto } from "src/app/types"; -export class GetMilestonesResponseDto extends GeneralResponseDto { - @ValidateNested({ each: true }) - @Type(() => MilestoneEntity) - milestones!: Model[]; +export interface GetMilestonesResponseDto extends GeneralResponseDto { + milestones: MilestoneEntity[]; } diff --git a/yarn.lock b/yarn.lock index ba7270b00..2a830a4da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3365,14 +3365,6 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/swagger-ui-express@^4.1.2": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz#7adbbbf5343b45869debef1e9ff39c9ba73e380f" - integrity sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA== - dependencies: - "@types/express" "*" - "@types/serve-static" "*" - "@types/tough-cookie@*": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" @@ -3398,6 +3390,11 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/validator@^13.11.8": + version "13.12.1" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.1.tgz#8835d22f7e25b261e624d02a42fe4ade2c689a3c" + integrity sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA== + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -4798,17 +4795,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -class-validator-jsonschema@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/class-validator-jsonschema/-/class-validator-jsonschema-3.1.0.tgz#95cafa347ddc9da49343639d3261f85d23b06d39" - integrity sha512-1If+ZK3ZKhJfA7QWi064RJ2oTadBqmBtVPRb4DyxIlWS2m2hc9kWHwoEfycOroVfTuANMZ4XOtp3JD27U5V95w== - dependencies: - lodash.groupby "^4.6.0" - lodash.merge "^4.6.2" - openapi3-ts "^2.0.0" - reflect-metadata "^0.1.13" - tslib "^2.0.3" - class-validator@^0.13.2: version "0.13.2" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" @@ -4817,6 +4803,15 @@ class-validator@^0.13.2: libphonenumber-js "^1.9.43" validator "^13.7.0" +class-validator@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" + integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.10.53" + validator "^13.9.0" + clean-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" @@ -9673,6 +9668,11 @@ libnpmpublish@^4.0.0: semver "^7.1.3" ssri "^8.0.1" +libphonenumber-js@^1.10.53: + version "1.11.7" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz#efe4fcf816e1982925e9c800d0013b0ee99b8283" + integrity sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A== + libphonenumber-js@^1.9.43: version "1.9.43" resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.43.tgz#2371e4383e6780990381d5b900b8c22666221cbb" @@ -9808,16 +9808,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.capitalize@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" - integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= - -lodash.groupby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" - integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= - lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -9885,11 +9875,6 @@ lodash.snakecase@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= -lodash.startcase@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" - integrity sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg= - lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" @@ -11472,13 +11457,6 @@ open@^7.4.2: is-docker "^2.0.0" is-wsl "^2.1.1" -openapi3-ts@^2.0.0, openapi3-ts@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.1.tgz#b270aecea09e924f1886bc02a72608fca5a98d85" - integrity sha512-v6X3iwddhi276siej96jHGIqTx3wzVfMTmpGJEQDt7GPI7pI6sywItURLzpEci21SBRpPN/aOWSF5mVfFVNmcg== - dependencies: - yaml "^1.10.0" - openapi3-ts@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-3.2.0.tgz#7e30d33c480e938e67e809ab16f419bc9beae3f8" @@ -11886,11 +11864,6 @@ path-to-regexp@^1.1.1, path-to-regexp@^1.8.0: dependencies: isarray "0.0.1" -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -13176,19 +13149,6 @@ router@^1.3.1: setprototypeof "1.2.0" utils-merge "1.0.1" -routing-controllers-openapi@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/routing-controllers-openapi/-/routing-controllers-openapi-3.1.0.tgz#90898a46c2490b203242e64bc08b536ba1228118" - integrity sha512-FnTYnbNfsCN+vTDAc7rhCm5u0nLAH+p+UpbJXZT10cgo2t7xiZ23BrrzsR5nnqMGwe/iwsDUEEr8lxs6KarscQ== - dependencies: - lodash.capitalize "^4.2.1" - lodash.merge "^4.6.2" - lodash.startcase "^4.4.0" - openapi3-ts "^2.0.1" - path-to-regexp "^2.2.1" - reflect-metadata "^0.1.13" - tslib "^2.1.0" - routing-controllers@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.9.0.tgz#979016523db37832d4c9a23c33b2654a89a563de" @@ -14173,18 +14133,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swagger-ui-dist@^3.18.1: - version "3.52.5" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.52.5.tgz#9aa8101a2be751f5145195b9e048bc21b12fac60" - integrity sha512-8z18eX8G/jbTXYzyNIaobrnD7PSN7yU/YkSasMmajrXtw0FGS64XjrKn5v37d36qmU3o1xLeuYnktshRr7uIFw== - -swagger-ui-express@^4.1.6: - version "4.1.6" - resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz#682294af3d5c70f74a1fa4d6a9b503a9ee55ea82" - integrity sha512-Xs2BGGudvDBtL7RXcYtNvHsFtP1DBFPMJFRxHe5ez/VG/rzVOEjazJOOSc/kSCyxreCTKfJrII6MJlL9a6t8vw== - dependencies: - swagger-ui-dist "^3.18.1" - syncpack@^5.8.15: version "5.8.15" resolved "https://registry.yarnpkg.com/syncpack/-/syncpack-5.8.15.tgz#4b9e7837fa738d7ddd80c49ac14a9cba152951b3" @@ -14627,7 +14575,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0: +tslib@^2.0.1, tslib@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -15132,6 +15080,11 @@ validator@^13.7.0: resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== +validator@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" From 442910403a9c7788cee0f1fb201ca5d229f00080 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 18:36:39 +0200 Subject: [PATCH 33/43] reordered deletions --- api/src/app/index.ts | 2 +- api/src/digest/cron.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/app/index.ts b/api/src/app/index.ts index a3005201c..0bd628eba 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -57,5 +57,5 @@ Sentry.setupExpressErrorHandler(app); // Start it app.listen(PORT, () => { const commonConfig = fsConfig(NODE_ENV); - logger.info({ message: `API Server up on: ${commonConfig.api.url}/docs` }); + logger.info({ message: `API Server up on: ${commonConfig.api.url}/` }); }); diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index d71b0e8bb..5a56c7274 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -173,8 +173,8 @@ export class DigestCron { try { await this.contributorsRepository.deleteAllRelationWithRepositoryButWithRunId(runId); - await this.contributorsRepository.deleteAllButWithRunId(runId); await this.contributionsRepository.deleteAllButWithRunId(runId); + await this.contributorsRepository.deleteAllButWithRunId(runId); await this.repositoriesRepository.deleteAllButWithRunId(runId); await this.projectsRepository.deleteAllButWithRunId(runId); } catch (error) { From ef3bde869d553d60322f4365498f394bfc2781f5 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 18:49:21 +0200 Subject: [PATCH 34/43] refactor: Remove unused code and dependencies in team page --- web/src/pages/team/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/pages/team/index.tsx b/web/src/pages/team/index.tsx index df339866d..641e2a014 100644 --- a/web/src/pages/team/index.tsx +++ b/web/src/pages/team/index.tsx @@ -1,12 +1,11 @@ import { useEffect } from "react"; import { Helmet } from "react-helmet-async"; -import { Link } from "src/components/link"; import { Loading } from "src/components/loading"; import { Locale, useLocale } from "src/components/locale"; import { TryAgain } from "src/components/try-again"; import { fetchContributorsListAction } from "src/redux/actions/contributors"; import { useAppDispatch, useAppSelector } from "src/redux/store"; -import { getRepositoryName, getRepositoryURL } from "src/utils/repository"; +import { getRepositoryName } from "src/utils/repository"; // ts-prune-ignore-next export default function Page(): JSX.Element { From 441782b0853f5a9e47b0c46103b7f70401166a2f Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 19:01:31 +0200 Subject: [PATCH 35/43] chore: Update NODE_ENV validation in ENVDto --- api/src/config/dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/config/dto.ts b/api/src/config/dto.ts index 88b80e00b..bc11402f4 100644 --- a/api/src/config/dto.ts +++ b/api/src/config/dto.ts @@ -14,7 +14,7 @@ export class ENVDto { PORT = 7070; @Matches("(" + environments.join(")|(") + ")") - NODE_ENV: Environment = "development"; + NODE_ENV!: Environment; @IsString() FETCH_CACHE_PATH = "./fetch_cache"; From 6552e14fb1ccfa0d7297fd6074f3a261da64f84c Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 20:06:24 +0200 Subject: [PATCH 36/43] remove more unused code --- api/src/data/service.ts | 48 +---------------------------------------- api/src/data/types.ts | 17 --------------- 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/api/src/data/service.ts b/api/src/data/service.ts index 7b58806d5..c346edb6c 100644 --- a/api/src/data/service.ts +++ b/api/src/data/service.ts @@ -1,9 +1,8 @@ import { getCollection } from "@dzcode.io/data/dist/collection"; -import { getEntry } from "@dzcode.io/data/dist/entry"; import { join } from "path"; import { Service } from "typedi"; -import { DataArticleEntity, DataDocumentationEntity, DataProjectEntity } from "./types"; +import { DataProjectEntity } from "./types"; @Service() export class DataService { @@ -15,50 +14,5 @@ export class DataService { return projects; }; - public listArticles = async (): Promise[]> => { - const articles = getCollection(this.dataModelsPath, "articles", "list.json"); - - if (articles === 404) throw new Error("Articles list not found"); - - const mappedArticles = articles.map(({ slug, title }) => ({ slug, title })); - - return mappedArticles; - }; - - public getArticle = async (slug: string): Promise => { - const article = getEntry(this.dataModelsPath, `articles/${slug}`); - - if (article === 404) throw new Error("Article not found"); - - return article; - }; - - public listDocumentation = async (): Promise< - Pick[] - > => { - const documentation = getCollection( - this.dataModelsPath, - "documentation", - "list.json", - ); - - if (documentation === 404) throw new Error("Documentation list not found"); - - const mappedDocumentation = documentation.map(({ slug, title }) => ({ slug, title })); - - return mappedDocumentation; - }; - - public getDocumentation = async (slug: string): Promise => { - const documentation = getEntry( - this.dataModelsPath, - `documentation/${slug}`, - ); - - if (documentation === 404) throw new Error("Documentation not found"); - - return documentation; - }; - private dataModelsPath = join(__dirname, "../../../data"); } diff --git a/api/src/data/types.ts b/api/src/data/types.ts index a910859cc..af426196c 100644 --- a/api/src/data/types.ts +++ b/api/src/data/types.ts @@ -1,6 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ArticleInfoEntity } from "@dzcode.io/models/dist/article"; -import { DocumentationInfoEntity } from "@dzcode.io/models/dist/documentation"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; export type DataProjectEntity = { @@ -12,17 +9,3 @@ export type DataProjectEntity = { name: string; }>; }; - -export type DataArticleEntity = Model & { - authors: string[]; - description: string; - content: string; - image: string; -}; - -export type DataDocumentationEntity = Model & { - authors: string[]; - description: string; - content: string; - image: string; -}; From 3adf3418ba902a7b2cf8972460fc9502f885cf89 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 20:06:37 +0200 Subject: [PATCH 37/43] capture exceptions in cron --- api/src/digest/cron.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index 5a56c7274..df1702066 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -66,8 +66,6 @@ export class DigestCron { const projectsFromDataFolder = await this.dataService.listProjects(); - // @TODO-ZM: add data with recordStatus="draft", delete, then update to recordStatus="ok" - // @TODO-ZM: in all repos, filter by recordStatus="ok" for (const project of projectsFromDataFolder) { const [{ id: projectId }] = await this.projectsRepository.upsert({ ...project, runId }); @@ -116,7 +114,7 @@ export class DigestCron { }); const type = issue.pull_request ? "PULL_REQUEST" : "ISSUE"; - const [{ id: contributionId }] = await this.contributionsRepository.upsert({ + await this.contributionsRepository.upsert({ title: issue.title, type, updatedAt: issue.updated_at, @@ -126,8 +124,6 @@ export class DigestCron { repositoryId, contributorId, }); - - console.log("contributionId", contributionId); } const repoContributors = await this.githubService.listRepositoryContributors({ @@ -156,13 +152,11 @@ export class DigestCron { }); } } catch (error) { - // @TODO-ZM: capture error - console.error(error); + captureException(error, { tags: { type: "CRON" } }); } } } catch (error) { - // @TODO-ZM: capture error - console.error(error); + captureException(error, { tags: { type: "CRON" } }); } if (addedRepositoryCount === 0) { @@ -178,8 +172,7 @@ export class DigestCron { await this.repositoriesRepository.deleteAllButWithRunId(runId); await this.projectsRepository.deleteAllButWithRunId(runId); } catch (error) { - // @TODO-ZM: capture error - console.error(error); + captureException(error, { tags: { type: "CRON" } }); } this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); From e723548e8c8cf823768d5943291f180635335056 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 20:11:27 +0200 Subject: [PATCH 38/43] removed getUnsafe --- api/src/fetch/service.ts | 12 ------------ api/src/github/service.ts | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/api/src/fetch/service.ts b/api/src/fetch/service.ts index d23969e1f..1e8ebdaf2 100644 --- a/api/src/fetch/service.ts +++ b/api/src/fetch/service.ts @@ -30,18 +30,6 @@ export class FetchService { return response; }; - // @TODO-ZM: using DTO, validate response and DRY the types - public getUnsafe = async ( - url: string, - { params = {}, headers = {} }: FetchConfig = {}, - ) => { - const _url = new URL(url); - Object.keys(params).forEach((key) => _url.searchParams.append(key, String(params[key]))); - - const response = await this.fetch(_url.toString(), { headers }); - return response; - }; - private makeFetchHappenInstance; // @TODO-ZM: make sure lockFactory works as expected private fetch = lockFactory( diff --git a/api/src/github/service.ts b/api/src/github/service.ts index d75de76d9..d34ce55f1 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -22,7 +22,7 @@ export class GithubService { ) {} public getUser = async ({ username }: GetUserInput): Promise => { - const user = await this.fetchService.getUnsafe( + const user = await this.fetchService.get( `${this.apiURL}/users/${username}`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, ); @@ -60,7 +60,7 @@ export class GithubService { owner, repository, }: Omit): Promise => { - const contributors = await this.fetchService.getUnsafe( + const contributors = await this.fetchService.get( `${this.apiURL}/repos/${owner}/${repository}/contributors`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, @@ -75,7 +75,7 @@ export class GithubService { }; public getRateLimit = async (): Promise<{ limit: number; used: number; ratio: number }> => { - const rateLimitInfo = await this.fetchService.getUnsafe( + const rateLimitInfo = await this.fetchService.get( `${this.apiURL}/rate_limit`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {} }, ); @@ -91,7 +91,7 @@ export class GithubService { owner, repository, }: GitHubListRepositoryMilestonesInput): Promise => { - const milestones = await this.fetchService.getUnsafe( + const milestones = await this.fetchService.get( `${this.apiURL}/repos/${owner}/${repository}/milestones`, { headers: this.githubToken ? { Authorization: `Token ${this.githubToken}` } : {}, From 9c376ebab20904639d463803dbab5405ef470732 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 20:43:44 +0200 Subject: [PATCH 39/43] no dtos please --- api/src/app/endpoints.ts | 16 +-- api/src/app/middlewares/error.ts | 4 +- api/src/app/middlewares/security.ts | 4 +- api/src/app/types/index.ts | 2 +- api/src/config/generate-config.ts | 8 +- api/src/config/service.ts | 4 +- api/src/config/{dto.ts => types.ts} | 2 +- api/src/contribution/controller.ts | 4 +- api/src/contribution/types.ts | 5 +- api/src/contributor/controller.ts | 4 +- api/src/contributor/types.ts | 4 +- api/src/github/controller.ts | 4 +- api/src/github/dto.ts | 19 --- api/src/github/service.ts | 3 +- api/src/github/types.ts | 24 +++- api/src/milestone/controller.ts | 4 +- api/src/milestone/types.ts | 4 +- api/src/project/controller.ts | 4 +- api/src/project/types.ts | 4 +- packages/models/package.json | 2 +- packages/models/src/_test/index.ts | 46 ------- .../account/__snapshots__/index.spec.ts.snap | 78 ------------ packages/models/src/account/index.spec.ts | 17 --- packages/models/src/account/index.ts | 26 ---- .../article/__snapshots__/index.spec.ts.snap | 81 ------------- packages/models/src/article/index.spec.ts | 18 --- packages/models/src/article/index.ts | 31 ----- .../__snapshots__/index.spec.ts.snap | 81 ------------- .../models/src/documentation/index.spec.ts | 18 --- packages/models/src/documentation/index.ts | 31 ----- .../language/__snapshots__/index.spec.ts.snap | 83 ------------- packages/models/src/language/index.spec.ts | 18 --- .../__snapshots__/index.spec.ts.snap | 112 ------------------ packages/models/src/milestone/index.spec.ts | 21 ---- .../__snapshots__/index.spec.ts.snap | 68 ----------- .../src/project-reference/index.spec.ts | 24 ---- .../models/src/project-reference/index.ts | 17 --- .../__snapshots__/index.spec.ts.snap | 57 --------- .../src/repository-reference/index.spec.ts | 16 --- .../models/src/repository-reference/index.ts | 14 --- web/src/redux/slices/contributions-page.ts | 4 +- web/src/redux/slices/contributors-page.ts | 4 +- web/src/redux/slices/landing-page.ts | 4 +- web/src/redux/slices/projects-page.ts | 4 +- 44 files changed, 70 insertions(+), 928 deletions(-) rename api/src/config/{dto.ts => types.ts} (96%) delete mode 100644 api/src/github/dto.ts delete mode 100644 packages/models/src/_test/index.ts delete mode 100644 packages/models/src/account/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/account/index.spec.ts delete mode 100644 packages/models/src/account/index.ts delete mode 100644 packages/models/src/article/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/article/index.spec.ts delete mode 100644 packages/models/src/article/index.ts delete mode 100644 packages/models/src/documentation/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/documentation/index.spec.ts delete mode 100644 packages/models/src/documentation/index.ts delete mode 100644 packages/models/src/language/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/language/index.spec.ts delete mode 100644 packages/models/src/milestone/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/milestone/index.spec.ts delete mode 100644 packages/models/src/project-reference/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/project-reference/index.spec.ts delete mode 100644 packages/models/src/project-reference/index.ts delete mode 100644 packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap delete mode 100644 packages/models/src/repository-reference/index.spec.ts delete mode 100644 packages/models/src/repository-reference/index.ts diff --git a/api/src/app/endpoints.ts b/api/src/app/endpoints.ts index e2d1c6572..bfccfc8b8 100644 --- a/api/src/app/endpoints.ts +++ b/api/src/app/endpoints.ts @@ -1,20 +1,20 @@ -import { GetContributionsResponseDto } from "src/contribution/types"; -import { GetContributorsResponseDto } from "src/contributor/types"; -import { GetMilestonesResponseDto } from "src/milestone/types"; -import { GetProjectsResponseDto } from "src/project/types"; +import { GetContributionsResponse } from "src/contribution/types"; +import { GetContributorsResponse } from "src/contributor/types"; +import { GetMilestonesResponse } from "src/milestone/types"; +import { GetProjectsResponse } from "src/project/types"; // ts-prune-ignore-next export interface Endpoints { "api:Projects": { - response: GetProjectsResponseDto; + response: GetProjectsResponse; }; "api:Contributions": { - response: GetContributionsResponseDto; + response: GetContributionsResponse; }; "api:Contributors": { - response: GetContributorsResponseDto; + response: GetContributorsResponse; }; "api:MileStones/dzcode": { - response: GetMilestonesResponseDto; + response: GetMilestonesResponse; }; } diff --git a/api/src/app/middlewares/error.ts b/api/src/app/middlewares/error.ts index ba8d25cca..f951d978f 100644 --- a/api/src/app/middlewares/error.ts +++ b/api/src/app/middlewares/error.ts @@ -1,6 +1,6 @@ import { ErrorRequestHandler } from "express"; import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers"; -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; import { LoggerService } from "src/logger/service"; import { Service } from "typedi"; @@ -9,7 +9,7 @@ import { Service } from "typedi"; export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { constructor(private loggerService: LoggerService) {} - error: ErrorRequestHandler = (err, req, res, next) => { + error: ErrorRequestHandler = (err, req, res, next) => { // Logs error this.loggerService.error({ message: "Internal Server Error", diff --git a/api/src/app/middlewares/security.ts b/api/src/app/middlewares/security.ts index e751bd38b..f62bddd62 100644 --- a/api/src/app/middlewares/security.ts +++ b/api/src/app/middlewares/security.ts @@ -3,8 +3,8 @@ import { RequestHandler, Router } from "express"; import rateLimit from "express-rate-limit"; import helmet from "helmet"; import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; -import { ENVDto } from "src/config/dto"; import { ConfigService } from "src/config/service"; +import { EnvRecord } from "src/config/types"; import { Service } from "typedi"; @Service() @@ -30,7 +30,7 @@ export class SecurityMiddleware implements ExpressMiddlewareInterface { } private router = Router(); - private env: ENVDto["NODE_ENV"]; + private env: EnvRecord["NODE_ENV"]; private whitelist: string[]; use: RequestHandler = this.router; diff --git a/api/src/app/types/index.ts b/api/src/app/types/index.ts index 2c99f8d76..6aefa8215 100644 --- a/api/src/app/types/index.ts +++ b/api/src/app/types/index.ts @@ -1,4 +1,4 @@ -export interface GeneralResponseDto { +export interface GeneralResponse { code?: number; msg?: string; debug?: Record; diff --git a/api/src/config/generate-config.ts b/api/src/config/generate-config.ts index 2fa8817ff..34cd90edc 100644 --- a/api/src/config/generate-config.ts +++ b/api/src/config/generate-config.ts @@ -2,15 +2,15 @@ import { plainToClass } from "class-transformer"; import { validateSync } from "class-validator"; import { config } from "dotenv"; -import { ENVDto } from "./dto"; +import { EnvRecord } from "./types"; -let cachedEnv: ENVDto | undefined = undefined; +let cachedEnv: EnvRecord | undefined = undefined; -export function generateConfig(): ENVDto { +export function generateConfig(): EnvRecord { if (cachedEnv) return cachedEnv; const _config = config(); - const output = plainToClass(ENVDto, { + const output = plainToClass(EnvRecord, { ...process.env, ...(_config.parsed || {}), }); diff --git a/api/src/config/service.ts b/api/src/config/service.ts index b6431653a..37162dc5b 100644 --- a/api/src/config/service.ts +++ b/api/src/config/service.ts @@ -1,9 +1,9 @@ import { Service } from "typedi"; -import { ENVDto } from "./dto"; import { generateConfig } from "./generate-config"; +import { EnvRecord } from "./types"; -let _env: ENVDto; +let _env: EnvRecord; @Service() export class ConfigService { diff --git a/api/src/config/dto.ts b/api/src/config/types.ts similarity index 96% rename from api/src/config/dto.ts rename to api/src/config/types.ts index bc11402f4..4bd23cf43 100644 --- a/api/src/config/dto.ts +++ b/api/src/config/types.ts @@ -10,7 +10,7 @@ try { /**/ } -export class ENVDto { +export class EnvRecord { PORT = 7070; @Matches("(" + environments.join(")|(") + ")") diff --git a/api/src/contribution/controller.ts b/api/src/contribution/controller.ts index be91fd1d1..22f65106e 100644 --- a/api/src/contribution/controller.ts +++ b/api/src/contribution/controller.ts @@ -2,7 +2,7 @@ import { Controller, Get } from "routing-controllers"; import { Service } from "typedi"; import { ContributionRepository } from "./repository"; -import { GetContributionsResponseDto } from "./types"; +import { GetContributionsResponse } from "./types"; @Service() @Controller("/Contributions") @@ -10,7 +10,7 @@ export class ContributionController { constructor(private readonly contributionRepository: ContributionRepository) {} @Get("/") - public async getContributions(): Promise { + public async getContributions(): Promise { const contributions = await this.contributionRepository.findForList(); return { diff --git a/api/src/contribution/types.ts b/api/src/contribution/types.ts index a2e3c5190..8c9f54c2e 100644 --- a/api/src/contribution/types.ts +++ b/api/src/contribution/types.ts @@ -2,10 +2,9 @@ import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; -// @TODO-ZM: remove "dto" from all interfaces -export interface GetContributionsResponseDto extends GeneralResponseDto { +export interface GetContributionsResponse extends GeneralResponse { contributions: Array< Pick & { repository: Pick & { diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts index 60427a964..645853295 100644 --- a/api/src/contributor/controller.ts +++ b/api/src/contributor/controller.ts @@ -2,7 +2,7 @@ import { Controller, Get } from "routing-controllers"; import { Service } from "typedi"; import { ContributorRepository } from "./repository"; -import { GetContributorsResponseDto } from "./types"; +import { GetContributorsResponse } from "./types"; @Service() @Controller("/Contributors") @@ -10,7 +10,7 @@ export class ContributorController { constructor(private readonly contributorRepository: ContributorRepository) {} @Get("/") - public async getContributors(): Promise { + public async getContributors(): Promise { const contributors = await this.contributorRepository.findForList(); return { diff --git a/api/src/contributor/types.ts b/api/src/contributor/types.ts index 1048b0f71..f0aede29f 100644 --- a/api/src/contributor/types.ts +++ b/api/src/contributor/types.ts @@ -1,9 +1,9 @@ import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; -export interface GetContributorsResponseDto extends GeneralResponseDto { +export interface GetContributorsResponse extends GeneralResponse { contributors: Array< Pick & { projects: Array< diff --git a/api/src/github/controller.ts b/api/src/github/controller.ts index 81a873370..add90ae0f 100644 --- a/api/src/github/controller.ts +++ b/api/src/github/controller.ts @@ -2,7 +2,7 @@ import { Controller, Get } from "routing-controllers"; import { GithubService } from "src/github/service"; import { Service } from "typedi"; -import { GetRateLimitResponseDto } from "./types"; +import { GetRateLimitResponse } from "./types"; @Service() @Controller("/Github") @@ -10,7 +10,7 @@ export class GithubController { constructor(private readonly githubService: GithubService) {} @Get("/RateLimit") - public async getRateLimitInfo(): Promise { + public async getRateLimitInfo(): Promise { const { limit, used, ratio } = await this.githubService.getRateLimit(); return { diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts deleted file mode 100644 index 4884b4511..000000000 --- a/api/src/github/dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { GithubUser } from "./types"; - -export interface GetRepositoryResponse { - name: string; - owner: GithubUser; -} - -interface GithubIssue { - title: string; - user: GithubUser; - labels: string[]; - state: "closed" | "open"; - assignees: GithubUser[]; - updated_at: string; - html_url: string; - pull_request: { html_url: string }; - comments: number; -} -export type GetRepositoryIssuesResponse = GithubIssue[]; diff --git a/api/src/github/service.ts b/api/src/github/service.ts index d34ce55f1..16067c2c9 100644 --- a/api/src/github/service.ts +++ b/api/src/github/service.ts @@ -2,10 +2,11 @@ import { ConfigService } from "src/config/service"; import { FetchService } from "src/fetch/service"; import { Service } from "typedi"; -import { GetRepositoryIssuesResponse, GetRepositoryResponse } from "./dto"; import { GeneralGithubQuery, GetRepositoryInput, + GetRepositoryIssuesResponse, + GetRepositoryResponse, GetUserInput, GitHubListRepositoryMilestonesInput, GithubMilestone, diff --git a/api/src/github/types.ts b/api/src/github/types.ts index 96d6f4006..f5404b5fb 100644 --- a/api/src/github/types.ts +++ b/api/src/github/types.ts @@ -1,6 +1,6 @@ -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; -export interface GithubUser { +interface GithubUser { login: string; name: string; html_url: string; @@ -93,8 +93,26 @@ export interface GitHubRateLimitApiResponse { }; } -export interface GetRateLimitResponseDto extends GeneralResponseDto { +export interface GetRateLimitResponse extends GeneralResponse { limit: number; used: number; ratio: number; } + +export interface GetRepositoryResponse { + name: string; + owner: GithubUser; +} + +interface GithubIssue { + title: string; + user: GithubUser; + labels: string[]; + state: "closed" | "open"; + assignees: GithubUser[]; + updated_at: string; + html_url: string; + pull_request: { html_url: string }; + comments: number; +} +export type GetRepositoryIssuesResponse = GithubIssue[]; diff --git a/api/src/milestone/controller.ts b/api/src/milestone/controller.ts index 92d0a5fa3..c475642c7 100644 --- a/api/src/milestone/controller.ts +++ b/api/src/milestone/controller.ts @@ -2,7 +2,7 @@ import { Controller, Get } from "routing-controllers"; import { GithubService } from "src/github/service"; import { Service } from "typedi"; -import { GetMilestonesResponseDto } from "./types"; +import { GetMilestonesResponse } from "./types"; @Service() @Controller("/Milestones") @@ -10,7 +10,7 @@ export class MilestoneController { constructor(private readonly githubService: GithubService) {} @Get("/dzcode") - public async getMilestones(): Promise { + public async getMilestones(): Promise { const githubMilestones = await this.githubService.listRepositoryMilestones({ owner: "dzcode-io", repository: "dzcode.io", diff --git a/api/src/milestone/types.ts b/api/src/milestone/types.ts index 628e43ab3..919ba7699 100644 --- a/api/src/milestone/types.ts +++ b/api/src/milestone/types.ts @@ -1,6 +1,6 @@ import { MilestoneEntity } from "@dzcode.io/models/dist/milestone"; -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; -export interface GetMilestonesResponseDto extends GeneralResponseDto { +export interface GetMilestonesResponse extends GeneralResponse { milestones: MilestoneEntity[]; } diff --git a/api/src/project/controller.ts b/api/src/project/controller.ts index a0fde7753..9838d9f10 100644 --- a/api/src/project/controller.ts +++ b/api/src/project/controller.ts @@ -2,7 +2,7 @@ import { Controller, Get } from "routing-controllers"; import { Service } from "typedi"; import { ProjectRepository } from "./repository"; -import { GetProjectsResponseDto } from "./types"; +import { GetProjectsResponse } from "./types"; @Service() @Controller("/Projects") @@ -10,7 +10,7 @@ export class ProjectController { constructor(private readonly projectRepository: ProjectRepository) {} @Get("/") - public async getProjects(): Promise { + public async getProjects(): Promise { const projects = await this.projectRepository.findForList(); return { diff --git a/api/src/project/types.ts b/api/src/project/types.ts index ccd5d703f..70b9017d3 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -1,8 +1,8 @@ import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -import { GeneralResponseDto } from "src/app/types"; +import { GeneralResponse } from "src/app/types"; -export interface GetProjectsResponseDto extends GeneralResponseDto { +export interface GetProjectsResponse extends GeneralResponse { projects: Array< Pick & { repositories: Array>; diff --git a/packages/models/package.json b/packages/models/package.json index d992b80da..7fa1155c4 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -39,7 +39,7 @@ "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --log-level warn", "test": "yarn build && yarn test:alone", - "test:alone": "jest --config ../tooling/jest.config.ts --rootDir .", + "test:alone": "jest --passWithNoTests --config ../tooling/jest.config.ts --rootDir .", "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" } } diff --git a/packages/models/src/_test/index.ts b/packages/models/src/_test/index.ts deleted file mode 100644 index 2bcfac264..000000000 --- a/packages/models/src/_test/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { OptionalPropertiesOf, RequiredPropertiesOf } from "@dzcode.io/utils/dist/ts"; -import { ClassConstructor, plainToClass } from "class-transformer"; -import { validateSync } from "class-validator"; -import { BaseEntity } from "src/_base"; - -export type Cases = [ - [string, RequiredPropertiesOf, false], - [string, Required, false], - [string, Record, boolean], -]; - -export const runDTOTestCases = ( - entity: ClassConstructor, - requiredFields: RequiredPropertiesOf, - optionalFields: OptionalPropertiesOf, -): void => { - const hasNoRequiredFields = Object.keys(requiredFields).length === 0; - const cases: Cases = [ - ["should match snapshot when providing required fields only", requiredFields, false], - [ - "should match snapshot when providing all fields", - { ...requiredFields, ...optionalFields } as Required, - false, - ], - [ - hasNoRequiredFields - ? "should match snapshot when providing an empty object" - : "should show an error that matches snapshot when passing empty object", - {}, - !hasNoRequiredFields, - ], - ]; - - it.each(cases)("%s", (name, input, expectErrors) => { - const output = plainToClass(entity, input); - const errors = validateSync(output); - - expect(output).toMatchSnapshot("output"); - expect(errors).toMatchSnapshot("errors"); - if (expectErrors) { - expect(errors.length).not.toBeLessThanOrEqual(0); - } else { - expect(errors.length).toBe(0); - } - }); -}; diff --git a/packages/models/src/account/__snapshots__/index.spec.ts.snap b/packages/models/src/account/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 9fe2b605a..000000000 --- a/packages/models/src/account/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,78 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -AccountEntity { - "avatarUrl": "https://avatars.githubusercontent.com/u/20110076?v=4", - "id": "github/20110076", - "name": "Zakaria Mansouri", - "profileUrl": "/Account/github/20110076", - "repositories": [], - "username": "ZibanPirate", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -AccountEntity { - "avatarUrl": "https://avatars.githubusercontent.com/u/20110076?v=4", - "id": "github/20110076", - "name": "Zakaria Mansouri", - "profileUrl": "/Account/github/20110076", - "username": "ZibanPirate", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "id must be a string", - }, - "property": "id", - "target": AccountEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "username must be a string", - }, - "property": "username", - "target": AccountEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "name must be a string", - }, - "property": "name", - "target": AccountEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isUrl": "profileUrl must be an URL address", - }, - "property": "profileUrl", - "target": AccountEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isUrl": "avatarUrl must be an URL address", - }, - "property": "avatarUrl", - "target": AccountEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `AccountEntity {}`; diff --git a/packages/models/src/account/index.spec.ts b/packages/models/src/account/index.spec.ts deleted file mode 100644 index fd54565c0..000000000 --- a/packages/models/src/account/index.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { AccountEntity } from "."; - -runDTOTestCases( - AccountEntity, - { - id: "github/20110076", - username: "ZibanPirate", - name: "Zakaria Mansouri", - profileUrl: "/Account/github/20110076", - avatarUrl: "https://avatars.githubusercontent.com/u/20110076?v=4", - }, - { - repositories: [], - }, -); diff --git a/packages/models/src/account/index.ts b/packages/models/src/account/index.ts deleted file mode 100644 index 7f63f0980..000000000 --- a/packages/models/src/account/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Type } from "class-transformer"; -import { IsString, IsUrl, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { RepositoryReferenceEntity } from "src/repository-reference"; - -export class AccountEntity extends BaseEntity { - @IsString() - id!: string; - - @IsString() - username!: string; - - @IsString() - name!: string; - - // eslint-disable-next-line camelcase - @IsUrl({ require_protocol: false, require_host: false }) - profileUrl!: string; - - @IsUrl() - avatarUrl!: string; - - @ValidateNested({ each: true }) - @Type(() => RepositoryReferenceEntity) - repositories?: Model[]; -} diff --git a/packages/models/src/article/__snapshots__/index.spec.ts.snap b/packages/models/src/article/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 6f7270641..000000000 --- a/packages/models/src/article/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -ArticleEntity { - "authors": [], - "content": "test-content", - "contributors": [], - "description": "test-description", - "image": "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - "slug": "learn/Getting_Started", - "title": "Getting Started", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -ArticleEntity { - "authors": [], - "content": "test-content", - "contributors": [], - "description": "test-description", - "image": "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - "slug": "learn/Getting_Started", - "title": "Getting Started", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "image must be a string", - }, - "property": "image", - "target": ArticleEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "description must be a string", - }, - "property": "description", - "target": ArticleEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "content must be a string", - }, - "property": "content", - "target": ArticleEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "slug must be a string", - }, - "property": "slug", - "target": ArticleEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "title must be a string", - }, - "property": "title", - "target": ArticleEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `ArticleEntity {}`; diff --git a/packages/models/src/article/index.spec.ts b/packages/models/src/article/index.spec.ts deleted file mode 100644 index baf5b259f..000000000 --- a/packages/models/src/article/index.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { ArticleEntity } from "."; - -runDTOTestCases( - ArticleEntity, - { - image: - "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - slug: "learn/Getting_Started", - title: "Getting Started", - description: "test-description", - content: "test-content", - authors: [], - contributors: [], - }, - {}, -); diff --git a/packages/models/src/article/index.ts b/packages/models/src/article/index.ts deleted file mode 100644 index 16f77445d..000000000 --- a/packages/models/src/article/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Type } from "class-transformer"; -import { IsString, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { AccountEntity } from "src/account"; - -export class ArticleInfoEntity extends BaseEntity { - @IsString() - slug!: string; - - @IsString() - title!: string; -} - -export class ArticleEntity extends ArticleInfoEntity { - @IsString() - image!: string; - - @IsString() - description!: string; - - @IsString() - content!: string; - - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - authors!: Model[]; - - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - contributors!: Model[]; -} diff --git a/packages/models/src/documentation/__snapshots__/index.spec.ts.snap b/packages/models/src/documentation/__snapshots__/index.spec.ts.snap deleted file mode 100644 index d899429e9..000000000 --- a/packages/models/src/documentation/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -DocumentationEntity { - "authors": [], - "content": "test-content", - "contributors": [], - "description": "test-description", - "image": "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - "slug": "learn/Getting_Started", - "title": "Getting Started", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -DocumentationEntity { - "authors": [], - "content": "test-content", - "contributors": [], - "description": "test-description", - "image": "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - "slug": "learn/Getting_Started", - "title": "Getting Started", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "image must be a string", - }, - "property": "image", - "target": DocumentationEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "description must be a string", - }, - "property": "description", - "target": DocumentationEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "content must be a string", - }, - "property": "content", - "target": DocumentationEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "slug must be a string", - }, - "property": "slug", - "target": DocumentationEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "title must be a string", - }, - "property": "title", - "target": DocumentationEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `DocumentationEntity {}`; diff --git a/packages/models/src/documentation/index.spec.ts b/packages/models/src/documentation/index.spec.ts deleted file mode 100644 index 8ed27367e..000000000 --- a/packages/models/src/documentation/index.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { DocumentationEntity } from "."; - -runDTOTestCases( - DocumentationEntity, - { - image: - "https://images.unsplash.com/photo-1520338661084-680395057c93?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=formatfit=crop&w=800&q=100", - slug: "learn/Getting_Started", - title: "Getting Started", - description: "test-description", - content: "test-content", - authors: [], - contributors: [], - }, - {}, -); diff --git a/packages/models/src/documentation/index.ts b/packages/models/src/documentation/index.ts deleted file mode 100644 index a2a74bccf..000000000 --- a/packages/models/src/documentation/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Type } from "class-transformer"; -import { IsString, ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { AccountEntity } from "src/account"; - -export class DocumentationInfoEntity extends BaseEntity { - @IsString() - slug!: string; - - @IsString() - title!: string; -} - -export class DocumentationEntity extends DocumentationInfoEntity { - @IsString() - image!: string; - - @IsString() - description!: string; - - @IsString() - content!: string; - - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - authors!: Model[]; - - @ValidateNested({ each: true }) - @Type(() => AccountEntity) - contributors!: Model[]; -} diff --git a/packages/models/src/language/__snapshots__/index.spec.ts.snap b/packages/models/src/language/__snapshots__/index.spec.ts.snap deleted file mode 100644 index a9b1c2a83..000000000 --- a/packages/models/src/language/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,83 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot of allLanguages: allLanguages 1`] = ` -[ - { - "code": "en", - "direction": "ltr", - "label": "English", - "shortLabel": "EN", - }, - { - "code": "ar", - "direction": "rtl", - "label": "العربية", - "shortLabel": "ع", - }, -] -`; - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -LanguageEntity { - "code": "en", - "direction": "ltr", - "label": "English", - "shortLabel": "EN", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -LanguageEntity { - "code": "en", - "direction": "ltr", - "label": "English", - "shortLabel": "EN", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isIn": "code must be one of the following values: en, ar", - }, - "property": "code", - "target": LanguageEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isIn": "shortLabel must be one of the following values: EN, ع", - }, - "property": "shortLabel", - "target": LanguageEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isIn": "label must be one of the following values: English, العربية", - }, - "property": "label", - "target": LanguageEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isIn": "direction must be one of the following values: ltr, rtl", - }, - "property": "direction", - "target": LanguageEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `LanguageEntity {}`; diff --git a/packages/models/src/language/index.spec.ts b/packages/models/src/language/index.spec.ts deleted file mode 100644 index 533a00e3b..000000000 --- a/packages/models/src/language/index.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { allLanguages, LanguageEntity } from "."; - -runDTOTestCases( - LanguageEntity, - { - code: "en", - label: "English", - shortLabel: "EN", - direction: "ltr", - }, - {}, -); - -it("should match snapshot of allLanguages", () => { - expect(allLanguages).toMatchSnapshot("allLanguages"); -}); diff --git a/packages/models/src/milestone/__snapshots__/index.spec.ts.snap b/packages/models/src/milestone/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 73f1b4b3d..000000000 --- a/packages/models/src/milestone/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,112 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -MilestoneEntity { - "closedAt": "2022-08-30T09:55:46Z", - "closedIssuesCount": 2, - "createdAt": "2022-08-18T09:55:46Z", - "description": "Adding an Arabic version of dzcode.io", - "dueAt": "2022-08-30T09:55:46Z", - "id": "8320713", - "openIssuesCount": 4, - "status": "open", - "title": "Localization (web)", - "url": "https://github.com/dzcode-io/dzcode.io/milestone/9", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -MilestoneEntity { - "closedIssuesCount": 2, - "createdAt": "2022-08-18T09:55:46Z", - "description": "Adding an Arabic version of dzcode.io", - "id": "8320713", - "openIssuesCount": 4, - "status": "open", - "title": "Localization (web)", - "url": "https://github.com/dzcode-io/dzcode.io/milestone/9", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "id must be a string", - }, - "property": "id", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "title must be a string", - }, - "property": "title", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "description must be a string", - }, - "property": "description", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isUrl": "url must be an URL address", - }, - "property": "url", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isNumber": "openIssuesCount must be a number conforming to the specified constraints", - }, - "property": "openIssuesCount", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isNumber": "closedIssuesCount must be a number conforming to the specified constraints", - }, - "property": "closedIssuesCount", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "status must be a string", - }, - "property": "status", - "target": MilestoneEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isDateString": "createdAt must be a valid ISO 8601 date string", - }, - "property": "createdAt", - "target": MilestoneEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `MilestoneEntity {}`; diff --git a/packages/models/src/milestone/index.spec.ts b/packages/models/src/milestone/index.spec.ts deleted file mode 100644 index eea290f35..000000000 --- a/packages/models/src/milestone/index.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { MilestoneEntity } from "."; - -runDTOTestCases( - MilestoneEntity, - { - id: "8320713", - title: "Localization (web)", - description: "Adding an Arabic version of dzcode.io", - url: "https://github.com/dzcode-io/dzcode.io/milestone/9", - status: "open", - closedIssuesCount: 2, - openIssuesCount: 4, - createdAt: "2022-08-18T09:55:46Z", - }, - { - closedAt: "2022-08-30T09:55:46Z", - dueAt: "2022-08-30T09:55:46Z", - }, -); diff --git a/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap b/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 7f3643bbd..000000000 --- a/packages/models/src/project-reference/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,68 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -ProjectReferenceEntity { - "name": "Leblad", - "repositories": [ - RepositoryReferenceEntity { - "name": "leblad", - "owner": "dzcode-io", - "provider": "github", - }, - RepositoryReferenceEntity { - "name": "leblad-py", - "owner": "abderrahmaneMustapha", - "provider": "github", - }, - ], - "slug": "Leblad", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -ProjectReferenceEntity { - "name": "Leblad", - "repositories": [ - RepositoryReferenceEntity { - "name": "leblad", - "owner": "dzcode-io", - "provider": "github", - }, - RepositoryReferenceEntity { - "name": "leblad-py", - "owner": "abderrahmaneMustapha", - "provider": "github", - }, - ], - "slug": "Leblad", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "slug must be a string", - }, - "property": "slug", - "target": ProjectReferenceEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "name must be a string", - }, - "property": "name", - "target": ProjectReferenceEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `ProjectReferenceEntity {}`; diff --git a/packages/models/src/project-reference/index.spec.ts b/packages/models/src/project-reference/index.spec.ts deleted file mode 100644 index 585118374..000000000 --- a/packages/models/src/project-reference/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { ProjectReferenceEntity } from "."; - -runDTOTestCases( - ProjectReferenceEntity, - { - name: "Leblad", - repositories: [ - { - provider: "github", - owner: "dzcode-io", - name: "leblad", - }, - { - provider: "github", - owner: "abderrahmaneMustapha", - name: "leblad-py", - }, - ], - slug: "Leblad", - }, - {}, -); diff --git a/packages/models/src/project-reference/index.ts b/packages/models/src/project-reference/index.ts deleted file mode 100644 index 827cc1714..000000000 --- a/packages/models/src/project-reference/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Type } from "class-transformer"; -import { IsString } from "class-validator"; -import { ValidateNested } from "class-validator"; -import { BaseEntity, Model } from "src/_base"; -import { RepositoryReferenceEntity } from "src/repository-reference"; - -// @TODO-ZM: remove this -export class ProjectReferenceEntity extends BaseEntity { - @IsString() - slug!: string; - @IsString() - name!: string; - - @ValidateNested({ each: true }) - @Type(() => RepositoryReferenceEntity) - repositories!: Model[]; -} diff --git a/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap b/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 15c39ca2f..000000000 --- a/packages/models/src/repository-reference/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should match snapshot when providing all fields: errors 1`] = `[]`; - -exports[`should match snapshot when providing all fields: output 1`] = ` -RepositoryReferenceEntity { - "contributions": [], - "contributors": [], - "name": "leblad", - "owner": "dzcode-io", - "provider": "github", -} -`; - -exports[`should match snapshot when providing required fields only: errors 1`] = `[]`; - -exports[`should match snapshot when providing required fields only: output 1`] = ` -RepositoryReferenceEntity { - "name": "leblad", - "owner": "dzcode-io", - "provider": "github", -} -`; - -exports[`should show an error that matches snapshot when passing empty object: errors 1`] = ` -[ - ValidationError { - "children": [], - "constraints": { - "isString": "provider must be a string", - }, - "property": "provider", - "target": RepositoryReferenceEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "owner must be a string", - }, - "property": "owner", - "target": RepositoryReferenceEntity {}, - "value": undefined, - }, - ValidationError { - "children": [], - "constraints": { - "isString": "name must be a string", - }, - "property": "name", - "target": RepositoryReferenceEntity {}, - "value": undefined, - }, -] -`; - -exports[`should show an error that matches snapshot when passing empty object: output 1`] = `RepositoryReferenceEntity {}`; diff --git a/packages/models/src/repository-reference/index.spec.ts b/packages/models/src/repository-reference/index.spec.ts deleted file mode 100644 index 68215db67..000000000 --- a/packages/models/src/repository-reference/index.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { runDTOTestCases } from "src/_test"; - -import { RepositoryReferenceEntity } from "."; - -runDTOTestCases( - RepositoryReferenceEntity, - { - provider: "github", - owner: "dzcode-io", - name: "leblad", - }, - { - contributions: [], - contributors: [], - }, -); diff --git a/packages/models/src/repository-reference/index.ts b/packages/models/src/repository-reference/index.ts deleted file mode 100644 index 532a3a1a7..000000000 --- a/packages/models/src/repository-reference/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsString } from "class-validator"; -import { BaseEntity } from "src/_base"; - -// @TODO-ZM: to remove this -export class RepositoryReferenceEntity extends BaseEntity { - @IsString() - provider!: "github" | "gitlab"; - - @IsString() - owner!: string; - - @IsString() - name!: string; -} diff --git a/web/src/redux/slices/contributions-page.ts b/web/src/redux/slices/contributions-page.ts index f945061d3..4cbeaa9e7 100644 --- a/web/src/redux/slices/contributions-page.ts +++ b/web/src/redux/slices/contributions-page.ts @@ -1,11 +1,11 @@ -import { GetContributionsResponseDto } from "@dzcode.io/api/dist/contribution/types"; +import { GetContributionsResponse } from "@dzcode.io/api/dist/contribution/types"; import { createSlice } from "@reduxjs/toolkit"; import { setReducerFactory } from "src/redux/utils"; import { Loadable } from "src/utils/loadable"; // ts-prune-ignore-next export interface ContributionsPageState { - contributionsList: Loadable; + contributionsList: Loadable; } const initialState: ContributionsPageState = { diff --git a/web/src/redux/slices/contributors-page.ts b/web/src/redux/slices/contributors-page.ts index 554642480..d132936a5 100644 --- a/web/src/redux/slices/contributors-page.ts +++ b/web/src/redux/slices/contributors-page.ts @@ -1,11 +1,11 @@ -import { GetContributorsResponseDto } from "@dzcode.io/api/dist/contributor/types"; +import { GetContributorsResponse } from "@dzcode.io/api/dist/contributor/types"; import { createSlice } from "@reduxjs/toolkit"; import { setReducerFactory } from "src/redux/utils"; import { Loadable } from "src/utils/loadable"; // ts-prune-ignore-next export interface ContributorsPageState { - contributorsList: Loadable; + contributorsList: Loadable; } const initialState: ContributorsPageState = { diff --git a/web/src/redux/slices/landing-page.ts b/web/src/redux/slices/landing-page.ts index 4c198d344..066c7e340 100644 --- a/web/src/redux/slices/landing-page.ts +++ b/web/src/redux/slices/landing-page.ts @@ -1,11 +1,11 @@ -import { GetMilestonesResponseDto } from "@dzcode.io/api/dist/milestone/types"; +import { GetMilestonesResponse } from "@dzcode.io/api/dist/milestone/types"; import { createSlice } from "@reduxjs/toolkit"; import { setReducerFactory } from "src/redux/utils"; import { Loadable } from "src/utils/loadable"; // ts-prune-ignore-next export interface LandingPageState { - milestones: Loadable; + milestones: Loadable; } const initialState: LandingPageState = { diff --git a/web/src/redux/slices/projects-page.ts b/web/src/redux/slices/projects-page.ts index 70c157e4f..19250703b 100644 --- a/web/src/redux/slices/projects-page.ts +++ b/web/src/redux/slices/projects-page.ts @@ -1,11 +1,11 @@ -import { GetProjectsResponseDto } from "@dzcode.io/api/dist/project/types"; +import { GetProjectsResponse } from "@dzcode.io/api/dist/project/types"; import { createSlice } from "@reduxjs/toolkit"; import { setReducerFactory } from "src/redux/utils"; import { Loadable } from "src/utils/loadable"; // ts-prune-ignore-next export interface ProjectsPageState { - projectsList: Loadable; + projectsList: Loadable; } const initialState: ProjectsPageState = { From 47031a5a9da4c8feab5e11971b189b2af59e8801 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 20:54:23 +0200 Subject: [PATCH 40/43] the only dto is in api now --- api/package.json | 2 +- packages/models/package.json | 5 +--- packages/models/src/language/index.ts | 18 ++++-------- packages/models/src/milestone/index.ts | 40 ++++++-------------------- yarn.lock | 23 ++++----------- 5 files changed, 21 insertions(+), 67 deletions(-) diff --git a/api/package.json b/api/package.json index 59a976f90..c978f7c83 100644 --- a/api/package.json +++ b/api/package.json @@ -27,7 +27,7 @@ "lodash": "^4.17.21", "make-fetch-happen": "^9.0.2", "morgan": "^1.10.0", - "reflect-metadata": "^0.1.13", + "reflect-metadata": "^0.2.2", "routing-controllers": "^0.9.0", "typedi": "^0.10.0", "winston": "^3.3.3" diff --git a/packages/models/package.json b/packages/models/package.json index 7fa1155c4..4cc8a384e 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -8,10 +8,7 @@ "url": "https://zakiii.com/" }, "dependencies": { - "@dzcode.io/utils": "*", - "class-transformer": "^0.5.1", - "class-validator": "^0.13.2", - "reflect-metadata": "^0.1.13" + "@dzcode.io/utils": "*" }, "devDependencies": { "@dzcode.io/tooling": "*" diff --git a/packages/models/src/language/index.ts b/packages/models/src/language/index.ts index 745561bdb..eb1c57768 100644 --- a/packages/models/src/language/index.ts +++ b/packages/models/src/language/index.ts @@ -1,4 +1,3 @@ -import { IsIn } from "class-validator"; import { BaseEntity } from "src/_base"; export const allLanguages = [ @@ -6,16 +5,9 @@ export const allLanguages = [ { code: "ar", shortLabel: "ع", label: "العربية", direction: "rtl" }, ] as const; -export class LanguageEntity extends BaseEntity { - @IsIn(allLanguages.map(({ code }) => code)) - code!: (typeof allLanguages)[number]["code"]; - - @IsIn(allLanguages.map(({ shortLabel }) => shortLabel)) - shortLabel!: (typeof allLanguages)[number]["shortLabel"]; - - @IsIn(allLanguages.map(({ label }) => label)) - label!: (typeof allLanguages)[number]["label"]; - - @IsIn(allLanguages.map(({ direction }) => direction)) - direction!: (typeof allLanguages)[number]["direction"]; +export interface LanguageEntity extends BaseEntity { + code: (typeof allLanguages)[number]["code"]; + shortLabel: (typeof allLanguages)[number]["shortLabel"]; + label: (typeof allLanguages)[number]["label"]; + direction: (typeof allLanguages)[number]["direction"]; } diff --git a/packages/models/src/milestone/index.ts b/packages/models/src/milestone/index.ts index bf9892f2d..ff9e41b5b 100644 --- a/packages/models/src/milestone/index.ts +++ b/packages/models/src/milestone/index.ts @@ -1,36 +1,14 @@ -import { IsDateString, IsNumber, IsOptional, IsString, IsUrl } from "class-validator"; import { BaseEntity } from "src/_base"; -export class MilestoneEntity extends BaseEntity { - @IsString() - id!: string; - - @IsString() - title!: string; - - @IsString() - description!: string; - - @IsUrl() - url!: string; - - @IsNumber() - openIssuesCount!: number; - - @IsNumber() - closedIssuesCount!: number; - - @IsString() - status!: "open" | "closed" | "in-progress"; - - @IsDateString() - createdAt!: string; - - @IsDateString() - @IsOptional() +export interface MilestoneEntity extends BaseEntity { + id: string; + title: string; + description: string; + url: string; + openIssuesCount: number; + closedIssuesCount: number; + status: "open" | "closed" | "in-progress"; + createdAt: string; dueAt?: string; - - @IsDateString() - @IsOptional() closedAt?: string; } diff --git a/yarn.lock b/yarn.lock index 2a830a4da..f6166e4ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4795,14 +4795,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -class-validator@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" - integrity sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw== - dependencies: - libphonenumber-js "^1.9.43" - validator "^13.7.0" - class-validator@^0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" @@ -9673,11 +9665,6 @@ libphonenumber-js@^1.10.53: resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz#efe4fcf816e1982925e9c800d0013b0ee99b8283" integrity sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A== -libphonenumber-js@^1.9.43: - version "1.9.43" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.43.tgz#2371e4383e6780990381d5b900b8c22666221cbb" - integrity sha512-tNB87ZutAiAkl3DE/Bo0Mxqn/XZbNxhPg4v9bYBwQQW4dlhBGqXl1vtmPxeDWbrijzwOA9vRjOOFm5V9SK/W3w== - libsodium-wrappers@^0.7.10: version "0.7.15" resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz#53f13e483820272a3d55b23be2e34402ac988055" @@ -12838,6 +12825,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -15075,11 +15067,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -validator@^13.7.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" - integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== - validator@^13.9.0: version "13.12.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" From f297f2e8d5d04b78155036566f2e54d8d1ede348 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 21:02:54 +0200 Subject: [PATCH 41/43] models extends BaseEntity --- api/src/contribution/table.ts | 3 +-- api/src/contributor/table.ts | 3 +-- api/src/project/table.ts | 3 +-- api/src/repository/table.ts | 3 +-- packages/models/src/_base/index.ts | 16 +++------------- packages/models/src/contribution/index.ts | 6 +++--- packages/models/src/contributor/index.ts | 7 +++---- packages/models/src/language/index.ts | 4 +--- packages/models/src/milestone/index.ts | 5 ++--- packages/models/src/project/index.ts | 7 +++---- packages/models/src/repository/index.ts | 10 +++------- packages/utils/src/ts/index.ts | 4 ---- 12 files changed, 22 insertions(+), 49 deletions(-) diff --git a/api/src/contribution/table.ts b/api/src/contribution/table.ts index 03d715cfe..e8501a1a4 100644 --- a/api/src/contribution/table.ts +++ b/api/src/contribution/table.ts @@ -1,4 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; import { sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; @@ -24,6 +23,6 @@ export const contributionsTable = sqliteTable("contributions", { .references(() => contributorsTable.id), }); -contributionsTable.$inferSelect satisfies Model; +contributionsTable.$inferSelect satisfies ContributionEntity; export type ContributionRow = typeof contributionsTable.$inferInsert; diff --git a/api/src/contributor/table.ts b/api/src/contributor/table.ts index 571200fde..4a6cd366d 100644 --- a/api/src/contributor/table.ts +++ b/api/src/contributor/table.ts @@ -1,4 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { ContributorEntity } from "@dzcode.io/models/dist/contributor"; import { sql } from "drizzle-orm"; import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; @@ -16,7 +15,7 @@ export const contributorsTable = sqliteTable("contributors", { avatarUrl: text("avatar_url").notNull(), }); -contributorsTable.$inferSelect satisfies Model; +contributorsTable.$inferSelect satisfies ContributorEntity; export type ContributorRow = typeof contributorsTable.$inferInsert; diff --git a/api/src/project/table.ts b/api/src/project/table.ts index 14176a04a..6301943a3 100644 --- a/api/src/project/table.ts +++ b/api/src/project/table.ts @@ -1,4 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { ProjectEntity } from "@dzcode.io/models/dist/project"; import { sql } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; @@ -13,6 +12,6 @@ export const projectsTable = sqliteTable("projects", { runId: text("run_id").notNull().default("initial-run-id"), }); -projectsTable.$inferSelect satisfies Model; +projectsTable.$inferSelect satisfies ProjectEntity; export type ProjectRow = typeof projectsTable.$inferInsert; diff --git a/api/src/repository/table.ts b/api/src/repository/table.ts index e2ec1ec4a..c2a3d620c 100644 --- a/api/src/repository/table.ts +++ b/api/src/repository/table.ts @@ -1,4 +1,3 @@ -import { Model } from "@dzcode.io/models/dist/_base"; import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; import { sql } from "drizzle-orm"; import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core"; @@ -26,6 +25,6 @@ export const repositoriesTable = sqliteTable( }, ); -repositoriesTable.$inferSelect satisfies Model; +repositoriesTable.$inferSelect satisfies RepositoryEntity; export type RepositoryRow = typeof repositoriesTable.$inferInsert; diff --git a/packages/models/src/_base/index.ts b/packages/models/src/_base/index.ts index d1c6f4492..e3c851e25 100644 --- a/packages/models/src/_base/index.ts +++ b/packages/models/src/_base/index.ts @@ -1,14 +1,4 @@ -import "reflect-metadata"; - -import { KeysMatching } from "@dzcode.io/utils/dist/ts"; - -export class BaseEntity { - private _entity?: undefined; +export interface BaseEntity { + id: number; + runId: string; } - -export type Model = never> = Pick< - ENTITY, - Exclude> -> & - Pick, RELATIONS> & - BaseEntity; diff --git a/packages/models/src/contribution/index.ts b/packages/models/src/contribution/index.ts index 7dbcb0de7..046e82c6c 100644 --- a/packages/models/src/contribution/index.ts +++ b/packages/models/src/contribution/index.ts @@ -1,9 +1,9 @@ -export interface ContributionEntity { - id: number; +import { BaseEntity } from "src/_base"; + +export interface ContributionEntity extends BaseEntity { title: string; type: "ISSUE" | "PULL_REQUEST"; url: string; updatedAt: string; activityCount: number; - runId: string; } diff --git a/packages/models/src/contributor/index.ts b/packages/models/src/contributor/index.ts index 08bce9e5b..5c63a438d 100644 --- a/packages/models/src/contributor/index.ts +++ b/packages/models/src/contributor/index.ts @@ -1,7 +1,6 @@ -export interface ContributorEntity { - // @TODO-ZM: move this to BaseEntity - id: number; - runId: string; +import { BaseEntity } from "src/_base"; + +export interface ContributorEntity extends BaseEntity { name: string; username: string; url: string; diff --git a/packages/models/src/language/index.ts b/packages/models/src/language/index.ts index eb1c57768..e56b4c4ea 100644 --- a/packages/models/src/language/index.ts +++ b/packages/models/src/language/index.ts @@ -1,11 +1,9 @@ -import { BaseEntity } from "src/_base"; - export const allLanguages = [ { code: "en", shortLabel: "EN", label: "English", direction: "ltr" }, { code: "ar", shortLabel: "ع", label: "العربية", direction: "rtl" }, ] as const; -export interface LanguageEntity extends BaseEntity { +export interface LanguageEntity { code: (typeof allLanguages)[number]["code"]; shortLabel: (typeof allLanguages)[number]["shortLabel"]; label: (typeof allLanguages)[number]["label"]; diff --git a/packages/models/src/milestone/index.ts b/packages/models/src/milestone/index.ts index ff9e41b5b..8878a3b72 100644 --- a/packages/models/src/milestone/index.ts +++ b/packages/models/src/milestone/index.ts @@ -1,6 +1,5 @@ -import { BaseEntity } from "src/_base"; - -export interface MilestoneEntity extends BaseEntity { +// TODO-ZM: digest this to database later (then extends BaseEntity) +export interface MilestoneEntity { id: string; title: string; description: string; diff --git a/packages/models/src/project/index.ts b/packages/models/src/project/index.ts index 744bb62a2..64955e455 100644 --- a/packages/models/src/project/index.ts +++ b/packages/models/src/project/index.ts @@ -1,7 +1,6 @@ -export interface ProjectEntity { - // @TODO-ZM: move this to BaseEntity - id: number; +import { BaseEntity } from "src/_base"; + +export interface ProjectEntity extends BaseEntity { slug: string; name: string; - runId: string; } diff --git a/packages/models/src/repository/index.ts b/packages/models/src/repository/index.ts index 6fd42de19..16283051f 100644 --- a/packages/models/src/repository/index.ts +++ b/packages/models/src/repository/index.ts @@ -1,11 +1,7 @@ -const RepositoryProviders = ["github", "gitlab"] as const; -type RepositoryProvider = (typeof RepositoryProviders)[number]; +import { BaseEntity } from "src/_base"; -export interface RepositoryEntity { - // @TODO-ZM: move this to BaseEntity - id: number; +export interface RepositoryEntity extends BaseEntity { owner: string; name: string; - runId: string; - provider: RepositoryProvider; + provider: "github" | "gitlab"; } diff --git a/packages/utils/src/ts/index.ts b/packages/utils/src/ts/index.ts index e51728a96..2d971000b 100644 --- a/packages/utils/src/ts/index.ts +++ b/packages/utils/src/ts/index.ts @@ -1,9 +1,5 @@ export type Flatten = T extends any[] ? T[number] : T; // eslint-disable-line @typescript-eslint/no-explicit-any -export type KeysMatching = { - [K in keyof Required]: Flatten[K]> extends V ? K : never; -}[keyof Required]; - export type OptionalPropertiesOf = Required< Pick< T, From 430557bd30a0e32ef86bdcad351724462689f373 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 8 Sep 2024 21:04:16 +0200 Subject: [PATCH 42/43] no languages and labels for now --- web/src/pages/contribute/index.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/web/src/pages/contribute/index.tsx b/web/src/pages/contribute/index.tsx index abf8924aa..ad91cd40e 100644 --- a/web/src/pages/contribute/index.tsx +++ b/web/src/pages/contribute/index.tsx @@ -59,18 +59,6 @@ export default function Page(): JSX.Element { {contribution.repository.owner}/{contribution.repository.name} - {/*
- {contribution.labels.map((label, labelIndex) => ( - - {label} - - ))} - {contribution.languages.map((language, languageIndex) => ( - - {language} - - ))} -
*/}
Date: Sun, 8 Sep 2024 22:50:58 +0200 Subject: [PATCH 43/43] sort projects based on score --- api/src/project/repository.ts | 27 +++++++++++++++++++-------- api/src/project/types.ts | 5 ++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/api/src/project/repository.ts b/api/src/project/repository.ts index 3bff57ccd..f5bc511b6 100644 --- a/api/src/project/repository.ts +++ b/api/src/project/repository.ts @@ -1,6 +1,7 @@ import { eq, ne, sql } from "drizzle-orm"; import { camelCaseObject } from "src/_utils/case"; import { unStringifyDeep } from "src/_utils/unstringify-deep"; +import { contributorRepositoryRelationTable } from "src/contributor/table"; import { repositoriesTable } from "src/repository/table"; import { SQLiteService } from "src/sqlite/service"; import { Service } from "typedi"; @@ -12,21 +13,31 @@ export class ProjectRepository { constructor(private readonly sqliteService: SQLiteService) {} public async findForList() { - // @TODO-ZM: reverse hierarchy instead here const statement = sql` SELECT p.id as id, p.name as name, - p.slug as slug, - json_group_array( - json_object('id', r.id, 'owner', r.owner, 'name', r.name) - ) AS repositories + sum(rs.repo_contributor_count) as contributor_count, + sum(rs.repo_score) as activity_count, + 100 * sum(rs.repo_contributor_count) + max(rs.repo_score) - sum(rs.repo_score) / sum(rs.repo_contributor_count) as score, + json_group_array(json_object('id', rs.id, 'owner', rs.owner, 'name', rs.name)) as repositories FROM - ${projectsTable} p + (SELECT + *, + sum(crr.score) as repo_score, + count(*) as repo_contributor_count + FROM + ${contributorRepositoryRelationTable} crr + JOIN + ${repositoriesTable} r ON crr.repository_id = r.id + GROUP BY + r.id) as rs JOIN - ${repositoriesTable} r ON p.id = r.project_id + ${projectsTable} p ON rs.project_id = p.id GROUP BY - p.id; + p.id + ORDER BY + score DESC `; const raw = this.sqliteService.db.all(statement); const unStringifiedRaw = unStringifyDeep(raw); diff --git a/api/src/project/types.ts b/api/src/project/types.ts index 70b9017d3..c099115c3 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -4,8 +4,11 @@ import { GeneralResponse } from "src/app/types"; export interface GetProjectsResponse extends GeneralResponse { projects: Array< - Pick & { + Pick & { repositories: Array>; + contributor_count: number; + activity_count: number; + score: number; } >; }