feat: more infrastructure

This commit is contained in:
Nevo David 2024-02-03 23:54:39 +07:00
parent 48eaa9b7d5
commit 00809fa072
82 changed files with 2209 additions and 1053 deletions

View File

@ -6,25 +6,15 @@
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
"rules": {
"@typescript-eslint/no-non-null-asserted-optional-chain": "off",
"@typescript-eslint/no-explicit-any": "off"
}
},
{
"files": ["*.js", "*.jsx"],

View File

@ -1,9 +1,21 @@
import { Module } from '@nestjs/common';
import {MiddlewareConsumer, Module, NestModule} from '@nestjs/common';
import {AuthController} from "@gitroom/backend/api/routes/auth.controller";
import {AuthService} from "@gitroom/backend/services/auth/auth.service";
import {UsersController} from "@gitroom/backend/api/routes/users.controller";
import {AuthMiddleware} from "@gitroom/backend/services/auth/auth.middleware";
const authenticatedController = [
UsersController
];
@Module({
imports: [],
controllers: [AuthController],
controllers: [AuthController, ...authenticatedController],
providers: [AuthService],
})
export class ApiModule {}
export class ApiModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes(...authenticatedController);
}
}

View File

@ -1,4 +1,6 @@
import {Body, Controller, Post} from '@nestjs/common';
import {Body, Controller, Post, Res} from '@nestjs/common';
import {Response} from 'express';
import {CreateOrgUserDto} from "@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto";
import {LoginUserDto} from "@gitroom/nestjs-libraries/dtos/auth/login.user.dto";
import {AuthService} from "@gitroom/backend/services/auth/auth.service";
@ -10,16 +12,45 @@ export class AuthController {
) {
}
@Post('/register')
register(
@Body() body: CreateOrgUserDto
async register(
@Body() body: CreateOrgUserDto,
@Res({ passthrough: true }) response: Response
) {
return this._authService.routeAuth(body.provider, body);
try {
const jwt = await this._authService.routeAuth(body.provider, body);
response.cookie('auth', jwt, {
domain: '.' + new URL(process.env.FRONTEND_URL!).hostname,
secure: true,
httpOnly: true,
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
});
response.header('reload', 'true');
response.status(200).send();
}
catch (e) {
response.status(400).send(e.message);
}
}
@Post('/login')
login(
@Body() body: LoginUserDto
async login(
@Body() body: LoginUserDto,
@Res({ passthrough: true }) response: Response
) {
return this._authService.routeAuth(body.provider, body);
try {
console.log('heghefrgefg');
const jwt = await this._authService.routeAuth(body.provider, body);
response.cookie('auth', jwt, {
domain: '.' + new URL(process.env.FRONTEND_URL!).hostname,
secure: true,
httpOnly: true,
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
});
response.header('reload', 'true');
response.status(200).send();
}
catch (e) {
response.status(400).send(e.message);
}
}
}

View File

@ -0,0 +1,13 @@
import {Controller, Get} from '@nestjs/common';
import {GetUserFromRequest} from "@gitroom/nestjs-libraries/user/user.from.request";
import {User} from "@prisma/client";
@Controller('/user')
export class UsersController {
@Get('/self')
async getSelf(
@GetUserFromRequest() user: User
) {
return user;
}
}

View File

@ -4,11 +4,12 @@ import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database
import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module";
import {AuthService} from "@gitroom/backend/services/auth/auth.service";
import {ApiModule} from "@gitroom/backend/api/api.module";
import {AuthMiddleware} from "@gitroom/backend/services/auth/auth.middleware";
@Module({
imports: [DatabaseModule, RedisModule, ApiModule],
controllers: [],
providers: [AuthService],
providers: [AuthService, AuthMiddleware],
get exports() {
return [...this.imports, ...this.providers];
}

View File

@ -1,12 +1,23 @@
import cookieParser from 'cookie-parser';
import {Logger, ValidationPipe} from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
cors: {
credentials: true,
exposedHeaders: ['reload'],
origin: [process.env.FRONTEND_URL]
}
});
app.useGlobalPipes(new ValidationPipe({
transform: true,
}));
app.use(cookieParser());
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(

View File

@ -0,0 +1,31 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as console from "console";
import {AuthService} from "@gitroom/helpers/auth/auth.service";
import {User} from '@prisma/client';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const auth = req.headers.auth || req.cookies.auth;
if (!auth) {
throw new Error('Unauthorized');
}
try {
const user = AuthService.verifyJWT(auth) as User | null;
if (!user) {
throw new Error('Unauthorized');
}
delete user.password;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
req.user = user;
}
catch (err) {
throw new Error('Unauthorized');
}
console.log('Request...');
next();
}
}

View File

@ -6,6 +6,7 @@ import {UsersService} from "@gitroom/nestjs-libraries/database/prisma/users/user
import {OrganizationService} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.service";
import {AuthService as AuthChecker} from "@gitroom/helpers/auth/auth.service";
import {ProvidersFactory} from "@gitroom/backend/services/auth/providers/providers.factory";
import * as console from "console";
@Injectable()
export class AuthService {

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'consumers',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/consumers',
};

View File

@ -0,0 +1,63 @@
{
"name": "commands",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/commands/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/commands",
"main": "apps/commands/src/main.ts",
"tsConfig": "apps/commands/tsconfig.app.json",
"webpackConfig": "apps/commands/webpack.config.js"
},
"configurations": {
"development": {},
"production": {}
}
},
"command": {
"executor": "nx:run-commands",
"defaultConfiguration": "development",
"options": {
"buildTarget": "commands:build",
"command": "cd dist/apps/commands && node main.js"
},
"configurations": {
"development": {
"buildTarget": "commands:build:development"
},
"production": {
"buildTarget": "commands:build:production"
}
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/commands/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/commands/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}

View File

@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { CommandModule as ExternalCommandModule } from 'nestjs-command';
import {CheckStars} from "./tasks/check.stars";
import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module";
import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module";
import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module";
import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service";
@Module({
imports: [ExternalCommandModule, DatabaseModule, RedisModule, BullMqModule.forRoot({
connection: ioRedis
})],
controllers: [],
providers: [CheckStars],
get exports() {
return [...this.imports, ...this.providers];
}
})
export class CommandModule {}

24
apps/commands/src/main.ts Normal file
View File

@ -0,0 +1,24 @@
import { NestFactory } from '@nestjs/core';
import {CommandModule} from "./command.module";
import {CommandService} from "nestjs-command";
async function bootstrap() {
// some comment again
const app = await NestFactory.createApplicationContext(CommandModule, {
logger: ['error']
});
try {
await app
.select(CommandModule)
.get(CommandService)
.exec();
await app.close()
} catch (error) {
console.error(error);
await app.close();
process.exit(1);
}
}
bootstrap();

View File

@ -0,0 +1,27 @@
import {Command, Positional} from 'nestjs-command';
import { Injectable } from '@nestjs/common';
import {BullMqClient} from "@gitroom/nestjs-libraries/bull-mq-transport/client/bull-mq.client";
import * as console from "console";
@Injectable()
export class CheckStars {
constructor(
private _workerServiceProducer: BullMqClient
) {
}
@Command({
command: 'sync:stars <login>',
describe: 'Sync stars for a login',
})
async create(
@Positional({
name: 'login',
describe: 'login {owner}/{repo}',
type: 'string'
})
login: string,
) {
this._workerServiceProducer.emit('check_stars', {payload: {login}}).subscribe();
return true;
}
}

View File

@ -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"]
}

View File

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@ -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"
]
}

View File

@ -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;
}
);

View File

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import {CheckTrending} from "./app/tasks/check.trending";
import {CheckTrending} from "./tasks/check.trending";
@Module({
imports: [ScheduleModule.forRoot()],

View File

@ -0,0 +1,21 @@
import { Injectable } from '@nestjs/common';
import {Cron} from '@nestjs/schedule';
import {StarsService} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.service";
import {BullMqClient} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-client";
import {WorkerServiceProducer} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-register";
@Injectable()
export class CheckStars {
constructor(
private _starsService: StarsService,
@WorkerServiceProducer() private _workerServiceProducer: BullMqClient
) {
}
@Cron('0 0 * * *')
async checkStars() {
const allGitHubRepositories = await this._starsService.getAllGitHubRepositories();
for (const repository of allGitHubRepositories) {
this._workerServiceProducer.emit('check_stars', JSON.stringify({login: repository.login}));
}
}
}

View File

@ -1,11 +1,10 @@
import { Injectable } from '@nestjs/common';
import {Interval} from '@nestjs/schedule';
import {isDev} from "@gitroom/helpers/utils/is.dev";
@Injectable()
export class CheckTrending {
@Interval(isDev() ? 10000 : 3600000)
CheckTrending() {
@Interval(3600000)
checkTrending() {
console.log('hello');
}
}

View File

@ -1,409 +0,0 @@
html {
-webkit-text-size-adjust: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif,
Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
Liberation Mono, Courier New, monospace;
}
svg {
display: block;
vertical-align: middle;
shape-rendering: auto;
text-rendering: optimizeLegibility;
}
pre {
background-color: rgba(55, 65, 81, 1);
border-radius: 0.25rem;
color: rgba(229, 231, 235, 1);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
Liberation Mono, Courier New, monospace;
overflow: scroll;
padding: 0.5rem 0.75rem;
}
.shadow {
box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.rounded {
border-radius: 1.5rem;
}
.wrapper {
width: 100%;
}
.container {
margin-left: auto;
margin-right: auto;
max-width: 768px;
padding-bottom: 3rem;
padding-left: 1rem;
padding-right: 1rem;
color: rgba(55, 65, 81, 1);
width: 100%;
}
#welcome {
margin-top: 2.5rem;
}
#welcome h1 {
font-size: 3rem;
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1;
}
#welcome span {
display: block;
font-size: 1.875rem;
font-weight: 300;
line-height: 2.25rem;
margin-bottom: 0.5rem;
}
#hero {
align-items: center;
background-color: hsla(214, 62%, 21%, 1);
border: none;
box-sizing: border-box;
color: rgba(55, 65, 81, 1);
display: grid;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#hero .text-container {
color: rgba(255, 255, 255, 1);
padding: 3rem 2rem;
}
#hero .text-container h2 {
font-size: 1.5rem;
line-height: 2rem;
position: relative;
}
#hero .text-container h2 svg {
color: hsla(162, 47%, 50%, 1);
height: 2rem;
left: -0.25rem;
position: absolute;
top: 0;
width: 2rem;
}
#hero .text-container h2 span {
margin-left: 2.5rem;
}
#hero .text-container a {
background-color: rgba(255, 255, 255, 1);
border-radius: 0.75rem;
color: rgba(55, 65, 81, 1);
display: inline-block;
margin-top: 1.5rem;
padding: 1rem 2rem;
text-decoration: inherit;
}
#hero .logo-container {
display: none;
justify-content: center;
padding-left: 2rem;
padding-right: 2rem;
}
#hero .logo-container svg {
color: rgba(255, 255, 255, 1);
width: 66.666667%;
}
#middle-content {
align-items: flex-start;
display: grid;
gap: 4rem;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#learning-materials {
padding: 2.5rem 2rem;
}
#learning-materials h2 {
font-weight: 500;
font-size: 1.25rem;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.list-item-link {
align-items: center;
border-radius: 0.75rem;
display: flex;
margin-top: 1rem;
padding: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 100%;
}
.list-item-link svg:first-child {
margin-right: 1rem;
height: 1.5rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1.5rem;
}
.list-item-link > span {
flex-grow: 1;
font-weight: 400;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link > span > span {
color: rgba(107, 114, 128, 1);
display: block;
flex-grow: 1;
font-size: 0.75rem;
font-weight: 300;
line-height: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link svg:last-child {
height: 1rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1rem;
}
.list-item-link:hover {
color: rgba(255, 255, 255, 1);
background-color: hsla(162, 47%, 50%, 1);
}
.list-item-link:hover > span {
}
.list-item-link:hover > span > span {
color: rgba(243, 244, 246, 1);
}
.list-item-link:hover svg:last-child {
transform: translateX(0.25rem);
}
#other-links {
}
.button-pill {
padding: 1.5rem 2rem;
transition-duration: 300ms;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
align-items: center;
display: flex;
}
.button-pill svg {
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
flex-shrink: 0;
width: 3rem;
}
.button-pill > span {
letter-spacing: -0.025em;
font-weight: 400;
font-size: 1.125rem;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.button-pill span span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
.button-pill:hover svg,
.button-pill:hover {
color: rgba(255, 255, 255, 1) !important;
}
#nx-console:hover {
background-color: rgba(0, 122, 204, 1);
}
#nx-console svg {
color: rgba(0, 122, 204, 1);
}
#nx-console-jetbrains {
margin-top: 2rem;
}
#nx-console-jetbrains:hover {
background-color: rgba(255, 49, 140, 1);
}
#nx-console-jetbrains svg {
color: rgba(255, 49, 140, 1);
}
#nx-repo:hover {
background-color: rgba(24, 23, 23, 1);
}
#nx-repo svg {
color: rgba(24, 23, 23, 1);
}
#nx-cloud {
margin-bottom: 2rem;
margin-top: 2rem;
padding: 2.5rem 2rem;
}
#nx-cloud > div {
align-items: center;
display: flex;
}
#nx-cloud > div svg {
border-radius: 0.375rem;
flex-shrink: 0;
width: 3rem;
}
#nx-cloud > div h2 {
font-size: 1.125rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#nx-cloud > div h2 span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
#nx-cloud p {
font-size: 1rem;
line-height: 1.5rem;
margin-top: 1rem;
}
#nx-cloud pre {
margin-top: 1rem;
}
#nx-cloud a {
color: rgba(107, 114, 128, 1);
display: block;
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 1.5rem;
text-align: right;
}
#nx-cloud a:hover {
text-decoration: underline;
}
#commands {
padding: 2.5rem 2rem;
margin-top: 3.5rem;
}
#commands h2 {
font-size: 1.25rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#commands p {
font-size: 1rem;
font-weight: 300;
line-height: 1.5rem;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}
details {
align-items: center;
display: flex;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
width: 100%;
}
details pre > span {
color: rgba(181, 181, 181, 1);
display: block;
}
summary {
border-radius: 0.5rem;
display: flex;
font-weight: 400;
padding: 0.5rem;
cursor: pointer;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
summary:hover {
background-color: rgba(243, 244, 246, 1);
}
summary svg {
height: 1.5rem;
margin-right: 1rem;
width: 1.5rem;
}
#love {
color: rgba(107, 114, 128, 1);
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 3.5rem;
opacity: 0.6;
text-align: center;
}
#love svg {
color: rgba(252, 165, 165, 1);
width: 1.25rem;
height: 1.25rem;
display: inline;
margin-top: -0.25rem;
}
@media screen and (min-width: 768px) {
#hero {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
#hero .logo-container {
display: flex;
}
#middle-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

View File

@ -1,18 +0,0 @@
import './global.css';
export const metadata = {
title: 'Welcome to frontend',
description: 'Generated by create-nx-workspace',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@ -1,2 +0,0 @@
.page {
}

View File

@ -1,450 +0,0 @@
import styles from './page.module.scss';
export default async function Index() {
/*
* Replace the elements below with your own.
*
* Note: The corresponding styles are in the ./index.scss file.
*/
return (
<div className={styles.page}>
<div className="wrapper">
<div className="container">
<div id="welcome">
<h1>
<span> Hello there, </span>
Welcome frontend 👋
</h1>
</div>
<div id="hero" className="rounded">
<div className="text-container">
<h2>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
<span>You&apos;re up and running</span>
</h2>
<a href="#commands"> What&apos;s next? </a>
</div>
<div className="logo-container">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
</svg>
</div>
</div>
<div id="middle-content">
<div id="learning-materials" className="rounded shadow">
<h2>Learning materials</h2>
<a
href="https://nx.dev/getting-started/intro?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
<span>
Documentation
<span> Everything is in there </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://blog.nrwl.io/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
<span>
Blog
<span> Changelog, features & events </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://www.youtube.com/@NxDevtools/videos?utm_source=nx-project&sub_confirmation=1"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<title>YouTube</title>
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
<span>
YouTube channel
<span> Nx Show, talks & tutorials </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
/>
</svg>
<span>
Interactive tutorials
<span> Create an app, step-by-step </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nxplaybook.com/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 14l9-5-9-5-9 5 9 5z" />
<path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
/>
</svg>
<span>
Video courses
<span> Nx custom courses </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
</div>
<div id="other-links">
<a
id="nx-console"
className="button-pill rounded shadow"
href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Visual Studio Code</title>
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
</svg>
<span>
Install Nx Console for VSCode
<span>The official VSCode extension for Nx.</span>
</span>
</a>
<a
id="nx-console-jetbrains"
className="button-pill rounded shadow"
href="https://plugins.jetbrains.com/plugin/21060-nx-console"
target="_blank"
rel="noreferrer"
>
<svg
height="48"
width="48"
viewBox="20 20 60 60"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m22.5 22.5h60v60h-60z" />
<g fill="#fff">
<path d="m29.03 71.25h22.5v3.75h-22.5z" />
<path d="m28.09 38 1.67-1.58a1.88 1.88 0 0 0 1.47.87c.64 0 1.06-.44 1.06-1.31v-5.98h2.58v6a3.48 3.48 0 0 1 -.87 2.6 3.56 3.56 0 0 1 -2.57.95 3.84 3.84 0 0 1 -3.34-1.55z" />
<path d="m36 30h7.53v2.19h-5v1.44h4.49v2h-4.42v1.49h5v2.21h-7.6z" />
<path d="m47.23 32.29h-2.8v-2.29h8.21v2.27h-2.81v7.1h-2.6z" />
<path d="m29.13 43.08h4.42a3.53 3.53 0 0 1 2.55.83 2.09 2.09 0 0 1 .6 1.53 2.16 2.16 0 0 1 -1.44 2.09 2.27 2.27 0 0 1 1.86 2.29c0 1.61-1.31 2.59-3.55 2.59h-4.44zm5 2.89c0-.52-.42-.8-1.18-.8h-1.29v1.64h1.24c.79 0 1.25-.26 1.25-.81zm-.9 2.66h-1.57v1.73h1.62c.8 0 1.24-.31 1.24-.86 0-.5-.4-.87-1.27-.87z" />
<path d="m38 43.08h4.1a4.19 4.19 0 0 1 3 1 2.93 2.93 0 0 1 .9 2.19 3 3 0 0 1 -1.93 2.89l2.24 3.27h-3l-1.88-2.84h-.87v2.84h-2.56zm4 4.5c.87 0 1.39-.43 1.39-1.11 0-.75-.54-1.12-1.4-1.12h-1.44v2.26z" />
<path d="m49.59 43h2.5l4 9.44h-2.79l-.67-1.69h-3.63l-.67 1.69h-2.71zm2.27 5.73-1-2.65-1.06 2.65z" />
<path d="m56.46 43.05h2.6v9.37h-2.6z" />
<path d="m60.06 43.05h2.42l3.37 5v-5h2.57v9.37h-2.26l-3.53-5.14v5.14h-2.57z" />
<path d="m68.86 51 1.45-1.73a4.84 4.84 0 0 0 3 1.13c.71 0 1.08-.24 1.08-.65 0-.4-.31-.6-1.59-.91-2-.46-3.53-1-3.53-2.93 0-1.74 1.37-3 3.62-3a5.89 5.89 0 0 1 3.86 1.25l-1.26 1.84a4.63 4.63 0 0 0 -2.62-.92c-.63 0-.94.25-.94.6 0 .42.32.61 1.63.91 2.14.46 3.44 1.16 3.44 2.91 0 1.91-1.51 3-3.79 3a6.58 6.58 0 0 1 -4.35-1.5z" />
</g>
</svg>
<span>
Install Nx Console for JetBrains
<span>
Available for WebStorm, Intellij IDEA Ultimate and more!
</span>
</span>
</a>
<div id="nx-cloud" className="rounded shadow">
<div>
<svg
id="nx-cloud-logo"
role="img"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill="transparent"
viewBox="0 0 24 24"
>
<path
strokeWidth="2"
d="M23 3.75V6.5c-3.036 0-5.5 2.464-5.5 5.5s-2.464 5.5-5.5 5.5-5.5 2.464-5.5 5.5H3.75C2.232 23 1 21.768 1 20.25V3.75C1 2.232 2.232 1 3.75 1h16.5C21.768 1 23 2.232 23 3.75Z"
/>
<path
strokeWidth="2"
d="M23 6v14.1667C23 21.7307 21.7307 23 20.1667 23H6c0-3.128 2.53867-5.6667 5.6667-5.6667 3.128 0 5.6666-2.5386 5.6666-5.6666C17.3333 8.53867 19.872 6 23 6Z"
/>
</svg>
<h2>
NxCloud
<span>Enable faster CI & better DX</span>
</h2>
</div>
<p>
You can activate distributed tasks executions and caching by
running:
</p>
<pre>nx connect-to-nx-cloud</pre>
<a
href="https://nx.app/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
{' '}
What is Nx Cloud?{' '}
</a>
</div>
<a
id="nx-repo"
className="button-pill rounded shadow"
href="https://github.com/nrwl/nx?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
<span>
Nx is open source
<span> Love Nx? Give us a star! </span>
</span>
</a>
</div>
</div>
<div id="commands" className="rounded shadow">
<h2>Next steps</h2>
<p>Here are some things you can do with Nx:</p>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Add UI library
</summary>
<pre>
<span># Generate UI lib</span>
nx g @nx/next:library ui
<span># Add a component</span>
nx g @nx/next:component ui/src/lib/button
</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
View interactive project graph
</summary>
<pre>nx graph</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Run affected commands
</summary>
<pre>
<span># see what&apos;s been affected by changes</span>
nx affected:graph
<span># run tests for current changes</span>
nx affected:test
<span># run e2e tests for current changes</span>
nx affected:e2e
</pre>
</details>
</div>
<p id="love">
Carefully crafted with
<svg
fill="currentColor"
stroke="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,10 @@
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@ -9,7 +9,8 @@
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/frontend"
"outputPath": "dist/apps/frontend",
"postcssConfig": "apps/{your app here}/postcss.config.js"
},
"configurations": {
"development": {

View File

@ -0,0 +1,5 @@
export default async function Index() {
return (
<>asd</>
);
}

View File

@ -0,0 +1,15 @@
import '../global.css';
import {LayoutSettings} from "@gitroom/frontend/components/layout/layout.settings";
export default async function Layout({ children }: { children: React.ReactNode }) {
/*
* Replace the elements below with your own.
*
* Note: The corresponding styles are in the ./index.scss file.
*/
return (
<LayoutSettings>
{children}
</LayoutSettings>
);
}

View File

@ -0,0 +1,5 @@
import {redirect} from "next/navigation";
export default async function Page() {
return redirect('/analytics');
}

View File

@ -0,0 +1,5 @@
export default async function Index() {
return (
<>asd</>
);
}

View File

@ -0,0 +1,16 @@
import '../global.css';
import {ReactNode} from "react";
export default async function AuthLayout({children}: {children: ReactNode}) {
return (
<div className="h-[100vh] w-[100vw] overflow-hidden bg-purple-400 flex justify-center items-center">
<div className="absolute w-60 h-60 rounded-xl bg-purple-300 -top-5 -left-16 z-0 transform rotate-45 hidden md:block"></div>
<div className="absolute w-48 h-48 rounded-xl bg-purple-300 -bottom-6 -right-10 transform rotate-12 hidden md:block"></div>
<div className="py-12 px-12 bg-white rounded-2xl shadow-xl z-20">
{children}
</div>
<div className="w-40 h-40 absolute bg-purple-300 rounded-full top-0 right-12 hidden md:block"></div>
<div className="w-20 h-40 absolute bg-purple-300 rounded-full bottom-20 left-10 transform rotate-45 hidden md:block"></div>
</div>
);
}

View File

@ -0,0 +1,7 @@
import {Login} from "@gitroom/frontend/components/auth/login";
export default async function Auth() {
return (
<Login />
);
}

View File

@ -0,0 +1,7 @@
import {Register} from "@gitroom/frontend/components/auth/register";
export default async function Auth() {
return (
<Register />
);
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,14 @@
import LayoutContext from "@gitroom/frontend/components/layout/layout.context";
import {ReactNode} from "react";
export default async function AppLayout({children}: {children: ReactNode}) {
return (
<html>
<body className="overflow-hidden">
<LayoutContext>
{children}
</LayoutContext>
</body>
</html>
)
}

View File

@ -0,0 +1,42 @@
"use client";
import { useForm, SubmitHandler } from "react-hook-form";
import {useFetch} from "@gitroom/helpers/utils/custom.fetch";
import Link from "next/link";
type Inputs = {
email: string;
password: string;
}
export function Login() {
const {
register,
handleSubmit,
} = useForm<Inputs>();
const fetchData = useFetch();
const onSubmit: SubmitHandler<Inputs> = (data) => {
fetchData('/auth/login', {
method: 'POST',
body: JSON.stringify({...data, provider: 'LOCAL'})
});
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-center mb-4 cursor-pointer">Create An Account</h1>
</div>
<div className="space-y-4">
<input {...register('email')} type="email" placeholder="Email Addres" className="block text-sm py-3 px-4 rounded-lg w-full border outline-purple-500"/>
<input {...register('password')} autoComplete="off" type="password" placeholder="Password" className="block text-sm py-3 px-4 rounded-lg w-full border outline-purple-500"/>
</div>
<div className="text-center mt-6">
<button type="submit" className="w-full py-2 text-xl text-white bg-purple-400 rounded-lg hover:bg-purple-500 transition-all">Sign in</button>
<p className="mt-4 text-sm">Don{"'"}t Have An Account? <Link href="/auth" className="underline cursor-pointer"> Sign Up</Link></p>
</div>
</form>
);
}

View File

@ -0,0 +1,44 @@
"use client";
import { useForm, SubmitHandler } from "react-hook-form";
import {useFetch} from "@gitroom/helpers/utils/custom.fetch";
import Link from "next/link";
type Inputs = {
email: string;
password: string;
company: string;
}
export function Register() {
const {
register,
handleSubmit,
} = useForm<Inputs>();
const fetchData = useFetch();
const onSubmit: SubmitHandler<Inputs> = (data) => {
fetchData('/auth/register', {
method: 'POST',
body: JSON.stringify({...data, provider: 'LOCAL'})
});
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-center mb-4 cursor-pointer">Create An Account</h1>
</div>
<div className="space-y-4">
<input {...register('email')} type="email" placeholder="Email Addres" className="block text-sm py-3 px-4 rounded-lg w-full border outline-purple-500"/>
<input {...register('password')} autoComplete="off" type="password" placeholder="Password" className="block text-sm py-3 px-4 rounded-lg w-full border outline-purple-500"/>
<input {...register('company')} autoComplete="off" type="text" placeholder="Company" className="block text-sm py-3 px-4 rounded-lg w-full border outline-purple-500"/>
</div>
<div className="text-center mt-6">
<button type="submit" className="w-full py-2 text-xl text-white bg-purple-400 rounded-lg hover:bg-purple-500 transition-all">Create Account</button>
<p className="mt-4 text-sm">Already Have An Account? <Link href="/auth/login" className="underline cursor-pointer"> Sign In</Link></p>
</div>
</form>
);
}

View File

@ -0,0 +1,22 @@
"use client";
import {ReactNode, useCallback} from "react";
import {FetchWrapperComponent} from "@gitroom/helpers/utils/custom.fetch";
export default async function LayoutContext({children}: {children: ReactNode}) {
const afterRequest = useCallback(async (url: string, options: RequestInit, response: Response) => {
console.log(response?.headers.get('cookie'));
if (response?.headers?.get('reload')) {
window.location.reload();
}
}, []);
return (
<FetchWrapperComponent
baseUrl={process.env.NEXT_PUBLIC_BACKEND_URL!}
afterRequest={afterRequest}
>
{children}
</FetchWrapperComponent>
)
}

View File

@ -0,0 +1,27 @@
import {ReactNode} from "react";
import {LeftMenu} from "@gitroom/frontend/components/layout/left.menu";
import {Title} from "@gitroom/frontend/components/layout/title";
import {headers} from "next/headers";
import {ContextWrapper} from "@gitroom/frontend/components/layout/user.context";
export const LayoutSettings = ({children}: {children: ReactNode}) => {
const user = JSON.parse(headers().get('user')!);
return (
<ContextWrapper user={user}>
<div className="min-w-[100vw] min-h-[100vh] bg-primary py-[14px] px-[12px] text-white flex">
<div className="w-[216px] p-2 gap-10 flex flex-col">
<div>
Logo
</div>
<LeftMenu />
</div>
<div className="flex-1 flex">
<div className="bg-secondary flex-1 rounded-3xl px-[24px] py-[28px]">
<Title />
{children}
</div>
</div>
</div>
</ContextWrapper>
);
}

View File

@ -0,0 +1,54 @@
"use client";
import {FC} from "react";
import Link from "next/link";
import clsx from "clsx";
import {usePathname} from "next/navigation";
export const menuItems = [
{
name: 'Analytics',
icon: 'analytics',
path: '/analytics',
},
{
name: 'Schedule',
icon: 'schedule',
path: '/schedule',
},
{
name: 'Media',
icon: 'media',
path: '/media',
},
{
name: 'Settings',
icon: 'settings',
path: '/settings',
},
{
name: 'Billing',
icon: 'billing',
path: '/billing',
},
];
export const LeftMenu: FC = () => {
const path = usePathname();
return (
<div className="flex flex-col h-full">
<ul className="gap-5 flex flex-col flex-1">
{menuItems.map((item, index) => (
<li key={item.name}>
<Link href={item.path} className={clsx("flex gap-2 items-center", menuItems.map(p => p.path).indexOf(path) === index && 'font-bold')}>
{item.name}
</Link>
</li>
))}
</ul>
<div>
<a href="/auth/logout">Logout</a>
</div>
</div>
);
}

View File

@ -0,0 +1,23 @@
"use client";
import {usePathname} from "next/navigation";
import {useMemo} from "react";
import {menuItems} from "@gitroom/frontend/components/layout/left.menu";
import {useUser} from "@gitroom/frontend/components/layout/user.context";
export const Title = () => {
const path = usePathname();
const currentTitle = useMemo(() => {
return menuItems.find(item => item.path === path)?.name;
}, [path]);
const user = useUser();
return (
<div className="flex">
<h1 className="text-2xl mb-5 flex-1">{currentTitle}</h1>
<div>bell</div>
<div>{user?.email}</div>
</div>
);
}

View File

@ -0,0 +1,16 @@
"use client";
import {createContext, FC, ReactNode, useContext} from "react";
import {User} from "@prisma/client";
export const UserContext = createContext<undefined|User>(undefined);
export const ContextWrapper: FC<{user: User, children: ReactNode}> = ({user, children}) => {
return (
<UserContext.Provider value={user}>
{children}
</UserContext.Provider>
)
}
export const useUser = () => useContext(UserContext);

View File

@ -0,0 +1,57 @@
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import {fetchBackend} from "@gitroom/helpers/utils/custom.fetch.func";
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
const nextUrl = request.nextUrl;
const authCookie = request.cookies.get('auth');
// If the URL is logout, delete the cookie and redirect to login
if (nextUrl.href.indexOf('/auth/logout') > -1) {
const response = NextResponse.redirect(new URL('/auth/login', nextUrl.href));
response.cookies.set('auth', '', {
path: '/',
sameSite: false,
httpOnly: true,
secure: true,
maxAge: -1,
domain: '.' + new URL(process.env.FRONTEND_URL!).hostname
});
return response;
}
if (nextUrl.href.indexOf('/auth') === -1 && !authCookie) {
return NextResponse.redirect(new URL('/auth', nextUrl.href));
}
// If the url is /auth and the cookie exists, redirect to /
if (nextUrl.href.indexOf('/auth') > -1 && authCookie) {
return NextResponse.redirect(new URL('/', nextUrl.href));
}
if (nextUrl.href.indexOf('/auth') > -1) {
return NextResponse.next();
}
try {
const user = await (await fetchBackend('/user/self', {
headers: {
auth: authCookie?.value!
}
})).json();
const next = NextResponse.next();
next.headers.set('user', JSON.stringify(user));
return next;
}
catch (err) {
return NextResponse.redirect(new URL('/auth/logout', nextUrl.href));
}
}
// See "Matching Paths" below to learn more
export const config = {
matcher: "/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)",
}

View File

@ -0,0 +1,21 @@
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const { join } = require('path');
module.exports = {
content: [
join(
__dirname,
'{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}'
),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
extend: {
colors: {
primary: '#090b13',
secondary: '#0b101b',
}
},
},
plugins: [],
};

View File

@ -1,9 +0,0 @@
import { Controller, Get } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';
@Controller()
export class AppController {
@EventPattern('new_vote')
handleData(data) {
}
}

View File

@ -1,12 +1,16 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { StarsController } from './stars.controller';
import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module";
import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module";
import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module";
import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service";
@Module({
imports: [RedisModule, DatabaseModule],
controllers: [AppController],
imports: [RedisModule, DatabaseModule, BullMqModule.forRoot({
connection: ioRedis
})],
controllers: [StarsController],
providers: [],
})
export class AppModule {}

View File

@ -0,0 +1,17 @@
import {Controller} from '@nestjs/common';
import {EventPattern, Transport} from '@nestjs/microservices';
import { JSDOM } from "jsdom";
@Controller()
export class StarsController {
@EventPattern('check_stars', Transport.REDIS)
async handleData(data: {id: string, login: string}) {
const loadedHtml = await (await fetch(`https://github.com/${data.login}`)).text();
const dom = new JSDOM(loadedHtml);
const totalStars = +(
dom.window.document.querySelector('#repo-stars-counter-star')?.getAttribute('title')?.replace(/,/g, '')
) || 0;
console.log(totalStars);
}
}

View File

@ -1,23 +1,19 @@
import { NestFactory } from '@nestjs/core';
import {NestFactory} from '@nestjs/core';
import { AppModule } from './app/app.module';
import { MicroserviceOptions } from '@nestjs/microservices';
import {BullMqTransport} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-transport";
import {AppModule} from './app/app.module';
import {MicroserviceOptions} from '@nestjs/microservices';
import {BullMqServer} from "@gitroom/nestjs-libraries/bull-mq-transport/server/bull-mq.server";
async function bootstrap() {
const strategy = new BullMqTransport();
// some comment again
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
strategy,
}
);
const load = await NestFactory.create(AppModule);
const strategy = load.get(BullMqServer);
await app.listen();
// some comment again
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
strategy,
});
// Let's make sure everything runs first!
await strategy.activate();
await app.listen();
}
bootstrap();
bootstrap();

View File

@ -0,0 +1,28 @@
export interface Params {
baseUrl: string,
beforeRequest?: (url: string, options: RequestInit) => Promise<RequestInit>,
afterRequest?: (url: string, options: RequestInit, response: Response) => Promise<void>
}
export const customFetch = (params: Params, auth?: string) => {
return async function newFetch (url: string, options: RequestInit = {}) {
const newRequestObject = await params?.beforeRequest?.(url, options);
const fetchRequest = await fetch(params.baseUrl + url, {
credentials: 'include',
...(
newRequestObject || options
),
headers: {
...options?.headers,
...auth ? {auth} : {},
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
await params?.afterRequest?.(url, options, fetchRequest);
return fetchRequest;
}
}
export const fetchBackend = customFetch({
baseUrl: process.env.NEXT_PUBLIC_BACKEND_URL!
});

View File

@ -0,0 +1,28 @@
"use client";
import {createContext, FC, ReactNode, useContext, useRef, useState} from "react";
import {customFetch, Params} from "./custom.fetch.func";
const FetchProvider = createContext(customFetch(
// @ts-ignore
{
baseUrl: '',
beforeRequest: () => {},
afterRequest: () => {}
} as Params));
export const FetchWrapperComponent: FC<Params & {children: ReactNode}> = (props) => {
const {children, ...params} = props;
// @ts-ignore
const fetchData = useRef(customFetch(params));
return (
// @ts-ignore
<FetchProvider.Provider value={fetchData.current}>
{children}
</FetchProvider.Provider>
)
}
export const useFetch = () => {
return useContext(FetchProvider);
}

View File

@ -0,0 +1,89 @@
import { DynamicModule, Module } from '@nestjs/common';
import { BullMqClient } from './client/bull-mq.client';
import { BULLMQ_MODULE_OPTIONS } from './constants/bull-mq.constants';
import { QueueEventsFactory } from './factories/queue-events.factory';
import { QueueFactory } from './factories/queue.factory';
import { WorkerFactory } from './factories/worker.factory';
import { IBullMqModuleOptionsAsync } from './interfaces/bull-mq-module-options-async.interface';
import { IBullMqModuleOptionsFactory } from './interfaces/bull-mq-module-options-factory.interface';
import { IBullMqModuleOptions } from './interfaces/bull-mq-module-options.interface';
import { BullMqServer } from './server/bull-mq.server';
@Module({})
export class BullMqCoreModule {
static forRoot(options: IBullMqModuleOptions): DynamicModule {
return {
module: BullMqCoreModule,
global: true,
providers: [
{ provide: BULLMQ_MODULE_OPTIONS, useValue: options },
QueueFactory,
QueueEventsFactory,
WorkerFactory,
BullMqServer,
BullMqClient,
],
exports: [BullMqServer, BullMqClient, BULLMQ_MODULE_OPTIONS],
};
}
static forRootAsync(options: IBullMqModuleOptionsAsync): DynamicModule {
return {
module: BullMqCoreModule,
global: true,
imports: options.imports ?? [],
providers: [
...(options.providers ?? []),
...this.createAsyncProviders(options),
QueueFactory,
QueueEventsFactory,
WorkerFactory,
BullMqServer,
BullMqClient,
],
exports: [BullMqServer, BullMqClient, BULLMQ_MODULE_OPTIONS],
};
}
private static createAsyncProviders(options: IBullMqModuleOptionsAsync) {
if (options.useExisting ?? options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
if (options.useClass) {
return [
this.createAsyncOptionsProvider(options),
{ provide: options.useClass, useClass: options.useClass },
];
}
throw new Error(
'Invalid BullMqModule async options: one of `useClass`, `useExisting` or `useFactory` should be defined.',
);
}
private static createAsyncOptionsProvider(
options: IBullMqModuleOptionsAsync,
) {
if (options.useFactory) {
return {
provide: BULLMQ_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject ?? [],
};
}
const inject: any[] = [];
if (options.useClass ?? options.useExisting) {
inject.push(options.useClass ?? options.useExisting);
}
return {
provide: BULLMQ_MODULE_OPTIONS,
useFactory: async (optionsFactory: IBullMqModuleOptionsFactory) =>
await optionsFactory.createModuleOptions(),
inject,
};
}
}

View File

@ -0,0 +1,21 @@
import { DynamicModule, Module } from '@nestjs/common';
import { BullMqCoreModule } from './bull-mq-core.module';
import { IBullMqModuleOptionsAsync } from './interfaces/bull-mq-module-options-async.interface';
import { IBullMqModuleOptions } from './interfaces/bull-mq-module-options.interface';
@Module({})
export class BullMqModule {
static forRoot(options: IBullMqModuleOptions): DynamicModule {
return {
module: BullMqModule,
imports: [BullMqCoreModule.forRoot(options)],
};
}
static forRootAsync(options: IBullMqModuleOptionsAsync): DynamicModule {
return {
module: BullMqModule,
imports: [BullMqCoreModule.forRootAsync(options)],
};
}
}

View File

@ -0,0 +1,94 @@
import { Inject, Injectable } from '@nestjs/common';
import {
ClientProxy,
ReadPacket,
RpcException,
WritePacket,
} from '@nestjs/microservices';
import { Queue } from 'bullmq';
import { v4 } from 'uuid';
import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants';
import { QueueEventsFactory } from '../factories/queue-events.factory';
import { QueueFactory } from '../factories/queue.factory';
import { IBullMqEvent } from '../interfaces/bull-mq-event.interface';
import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface";
@Injectable()
export class BullMqClient extends ClientProxy {
constructor(
@Inject(BULLMQ_MODULE_OPTIONS)
private readonly options: IBullMqModuleOptions,
private readonly queueFactory: QueueFactory,
private readonly queueEventsFactory: QueueEventsFactory,
) {
super();
}
async connect(): Promise<void> {
return;
}
async close(): Promise<void> {
return;
}
protected publish(
packet: ReadPacket<IBullMqEvent<any>>,
callback: (packet: WritePacket<any>) => void,
): () => void {
const queue = this.getQueue(packet.pattern);
const events = this.queueEventsFactory.create(packet.pattern, {
connection: this.options.connection,
});
events.on('completed', (job) =>
callback({
response: job.returnvalue,
isDisposed: true,
}),
);
events.on('failed', async (jobInfo) => {
const job = await queue.getJob(jobInfo.jobId);
const err = new RpcException(jobInfo.failedReason);
err.stack = job?.stacktrace?.[0];
callback({
err,
isDisposed: true,
});
});
queue
.add(packet.pattern, packet.data, {
jobId: packet.data.id ?? v4(),
...packet.data.options,
})
.then(async (job) => {
try {
await job.waitUntilFinished(events);
} catch {
// BullMq unnecessarily re-throws the error we're handling in
// waitUntilFinished(), so we ignore that here.
} finally {
await events.close();
await queue.close();
}
});
return () => void 0;
}
protected async dispatchEvent(
packet: ReadPacket<IBullMqEvent<any>>,
): Promise<any> {
const queue = this.getQueue(packet.pattern);
await queue.add(packet.pattern, packet.data, {
jobId: packet.data.id ?? v4(),
...packet.data.options,
});
await queue.close();
}
protected getQueue(pattern: any): Queue {
const queue = this.queueFactory.create(pattern, {
connection: this.options.connection,
});
return queue;
}
}

View File

@ -0,0 +1 @@
export const BULLMQ_MODULE_OPTIONS = 'BULLMQ_MODULE_OPTIONS';

View File

@ -0,0 +1,77 @@
import {
ArgumentsHost,
Catch,
Inject,
InternalServerErrorException,
Logger,
LogLevel,
RpcExceptionFilter,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { RpcException } from '@nestjs/microservices';
import { Observable, of, throwError } from 'rxjs';
import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants';
import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface";
@Catch(RpcException)
export class BullMqRpcExceptionFilter
extends BaseExceptionFilter
implements RpcExceptionFilter<RpcException>
{
private readonly logger = new Logger();
constructor(
@Inject(BULLMQ_MODULE_OPTIONS)
private readonly options: IBullMqModuleOptions,
) {
super();
}
override catch(exception: RpcException, host: ArgumentsHost): Observable<void> {
if (host.getType() === 'http') {
const err = new InternalServerErrorException(
exception.message,
exception.constructor.name,
);
if (exception.stack) {
err.stack = exception.stack;
}
this.logException(err, host);
return of(super.catch(err, host));
}
const err = {
name: exception.name,
error: exception.name,
message: exception.message,
stack: exception.stack || undefined,
};
this.logException(err, host);
return throwError(() => err);
}
logException(exception: Error, host: ArgumentsHost): void {
const defaultLogLevel: LogLevel = 'error';
switch (this.options.logExceptionsAsLevel ?? defaultLogLevel) {
case 'off':
return;
case 'log':
return this.logger.log(exception.stack, host.getType());
case 'error':
return this.logger.error(
exception.message,
exception.stack,
host.getType(),
);
case 'warn':
return this.logger.warn(exception.stack, host.getType());
case 'debug':
return this.logger.debug(exception.stack, host.getType());
case 'verbose':
return this.logger.verbose(exception.stack, host.getType());
}
}
}

View File

@ -0,0 +1,5 @@
import { RpcException } from '@nestjs/microservices';
export class BullMqRpcValidationException extends RpcException {
override name = this.constructor.name;
}

View File

@ -0,0 +1,9 @@
import { Injectable } from '@nestjs/common';
import { QueueEvents, QueueEventsOptions } from 'bullmq';
@Injectable()
export class QueueEventsFactory {
create(name: string, options?: QueueEventsOptions): QueueEvents {
return new QueueEvents(name, options);
}
}

View File

@ -0,0 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Queue, QueueOptions } from 'bullmq';
@Injectable()
export class QueueFactory {
create(name: string, options?: QueueOptions): Queue {
return new Queue(name, options);
}
}

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { Processor, Worker, WorkerOptions } from 'bullmq';
@Injectable()
export class WorkerFactory {
create<T, R, N extends string>(
name: string,
processor?: string | Processor<T, R, N>,
opts?: WorkerOptions,
): Worker<T, R, N> {
return new Worker(name, processor, opts);
}
}

View File

@ -0,0 +1,7 @@
import { JobsOptions } from 'bullmq';
export interface IBullMqEvent<T> {
id?: string;
payload: T;
options?: JobsOptions;
}

View File

@ -0,0 +1,14 @@
import { Type } from '@nestjs/common';
import { IBullMqModuleOptionsFactory } from './bull-mq-module-options-factory.interface';
import { IBullMqModuleOptions } from './bull-mq-module-options.interface';
export interface IBullMqModuleOptionsAsync {
imports?: any[];
providers?: any[];
inject?: any[];
useClass?: Type<IBullMqModuleOptionsFactory>;
useExisting?: Type<IBullMqModuleOptionsFactory>;
useFactory?: (
...args: any[]
) => IBullMqModuleOptions | Promise<IBullMqModuleOptions>;
}

View File

@ -0,0 +1,5 @@
import { IBullMqModuleOptions } from './bull-mq-module-options.interface';
export interface IBullMqModuleOptionsFactory {
createModuleOptions(): IBullMqModuleOptions | Promise<IBullMqModuleOptions>;
}

View File

@ -0,0 +1,6 @@
import { LogLevel } from '@nestjs/common';
import { WorkerOptions } from 'bullmq';
export interface IBullMqModuleOptions extends WorkerOptions {
logExceptionsAsLevel?: LogLevel | 'off';
}

View File

@ -0,0 +1,19 @@
import {
ValidationError,
ValidationPipe,
ValidationPipeOptions,
} from '@nestjs/common';
import {BullMqRpcValidationException} from "@gitroom/nestjs-libraries/bull-mq-transport/exceptions/bull-mq-rpc-validation.exception";
export class BullMqRpcValidationPipe extends ValidationPipe {
constructor(options?: ValidationPipeOptions) {
super({
exceptionFactory: (
errors: ValidationError[],
): BullMqRpcValidationException => {
return new BullMqRpcValidationException(errors.toString());
},
...options,
});
}
}

View File

@ -0,0 +1,69 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import {
CustomTransportStrategy,
Server,
Transport,
} from '@nestjs/microservices';
import { Job, Worker } from 'bullmq';
import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants';
import { WorkerFactory } from '../factories/worker.factory';
import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface";
@Injectable()
export class BullMqServer extends Server implements CustomTransportStrategy {
transportId = Transport.REDIS;
protected override readonly logger = new Logger(this.constructor.name);
protected readonly workers = new Map<string, Worker>();
constructor(
@Inject(BULLMQ_MODULE_OPTIONS)
private readonly options: IBullMqModuleOptions,
private readonly workerFactory: WorkerFactory,
) {
super();
this.initializeSerializer(this.serializer);
this.initializeDeserializer(this.deserializer);
}
listen(callback: (...optionalParams: unknown[]) => void) {
for (const [pattern, handler] of this.messageHandlers) {
if (
pattern &&
handler &&
!this.workers.has(pattern)
) {
const worker = this.workerFactory.create(
pattern,
(job: Job) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise<unknown>(async (resolve, reject) => {
const stream$ = this.transformToObservable(
await handler(job.data.payload, job),
);
this.send(stream$, (packet) => {
if (packet.err) {
return reject(packet.err);
}
resolve(packet.response);
});
});
},
{
...this.options,
},
);
this.workers.set(pattern, worker);
this.logger.log(`Registered queue "${pattern}"`);
}
}
callback();
}
async close() {
for (const worker of this.workers.values()) {
await worker.close();
}
}
}

View File

@ -1,31 +0,0 @@
import { ClientProxy, ReadPacket, WritePacket } from '@nestjs/microservices';
import { Queue } from 'bullmq';
import { ioRedis } from '../redis/redis.service';
export class BullMqClient extends ClientProxy {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
private queue: Queue;
override async connect() {
return;
}
async dispatchEvent(packet: ReadPacket<any>): Promise<any> {
this.queue = this.queue || new Queue(packet.pattern, { connection: ioRedis });
return this.queue.add('default', packet.data);
}
override close(): any {
return this.queue.close();
}
protected publish(
packet: ReadPacket,
callback: (packet: WritePacket) => void
): () => void {
return () => {
return;
};
}
}

View File

@ -1,20 +0,0 @@
import { Inject, Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { BullMqClient } from './bullmq-client';
@Module({
imports: [
ClientsModule.register([
{
name: 'BULL_MQ_QUEUE',
customClass: BullMqClient,
},
]),
],
get exports() {
return this.imports;
}
})
export class BullmqRegister {}
export const VoteServiceProducer = () => Inject('BULL_MQ_QUEUE');

View File

@ -1,43 +0,0 @@
import { Server, CustomTransportStrategy } from '@nestjs/microservices';
import { Job, Worker } from 'bullmq';
import { ioRedis } from '../redis/redis.service';
export class BullMqTransport extends Server implements CustomTransportStrategy {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
private worker: Worker[];
public listen(callback: () => void) {
this.worker = [];
this.messageHandlers.forEach((message, key) => {
this.worker.push(
new Worker(
key,
async (job: Job) => {
console.log('processing', job.id);
await message(JSON.parse(job.data));
},
{
connection: ioRedis,
runRetryDelay: 3000,
concurrency: 5,
autorun: false,
}
)
);
});
callback();
}
activate() {
return Promise.all(this.worker.map((p) => {
console.log('listening to', p.name);
return p.run();
}));
}
public close() {
return Promise.all(this.worker.map((p) => p.close()));
}
}

View File

@ -4,6 +4,8 @@ import {OrganizationRepository} from "@gitroom/nestjs-libraries/database/prisma/
import {OrganizationService} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.service";
import {UsersService} from "@gitroom/nestjs-libraries/database/prisma/users/users.service";
import {UsersRepository} from "@gitroom/nestjs-libraries/database/prisma/users/users.repository";
import {StarsService} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.service";
import {StarsRepository} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.repository";
@Global()
@Module({
@ -15,7 +17,9 @@ import {UsersRepository} from "@gitroom/nestjs-libraries/database/prisma/users/u
UsersService,
UsersRepository,
OrganizationService,
OrganizationRepository
OrganizationRepository,
StarsService,
StarsRepository
],
get exports() {
return this.providers;

View File

@ -26,6 +26,7 @@ model Organization {
postTags PostTag[]
postMedia PostMedia[]
post Post[]
slots Slots[]
}
model User {
@ -160,6 +161,14 @@ model PostMedia {
updatedAt DateTime @updatedAt
}
model Slots {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
day Int
time Int
}
model Post {
id String @id @default(cuid())
state State @default(QUEUE)
@ -174,6 +183,7 @@ model Post {
canonicalUrl String?
canonicalPostId String?
parentPostId String?
releaseURL String?
canonicalPost Post? @relation("canonicalPostId", fields: [canonicalPostId], references: [id])
parentPost Post? @relation("parentPostId", fields: [parentPostId], references: [id])
canonicalChildren Post[] @relation("canonicalPostId")

View File

@ -0,0 +1,16 @@
import {PrismaRepository} from "@gitroom/nestjs-libraries/database/prisma/prisma.service";
import {Injectable} from "@nestjs/common";
@Injectable()
export class StarsRepository {
constructor(
private _github: PrismaRepository<'gitHub'>
) {
}
getAllGitHubRepositories() {
return this._github.model.gitHub.findMany({
distinct: ['login'],
});
}
}

View File

@ -0,0 +1,13 @@
import {Injectable} from "@nestjs/common";
import {StarsRepository} from "@gitroom/nestjs-libraries/database/prisma/stars/stars.repository";
@Injectable()
export class StarsService {
constructor(
private _starsRepository: StarsRepository
){}
getAllGitHubRepositories() {
return this._starsRepository.getAllGitHubRepositories();
}
}

View File

@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUserFromRequest = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
}
);

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,3 @@
# nestjs-libraries
This library was generated with [Nx](https://nx.dev).

View File

@ -0,0 +1,13 @@
{
"name": "react-shared-libraries",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libraries/react-shared-libraries/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}

View File

@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
}

815
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@
"license": "MIT",
"scripts": {
"dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers,cron --parallel=4\"",
"workers": "nx run workers:serve:development",
"cron": "nx run cron:serve:development",
"command": "nx run commands:build && nx run commands:command",
"prisma-generate": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma generate",
"prisma-db-push": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma db push"
},
@ -25,22 +27,28 @@
"bullmq": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"clsx": "^2.1.0",
"cookie-parser": "^1.4.6",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2",
"nestjs-command": "^3.1.4",
"next": "13.4.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.49.3",
"react-query": "^3.39.3",
"react-router-dom": "6.11.2",
"redis": "^4.6.12",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"stripe": "^14.14.0",
"tslib": "^2.3.0"
"tslib": "^2.3.0",
"yargs": "^17.7.2"
},
"devDependencies": {
"@nestjs/schematics": "^10.0.1",
"@nestjs/testing": "^10.0.2",
"@nx/eslint": "17.2.8",
"@nx/eslint": "^17.2.8",
"@nx/eslint-plugin": "17.2.8",
"@nx/jest": "17.2.8",
"@nx/js": "17.2.8",
@ -56,15 +64,19 @@
"@swc/cli": "~0.1.62",
"@swc/core": "~1.3.85",
"@testing-library/react": "14.0.0",
"@types/cookie-parser": "^1.4.6",
"@types/jest": "^29.4.0",
"@types/node": "18.16.9",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14",
"@types/uuid": "^9.0.8",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-react": "^4.2.0",
"@vitest/coverage-v8": "~0.34.6",
"@vitest/ui": "~0.34.6",
"autoprefixer": "^10.4.17",
"babel-jest": "^29.4.1",
"eslint": "~8.48.0",
"eslint-config-next": "13.4.4",
@ -78,10 +90,12 @@
"jest-environment-node": "^29.4.1",
"jsdom": "~22.1.0",
"nx": "17.2.8",
"postcss": "^8.4.33",
"prettier": "^2.6.2",
"prisma": "^5.8.1",
"react-refresh": "^0.10.0",
"sass": "1.62.1",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.1.0",
"ts-node": "10.9.1",
"typescript": "~5.2.2",