feat: first commit

This commit is contained in:
Nevo David 2024-01-28 16:20:26 +07:00
parent 74bf5a4a53
commit 35558281be
74 changed files with 17894 additions and 78 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
node_modules

35
.eslintrc.json Normal file
View File

@ -0,0 +1,35 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"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": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
}
]
}

5
.gitignore vendored
View File

@ -4,7 +4,7 @@
dist
tmp
/out-tsc
.env
# dependencies
node_modules
@ -39,3 +39,6 @@ testem.log
Thumbs.db
.nx/cache
# Next.js
.next

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -1,6 +1,8 @@
{
"recommendations": [
"nrwl.angular-console"
"nrwl.angular-console",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner"
]
}

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: 'backend',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/backend',
};

53
apps/backend/project.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "backend",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/backend/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/backend",
"main": "apps/backend/src/main.ts",
"tsConfig": "apps/backend/tsconfig.app.json",
"assets": ["apps/backend/src/assets"],
"webpackConfig": "apps/backend/webpack.config.js"
},
"configurations": {
"development": {},
"production": {}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"options": {
"buildTarget": "backend:build"
},
"configurations": {
"development": {
"buildTarget": "backend:build:development"
},
"production": {
"buildTarget": "backend:build:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/backend/jest.config.ts"
}
}
},
"tags": []
}

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import {AuthController} from "@gitroom/backend/api/routes/auth.controller";
import {AuthService} from "@gitroom/backend/services/auth/auth.service";
@Module({
imports: [],
controllers: [AuthController],
providers: [AuthService],
})
export class ApiModule {}

View File

@ -0,0 +1,25 @@
import {Body, Controller, Post} from '@nestjs/common';
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";
@Controller()
export class AuthController {
constructor(
private _authService: AuthService
) {
}
@Post('/register')
register(
@Body() body: CreateOrgUserDto
) {
return this._authService.routeAuth(body.provider, body);
}
@Post('/login')
login(
@Body() body: LoginUserDto
) {
return this._authService.routeAuth(body.provider, body);
}
}

View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module";
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";
@Module({
imports: [DatabaseModule, RedisModule, ApiModule],
controllers: [],
providers: [AuthService],
get exports() {
return [...this.imports, ...this.providers];
}
})
export class AppModule {}

View File

22
apps/backend/src/main.ts Normal file
View File

@ -0,0 +1,22 @@
/**
* 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';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
transform: true,
}));
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(
`🚀 Application is running on: http://localhost:${port}`
);
}
bootstrap();

View File

@ -0,0 +1,69 @@
import {Injectable} from "@nestjs/common";
import {Provider, User} from '@prisma/client';
import {CreateOrgUserDto} from "@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto";
import {LoginUserDto} from "@gitroom/nestjs-libraries/dtos/auth/login.user.dto";
import {UsersService} from "@gitroom/nestjs-libraries/database/prisma/users/users.service";
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";
@Injectable()
export class AuthService {
constructor(
private _user: UsersService,
private _organization: OrganizationService,
) {
}
async routeAuth(
provider: Provider,
body: CreateOrgUserDto | LoginUserDto
) {
if (provider === Provider.LOCAL) {
const user = await this._user.getUserByEmail(body.email);
if (body instanceof CreateOrgUserDto) {
if (user) {
throw new Error('User already exists');
}
const create = await this._organization.createOrgAndUser(body);
return this.jwt(create.users[0].user);
}
if (!user || !AuthChecker.comparePassword(body.password, user.password)) {
throw new Error('Invalid user');
}
return this.jwt(user);
}
const user = await this.loginOrRegisterProvider(provider, body as LoginUserDto);
return this.jwt(user);
}
private async loginOrRegisterProvider(provider: Provider, body: LoginUserDto) {
const providerInstance = ProvidersFactory.loadProviders(provider);
const providerUser = await providerInstance.getUser(body.providerToken);
if (!providerUser) {
throw new Error('Invalid provider token');
}
const user = await this._user.getUserByProvider(providerUser.id, provider);
if (user) {
return user;
}
const create = await this._organization.createOrgAndUser({
company: '',
email: providerUser.email,
password: '',
provider,
providerId: providerUser.id
});
return create.users[0].user;
}
private async jwt(user: User) {
return AuthChecker.signJWT(user);
}
}

View File

@ -0,0 +1,3 @@
export interface ProvidersInterface {
getUser(providerToken: string): Promise<{email: string, id: string}> | false;
}

View File

@ -0,0 +1,15 @@
import {ProvidersInterface} from "@gitroom/backend/services/auth/providers.interface";
export class GithubProvider implements ProvidersInterface {
async getUser(providerToken: string): Promise<{email: string, id: string}> {
const data = await (await fetch('https://api.github.com/user', {
headers: {
Authorization: `token ${providerToken}`
}
})).json();
return {
email: data.email,
id: data.id,
};
}
}

View File

@ -0,0 +1,12 @@
import {Provider} from "@prisma/client";
import {GithubProvider} from "@gitroom/backend/services/auth/providers/github.provider";
import {ProvidersInterface} from "@gitroom/backend/services/auth/providers.interface";
export class ProvidersFactory {
static loadProviders(provider: Provider): ProvidersInterface {
switch (provider) {
case Provider.GITHUB:
return new GithubProvider();
}
}
}

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

@ -0,0 +1,31 @@
{
"extends": [
"plugin:@nx/react-typescript",
"next",
"next/core-web-vitals",
"../../.eslintrc.json"
],
"ignorePatterns": ["!**/*", ".next/**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@next/next/no-html-link-for-pages": ["error", "apps/frontend/pages"]
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
"env": {
"jest": true
}
}
]
}

View File

View File

@ -0,0 +1,409 @@
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

@ -0,0 +1,18 @@
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

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

450
apps/frontend/app/page.tsx Normal file
View File

@ -0,0 +1,450 @@
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>
);
}

6
apps/frontend/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module '*.svg' {
const content: any;
export const ReactComponent: any;
export default content;
}

View File

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'frontend',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/frontend',
};

5
apps/frontend/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -0,0 +1,22 @@
//@ts-check
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { composePlugins, withNx } = require('@nx/next');
/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
**/
const nextConfig = {
nx: {
// Set this to true if you would like to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false,
},
};
const plugins = [
// Add more Next.js plugins to this list if needed.
withNx,
];
module.exports = composePlugins(...plugins)(nextConfig);

View File

@ -0,0 +1,58 @@
{
"name": "frontend",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/frontend",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/next:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/frontend"
},
"configurations": {
"development": {
"outputPath": "apps/frontend"
},
"production": {}
}
},
"serve": {
"executor": "@nx/next:server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "frontend:build",
"dev": true
},
"configurations": {
"development": {
"buildTarget": "frontend:build:development",
"dev": true
},
"production": {
"buildTarget": "frontend:build:production",
"dev": false
}
}
},
"export": {
"executor": "@nx/next:export",
"options": {
"buildTarget": "frontend:build:production"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/frontend/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,40 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "preserve",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"../../apps/frontend/.next/types/**/*.ts",
"../../dist/apps/frontend/.next/types/**/*.ts",
"next-env.d.ts",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts"
]
}

View File

@ -0,0 +1,21 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"],
"jsx": "react"
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

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',
};

62
apps/workers/project.json Normal file
View File

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

View File

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

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import {RedisModule} from "@gitroom/nestjs-libraries/redis/redis.module";
import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module";
@Module({
imports: [RedisModule, DatabaseModule],
controllers: [AppController],
providers: [],
})
export class AppModule {}

28
apps/workers/src/main.ts Normal file
View File

@ -0,0 +1,28 @@
/**
* 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';
import { MicroserviceOptions } from '@nestjs/microservices';
import {BullMqTransport} from "@gitroom/nestjs-libraries/bullmq-transport/bullmq-transport";
async function bootstrap() {
const strategy = new BullMqTransport();
// some comment again
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
strategy,
}
);
await app.listen();
// Let's make sure everything runs first!
await strategy.activate();
}
bootstrap();

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

5
jest.config.ts Normal file
View File

@ -0,0 +1,5 @@
import { getJestProjects } from '@nx/jest';
export default {
projects: getJestProjects(),
};

3
jest.preset.js Normal file
View File

@ -0,0 +1,3 @@
const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset };

View File

@ -0,0 +1,17 @@
import {sign, verify} from 'jsonwebtoken';
import {hashSync, compareSync} from 'bcrypt';
export class AuthService {
static hashPassword (password: string) {
return hashSync(password, 10);
}
static comparePassword (password: string, hash: string) {
return compareSync(password, hash);
}
static signJWT (value: object) {
return sign(value, process.env.JWT_SECRET!);
}
static verifyJWT (token: string) {
return verify(token, process.env.JWT_SECRET!);
}
}

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": "nestjs-libraries",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libraries/nestjs-libraries/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}

View File

@ -0,0 +1,31 @@
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

@ -0,0 +1,20 @@
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

@ -0,0 +1,43 @@
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

@ -0,0 +1,26 @@
import {Global, Module} from "@nestjs/common";
import {PrismaRepository, PrismaService} from "./prisma.service";
import {OrganizationRepository} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository";
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";
@Global()
@Module({
imports: [],
controllers: [],
providers: [
PrismaService,
PrismaRepository,
UsersService,
UsersRepository,
OrganizationService,
OrganizationRepository
],
get exports() {
return this.providers;
}
})
export class DatabaseModule {
}

View File

@ -0,0 +1,41 @@
import {PrismaRepository} from "@gitroom/nestjs-libraries/database/prisma/prisma.service";
import {Role} from '@prisma/client';
import {Injectable} from "@nestjs/common";
import {AuthService} from "@gitroom/helpers/auth/auth.service";
import {CreateOrgUserDto} from "@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto";
@Injectable()
export class OrganizationRepository {
constructor(
private _organization: PrismaRepository<'organization'>
) {
}
async createOrgAndUser(body: Omit<CreateOrgUserDto, 'providerToken'> & {providerId?: string}) {
return this._organization.model.organization.create({
data: {
name: body.company,
users: {
create: {
role: Role.USER,
user: {
create: {
email: body.email,
password: body.password ? AuthService.hashPassword(body.password) : '',
providerName: body.provider,
providerId: body.providerId || '',
}
}
}
}
},
select: {
users: {
select: {
user: true
}
}
}
});
}
}

View File

@ -0,0 +1,13 @@
import {CreateOrgUserDto} from "@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto";
import {Injectable} from "@nestjs/common";
import {OrganizationRepository} from "@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository";
@Injectable()
export class OrganizationService {
constructor(
private _organizationRepository: OrganizationRepository
){}
async createOrgAndUser(body: Omit<CreateOrgUserDto, 'providerToken'> & {providerId?: string}) {
return this._organizationRepository.createOrgAndUser(body);
}
}

View File

@ -0,0 +1,18 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
@Injectable()
export class PrismaRepository<T extends keyof PrismaService> {
public model: Pick<PrismaService, T>;
constructor(private _prismaService: PrismaService) {
this.model = this._prismaService;
}
}

View File

@ -0,0 +1,55 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Organization {
id String @id @default(uuid())
name String
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users UserOrganization[]
}
model User {
id String @id @default(uuid())
email String
password String?
providerName Provider
providerId String?
organizations UserOrganization[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([email, providerName])
}
model UserOrganization {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String
organization Organization @relation(fields: [organizationId], references: [id])
organizationId String
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Provider {
LOCAL
GITHUB
}
enum Role {
SUPERADMIN
ADMIN
USER
}

View File

@ -0,0 +1,28 @@
import {PrismaRepository} from "@gitroom/nestjs-libraries/database/prisma/prisma.service";
import {Injectable} from "@nestjs/common";
import {Provider} from '@prisma/client';
@Injectable()
export class UsersRepository {
constructor(
private _user: PrismaRepository<'user'>
) {
}
getUserByEmail(email: string) {
return this._user.model.user.findFirst({
where: {
email
}
});
}
getUserByProvider(providerId: string, provider: Provider) {
return this._user.model.user.findFirst({
where: {
providerId,
providerName: provider
}
});
}
}

View File

@ -0,0 +1,18 @@
import {Injectable} from "@nestjs/common";
import {UsersRepository} from "@gitroom/nestjs-libraries/database/prisma/users/users.repository";
import {Provider} from "@prisma/client";
@Injectable()
export class UsersService {
constructor(
private _usersRepository: UsersRepository
){}
getUserByEmail(email: string) {
return this._usersRepository.getUserByEmail(email);
}
getUserByProvider(providerId: string, provider: Provider) {
return this._usersRepository.getUserByProvider(providerId, provider);
}
}

View File

@ -0,0 +1,26 @@
import {IsDefined, IsEmail, IsString, ValidateIf} from "class-validator";
import {Provider} from '@prisma/client';
export class CreateOrgUserDto {
@IsString()
@IsDefined()
@ValidateIf(o => !o.providerToken)
password: string;
@IsString()
@IsDefined()
provider: Provider;
@IsString()
@IsDefined()
@ValidateIf(o => !o.password)
providerToken: string;
@IsEmail()
@IsDefined()
email: string;
@IsString()
@IsDefined()
company: string;
}

View File

@ -0,0 +1,22 @@
import {IsDefined, IsEmail, IsString, ValidateIf} from "class-validator";
import {Provider} from '@prisma/client';
export class LoginUserDto {
@IsString()
@IsDefined()
@ValidateIf(o => !o.providerToken)
password: string;
@IsString()
@IsDefined()
provider: Provider;
@IsString()
@IsDefined()
@ValidateIf(o => !o.password)
providerToken: string;
@IsEmail()
@IsDefined()
email: string;
}

View File

@ -0,0 +1,15 @@
import {Global, Module} from "@nestjs/common";
import {RedisService} from "@gitroom/nestjs-libraries/redis/redis.service";
@Global()
@Module({
imports: [],
controllers: [],
providers: [RedisService],
get exports() {
return this.providers;
}
})
export class RedisModule {
}

View File

@ -0,0 +1,30 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { createClient } from 'redis';
import { Redis } from 'ioredis';
export const ioRedis = new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null,
});
const client = createClient({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
url: process.env.REDIS_URL,
});
const pubSub = client.duplicate();
@Injectable()
export class RedisService implements OnModuleInit {
async onModuleInit() {
await client.connect();
}
client() {
return client;
}
pubSub() {
return pubSub;
}
}

View File

@ -0,0 +1,8 @@
import Stripe from 'stripe';
import {Injectable} from "@nestjs/common";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
@Injectable()
export class StripeService {
}

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

52
nx.json
View File

@ -4,12 +4,56 @@
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"lint": {
"cache": true
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.js"
]
},
"@nx/vite:test": {
"cache": true,
"inputs": ["default", "^default"]
},
"@nx/jest:jest": {
"cache": true,
"inputs": ["default", "^default", "{workspaceRoot}/jest.preset.js"],
"options": {
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"generators": {
"@nx/react": {
"application": {
"style": "scss",
"linter": "eslint",
"bundler": "vite",
"babel": true
},
"component": {
"style": "scss"
},
"library": {
"style": "scss",
"linter": "eslint"
}
},
"@nx/next": {
"application": {
"style": "scss",
"linter": "eslint"
}
}
}
}

15695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,92 @@
"name": "gitroom",
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"scripts": {
"dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers --parallel\"",
"prisma-generate": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma generate",
"prisma-db-push": "cd ./libraries/nestjs-libraries/src/database/prisma && prisma db push"
},
"private": true,
"dependencies": {},
"dependencies": {
"@nestjs/common": "^10.0.2",
"@nestjs/core": "^10.0.2",
"@nestjs/microservices": "^10.3.1",
"@nestjs/platform-express": "^10.0.2",
"@prisma/client": "^5.8.1",
"@swc/helpers": "~0.5.2",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.5",
"@types/stripe": "^8.0.417",
"axios": "^1.0.0",
"bcrypt": "^5.1.1",
"bullmq": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2",
"next": "13.4.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"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"
},
"devDependencies": {
"@nestjs/schematics": "^10.0.1",
"@nestjs/testing": "^10.0.2",
"@nx/eslint": "17.2.8",
"@nx/eslint-plugin": "17.2.8",
"@nx/jest": "17.2.8",
"@nx/js": "17.2.8",
"nx": "17.2.8"
"@nx/nest": "^17.2.8",
"@nx/next": "^17.2.8",
"@nx/node": "17.2.8",
"@nx/react": "17.2.8",
"@nx/vite": "17.2.8",
"@nx/webpack": "17.2.8",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@svgr/webpack": "^8.0.1",
"@swc-node/register": "~1.6.7",
"@swc/cli": "~0.1.62",
"@swc/core": "~1.3.85",
"@testing-library/react": "14.0.0",
"@types/jest": "^29.4.0",
"@types/node": "18.16.9",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14",
"@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",
"babel-jest": "^29.4.1",
"eslint": "~8.48.0",
"eslint-config-next": "13.4.4",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"jest": "^29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-environment-node": "^29.4.1",
"jsdom": "~22.1.0",
"nx": "17.2.8",
"prettier": "^2.6.2",
"prisma": "^5.8.1",
"react-refresh": "^0.10.0",
"sass": "1.62.1",
"ts-jest": "^29.1.0",
"ts-node": "10.9.1",
"typescript": "~5.2.2",
"url-loader": "^4.1.1",
"vite": "^5.0.0",
"vitest": "~0.34.6"
},
"workspaces": [
"packages/*"
"apps/*"
]
}

28
tsconfig.base.json Normal file
View File

@ -0,0 +1,28 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false,
"experimentalDecorators": true,
"noPropertyAccessFromIndexSignature": false,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@gitroom/backend/*": ["apps/backend/src/*"],
"@gitroom/frontend/*": ["apps/frontend/src/*"],
"@gitroom/nestjs-libraries/*": ["libraries/nestjs-libraries/src/*"],
"@gitroom/helpers/*": ["libraries/helpers/src/*"],
"@gitroom/workers/*": ["apps/workers/src/*"]
}
},
"exclude": ["node_modules", "tmp"]
}