diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index d2b3d08e..37b8be10 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -1,8 +1,3 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ - import {Logger, ValidationPipe} from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; diff --git a/apps/backend/src/services/auth/auth.service.ts b/apps/backend/src/services/auth/auth.service.ts index 3a913cfd..e58ec775 100644 --- a/apps/backend/src/services/auth/auth.service.ts +++ b/apps/backend/src/services/auth/auth.service.ts @@ -41,7 +41,7 @@ export class AuthService { } private async loginOrRegisterProvider(provider: Provider, body: LoginUserDto) { - const providerInstance = ProvidersFactory.loadProviders(provider); + const providerInstance = ProvidersFactory.loadProvider(provider); const providerUser = await providerInstance.getUser(body.providerToken); if (!providerUser) { throw new Error('Invalid provider token'); diff --git a/apps/backend/src/services/auth/providers/providers.factory.ts b/apps/backend/src/services/auth/providers/providers.factory.ts index 82c692c9..96af6adb 100644 --- a/apps/backend/src/services/auth/providers/providers.factory.ts +++ b/apps/backend/src/services/auth/providers/providers.factory.ts @@ -3,7 +3,7 @@ import {GithubProvider} from "@gitroom/backend/services/auth/providers/github.pr import {ProvidersInterface} from "@gitroom/backend/services/auth/providers.interface"; export class ProvidersFactory { - static loadProviders(provider: Provider): ProvidersInterface { + static loadProvider(provider: Provider): ProvidersInterface { switch (provider) { case Provider.GITHUB: return new GithubProvider(); diff --git a/apps/cron/.eslintrc.json b/apps/cron/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/apps/cron/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/cron/jest.config.ts b/apps/cron/jest.config.ts new file mode 100644 index 00000000..b12f8f71 --- /dev/null +++ b/apps/cron/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'consumers', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/consumers', +}; diff --git a/apps/cron/project.json b/apps/cron/project.json new file mode 100644 index 00000000..d936df9a --- /dev/null +++ b/apps/cron/project.json @@ -0,0 +1,62 @@ +{ + "name": "cron", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/cron/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "target": "node", + "compiler": "tsc", + "outputPath": "dist/apps/cron", + "main": "apps/cron/src/main.ts", + "tsConfig": "apps/cron/tsconfig.app.json", + "webpackConfig": "apps/cron/webpack.config.js" + }, + "configurations": { + "development": {}, + "production": {} + } + }, + "serve": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "options": { + "buildTarget": "cron:build" + }, + "configurations": { + "development": { + "buildTarget": "cron:build:development" + }, + "production": { + "buildTarget": "cron:build:production" + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/cron/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/cron/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/cron/src/app/tasks/check.trending.ts b/apps/cron/src/app/tasks/check.trending.ts new file mode 100644 index 00000000..38fdc79d --- /dev/null +++ b/apps/cron/src/app/tasks/check.trending.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import {Interval} from '@nestjs/schedule'; +import {isDev} from "@gitroom/helpers/utils/is.dev"; + +@Injectable() +export class CheckTrending { + @Interval(isDev() ? 10000 : 3600000) + CheckTrending() { + console.log('hello'); + } +} \ No newline at end of file diff --git a/apps/cron/src/cron.module.ts b/apps/cron/src/cron.module.ts new file mode 100644 index 00000000..447fbd30 --- /dev/null +++ b/apps/cron/src/cron.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import {CheckTrending} from "./app/tasks/check.trending"; + +@Module({ + imports: [ScheduleModule.forRoot()], + controllers: [], + providers: [CheckTrending], +}) +export class CronModule {} diff --git a/apps/cron/src/main.ts b/apps/cron/src/main.ts new file mode 100644 index 00000000..c561827d --- /dev/null +++ b/apps/cron/src/main.ts @@ -0,0 +1,9 @@ +import { NestFactory } from '@nestjs/core'; +import {CronModule} from "./cron.module"; + +async function bootstrap() { + // some comment again + await NestFactory.createApplicationContext(CronModule); +} + +bootstrap(); diff --git a/apps/cron/tsconfig.app.json b/apps/cron/tsconfig.app.json new file mode 100644 index 00000000..a2ce7652 --- /dev/null +++ b/apps/cron/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2021" + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/apps/cron/tsconfig.json b/apps/cron/tsconfig.json new file mode 100644 index 00000000..c1e2dd4e --- /dev/null +++ b/apps/cron/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/cron/tsconfig.spec.json b/apps/cron/tsconfig.spec.json new file mode 100644 index 00000000..9b2a121d --- /dev/null +++ b/apps/cron/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/apps/cron/webpack.config.js b/apps/cron/webpack.config.js new file mode 100644 index 00000000..c1917ce0 --- /dev/null +++ b/apps/cron/webpack.config.js @@ -0,0 +1,13 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +// Nx plugins for webpack. +module.exports = composePlugins( + withNx({ + target: 'node', + }), + (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); diff --git a/apps/workers/src/main.ts b/apps/workers/src/main.ts index c02a72b7..501d16da 100644 --- a/apps/workers/src/main.ts +++ b/apps/workers/src/main.ts @@ -1,8 +1,3 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ - import { NestFactory } from '@nestjs/core'; import { AppModule } from './app/app.module'; diff --git a/libraries/helpers/src/utils/is.dev.ts b/libraries/helpers/src/utils/is.dev.ts new file mode 100644 index 00000000..fc8d439e --- /dev/null +++ b/libraries/helpers/src/utils/is.dev.ts @@ -0,0 +1,3 @@ +export const isDev = () => { + return process.env.NODE_ENV === 'development' || !process.env.NODE_ENV; +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts index 35d2cfb1..54cd84f9 100644 --- a/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts @@ -24,6 +24,7 @@ export class OrganizationRepository { password: body.password ? AuthService.hashPassword(body.password) : '', providerName: body.provider, providerId: body.providerId || '', + timezone: 0 } } } diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 75b495f3..43db264f 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -11,23 +11,33 @@ datasource db { } model Organization { - id String @id @default(uuid()) - name String - description String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - users UserOrganization[] + id String @id @default(uuid()) + name String + description String? + users UserOrganization[] + media Media[] + paymentId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + github GitHub[] + subscription Subscription? + channel Channel? + tags Tag[] + postTags PostTag[] + postMedia PostMedia[] + post Post[] } model User { - id String @id @default(uuid()) + id String @id @default(uuid()) email String password String? providerName Provider providerId String? organizations UserOrganization[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + timezone Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@unique([email, providerName]) } @@ -43,13 +53,182 @@ model UserOrganization { updatedAt DateTime @updatedAt } +model GitHub { + id String @id @default(uuid()) + login String + name String + token String + jobId String + organization Organization @relation(fields: [organizationId], references: [id]) + organizationId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + stars Star[] + + @@index([login]) +} + +model Trending { + id String @id @default(uuid()) + login String + feed Int + language Int + date DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + @@index([login]) +} + +model Star { + id String @id @default(uuid()) + githubId String + github GitHub @relation(fields: [githubId], references: [id]) + stars Int + totalStars Int + date DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Media { + id String @id @default(uuid()) + name String + url String + organization Organization @relation(fields: [organizationId], references: [id]) + organizationId String + posts PostMedia[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Subscription { + id String @id @default(cuid()) + organizationId String @unique + organization Organization @relation(fields: [organizationId], references: [id]) + subscriptionTier SubscriptionTier + subscriptionState SubscriptionState + identifier String? + cancelAt DateTime? + period Period + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Channel { + id String @id @default(cuid()) + organizationId String @unique + organization Organization @relation(fields: [organizationId], references: [id]) + channelProvider ChannelProvider + type Type + token String + refreshToken String? + additionalData Json? + posts Post[] +} + +model Tag { + id String @id @default(cuid()) + organizationId String + organization Organization @relation(fields: [organizationId], references: [id]) + name String + posts PostTag[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model PostTag { + id String @id @default(cuid()) + organizationId String + organization Organization @relation(fields: [organizationId], references: [id]) + postId String + post Post @relation(fields: [postId], references: [id]) + tagId String + tag Tag @relation(fields: [tagId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model PostMedia { + id String @id @default(cuid()) + organizationId String + organization Organization @relation(fields: [organizationId], references: [id]) + postId String + post Post @relation(fields: [postId], references: [id]) + mediaId String + media Media @relation(fields: [mediaId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Post { + id String @id @default(cuid()) + state State @default(QUEUE) + queueId String? + publishDate DateTime + organizationId String + channelId String + organization Organization @relation(fields: [organizationId], references: [id]) + channel Channel @relation(fields: [channelId], references: [id]) + title String? + description String? + canonicalUrl String? + canonicalPostId String? + parentPostId String? + canonicalPost Post? @relation("canonicalPostId", fields: [canonicalPostId], references: [id]) + parentPost Post? @relation("parentPostId", fields: [parentPostId], references: [id]) + canonicalChildren Post[] @relation("canonicalPostId") + childrenPost Post[] @relation("parentPostId") + tags PostTag[] + media PostMedia[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +enum Type { + ARTICLE + SOCIAL +} + +enum State { + QUEUE + SENT + DRAFT +} + +enum ChannelProvider { + TWITTER + LINKEDIN + DEV + HASHNODE + MEDIUM + HACKERNOON + YOUTUBE + GITHUB + DISCORD +} + +enum SubscriptionTier { + BASIC + PRO +} + +enum SubscriptionState { + ACTIVE + INACTIVE +} + +enum Period { + MONTHLY + YEARLY +} + enum Provider { - LOCAL - GITHUB + LOCAL + GITHUB } enum Role { SUPERADMIN ADMIN USER -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 1786242e..b1531b0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@nestjs/core": "^10.0.2", "@nestjs/microservices": "^10.3.1", "@nestjs/platform-express": "^10.0.2", + "@nestjs/schedule": "^4.0.0", "@prisma/client": "^5.8.1", "@swc/helpers": "~0.5.2", "@types/bcrypt": "^5.0.2", @@ -3581,6 +3582,32 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/schedule": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.0.0.tgz", + "integrity": "sha512-zz4h54m/F/1qyQKvMJCRphmuwGqJltDAkFxUXCVqJBXEs5kbPt93Pza3heCQOcMH22MZNhGlc9DmDMLXVHmgVQ==", + "dependencies": { + "cron": "3.1.3", + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.12" + } + }, + "node_modules/@nestjs/schedule/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nestjs/schematics": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.0.tgz", @@ -6042,6 +6069,11 @@ "@types/node": "*" } }, + "node_modules/@types/luxon": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.8.tgz", + "integrity": "sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -9102,6 +9134,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.3.tgz", + "integrity": "sha512-KVxeKTKYj2eNzN4ElnT6nRSbjbfhyxR92O/Jdp6SH3pc05CDJws59jBrZWEMQlxevCiE6QUTrXy+Im3vC3oD3A==", + "dependencies": { + "@types/luxon": "~3.3.0", + "luxon": "~3.4.0" + } + }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", diff --git a/package.json b/package.json index d39e9daa..e915a614 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "license": "MIT", "scripts": { - "dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers --parallel\"", + "dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers,cron --parallel=4\"", + "cron": "nx run cron:serve:development", "prisma-generate": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma generate", "prisma-db-push": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma db push" }, @@ -13,6 +14,7 @@ "@nestjs/core": "^10.0.2", "@nestjs/microservices": "^10.3.1", "@nestjs/platform-express": "^10.0.2", + "@nestjs/schedule": "^4.0.0", "@prisma/client": "^5.8.1", "@swc/helpers": "~0.5.2", "@types/bcrypt": "^5.0.2", diff --git a/tsconfig.base.json b/tsconfig.base.json index 4b4cad36..a99bbf0e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -21,7 +21,8 @@ "@gitroom/frontend/*": ["apps/frontend/src/*"], "@gitroom/nestjs-libraries/*": ["libraries/nestjs-libraries/src/*"], "@gitroom/helpers/*": ["libraries/helpers/src/*"], - "@gitroom/workers/*": ["apps/workers/src/*"] + "@gitroom/workers/*": ["apps/workers/src/*"], + "@gitroom/cron/*": ["apps/cron/src/*"] } }, "exclude": ["node_modules", "tmp"]