add app api

This commit is contained in:
Chika 2022-11-23 17:06:34 +03:00
parent ae75339cea
commit 0cc22efb74
70 changed files with 11018 additions and 636 deletions

View File

@ -1,7 +1,7 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
extends: ["custom/common"],
settings: {
next: {
rootDir: ["apps/*/"],

12
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": true
},
"explorerExclude.backup": {}
}

5
apps/api/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
dist

7
apps/api/.env Normal file
View File

@ -0,0 +1,7 @@
SECRET=secret
TOKEN_TTL=1209600
# CACHE
REDIS_HOST=localhost
CACHE_TTL=7776000

2
apps/api/.eslintignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

9
apps/api/.eslintrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
root: true,
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
extends: ['custom/nest'],
};

35
apps/api/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

1
apps/api/.prettierignore Normal file
View File

@ -0,0 +1 @@
dist

18
apps/api/.prettierrc Normal file
View File

@ -0,0 +1,18 @@
{
"endOfLine": "auto",
"arrowParens": "always",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}

22
apps/api/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM node:16-alpine AS deps
WORKDIR /app
COPY package.json yarn.lock ./
COPY .npmrc ./
RUN yarn config set "strict-ssl" false
RUN yarn install --frozen-lockfile --prod=true
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
FROM node:16-alpine AS runner
WORKDIR /app
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD [ "node", "dist/main.js" ]

73
apps/api/README.md Normal file
View File

@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

5
apps/api/nest-cli.json Normal file
View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

78
apps/api/package.json Normal file
View File

@ -0,0 +1,78 @@
{
"name": "api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@fastify/cookie": "^8.0.0",
"@nestjs/cli": "^9.0.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^9.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-fastify": "^9.0.11",
"cache-manager": "^4.1.0",
"cache-manager-ioredis": "^2.1.0",
"ldap-authentication": "^2.3.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
},
"devDependencies": {
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/cache-manager": "^4.0.1",
"@types/ioredis": "^4.28.10",
"@types/jest": "28.1.4",
"@types/ldap-authentication": "^2.2.0",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"eslint": "^8.28.0",
"eslint-config-custom": "*",
"jest": "28.1.2",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.5",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,23 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@ -0,0 +1,30 @@
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { LdapModule } from './ldap/ldap.module';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
JwtModule.register({
secret: process.env.SECRET,
signOptions: {
expiresIn: process.env.TOKEN_TTL,
},
}),
AuthModule,
UsersModule,
LdapModule,
],
controllers: [AppController],
providers: [AppService],
exports: [JwtModule],
})
export class AppModule {}

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@ -0,0 +1,21 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
providers: [AuthService],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,93 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable import/no-extraneous-dependencies */
import { Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify';
import { AuthService } from './auth.service';
import { COOKIE_TOKEN_NAME } from './lib/constants';
import type { Credentials } from './types/request';
@Controller('auth')
export class AuthController {
cookieOptions: { maxAge: number; path: string };
constructor(private readonly authService: AuthService) {
this.cookieOptions = {
maxAge: Number.parseInt(process.env.TOKEN_TTL, 10),
path: '/',
};
}
private clearCookies(req, reply) {
if (req.cookies) {
Object.keys(req.cookies).forEach((cookieName) => {
reply.clearCookie(cookieName, {
path: '/',
});
});
}
}
private getTargetUri(req) {
const refererURL = new URL(req?.headers?.referer);
const targetUri = refererURL.searchParams.get('uri') || '/';
return targetUri;
}
private getInvalidPasswordUrl(targetUri: string) {
const params = new URLSearchParams();
params.append('uri', targetUri);
params.append('invalid', 'true');
const invalidPasswordURI = '/login'.concat('?', params.toString());
return invalidPasswordURI;
}
@Post('/login')
async login(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const targetUri = this.getTargetUri(req);
const { login, password } = req.body as Credentials;
try {
const token = await this.authService.login(login, password);
return await reply
.setCookie(COOKIE_TOKEN_NAME, token, this.cookieOptions)
.status(302)
.redirect(targetUri);
} catch {
const invalidPasswordURI = this.getInvalidPasswordUrl(targetUri);
return reply.status(302).redirect(decodeURIComponent(invalidPasswordURI));
}
}
@Get('/logout')
async logout(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
this.clearCookies(req, reply);
const token = req.cookies[COOKIE_TOKEN_NAME];
await this.authService.logout(token);
return reply.status(302).redirect('/login');
}
@Get('/check-token')
async checkToken(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const token = req.cookies[COOKIE_TOKEN_NAME];
try {
this.authService.checkToken(token);
return await reply.send();
} catch (error) {
if (error.name === 'TokenExpiredError') {
const newToken = this.authService.refreshToken(token);
return await reply.setCookie(COOKIE_TOKEN_NAME, newToken, this.cookieOptions).send();
}
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
}
}
}

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { LdapModule } from '../ldap/ldap.module';
import { UsersModule } from '../users/users.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [UsersModule, LdapModule],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}

View File

@ -0,0 +1,19 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,57 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { LdapService } from '../ldap/ldap.service';
import { UsersCache } from '../users/users.cache';
import type { DecodedToken, TokenPayload } from './types/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly ldapService: LdapService,
private readonly usersCache: UsersCache,
private readonly jwtService: JwtService
) {}
public async login(login: string, password: string) {
const {
displayName,
department,
title,
mail,
sAMAccountName: username,
} = await this.ldapService.authenticate(login, password);
const user = {
username,
domain: process.env.domain,
displayName,
department,
position: title,
mail,
domainName: `${process.env.domain}\\${username}`,
};
await this.usersCache.addUser(username, user);
const payload: TokenPayload = {
username,
domain: process.env.domain,
};
return this.jwtService.sign(payload);
}
public async logout(token: string) {
const { username } = this.jwtService.decode(token) as DecodedToken;
await this.usersCache.deleteUser(username);
}
public checkToken(token: string) {
this.jwtService.verify(token);
}
public refreshToken(token: string) {
const { exp, iat, ...payload } = this.jwtService.decode(token) as DecodedToken;
return this.jwtService.sign(payload);
}
}

View File

@ -0,0 +1 @@
export const COOKIE_TOKEN_NAME = 'token';

View File

@ -0,0 +1,9 @@
export type TokenPayload = {
username: string;
domain: string;
};
export type DecodedToken = {
exp: number;
iat: number;
} & TokenPayload;

View File

@ -0,0 +1,4 @@
export type Credentials = {
login: string;
password: string;
};

View File

@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { LdapService } from './ldap.service';
@Module({
providers: [LdapService],
exports: [LdapService],
})
export class LdapModule {}

View File

@ -0,0 +1,19 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { LdapService } from './ldap.service';
describe('LdapService', () => {
let service: LdapService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LdapService],
}).compile();
service = module.get<LdapService>(LdapService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import type { AuthenticationOptions } from 'ldap-authentication';
import { authenticate } from 'ldap-authentication';
import type { LdapUser } from './types/user';
@Injectable()
export class LdapService {
async authenticate(login: string, password?: string) {
const options: AuthenticationOptions = {
ldapOpts: {
url: process.env.ldapUrl,
},
adminDn: process.env.bindDN,
adminPassword: process.env.bindCredentials,
userSearchBase: process.env.base,
usernameAttribute: process.env.attribute,
username: login,
userPassword: password,
verifyUserExists: password === undefined,
};
const ldapUser: LdapUser = await authenticate(options);
return ldapUser;
}
}

View File

@ -0,0 +1,63 @@
export type LdapUser = {
dn: string;
controls: any[];
objectClass: string[];
cn: string;
sn: string;
c: string;
title: string;
physicalDeliveryOfficeName: string;
givenName: string;
distinguishedName: string;
instanceType: string;
whenCreated: string;
whenChanged: string;
displayName: string;
uSNCreated: string;
memberOf: string[];
uSNChanged: string;
co: string;
department: string;
proxyAddresses: string[];
name: string;
objectGUID: string;
userAccountControl: string;
badPwdCount: string;
codePage: string;
countryCode: string;
employeeID: string;
badPasswordTime: string;
lastLogoff: string;
lastLogon: string;
pwdLastSet: string;
primaryGroupID: string;
objectSid: string;
accountExpires: string;
logonCount: string;
sAMAccountName: string;
sAMAccountType: string;
showInAddressBook: string[];
legacyExchangeDN: string;
userPrincipalName: string;
lockoutTime: string;
objectCategory: string;
dSCorePropagationData: string[];
'mS-DS-ConsistencyGuid': string;
lastLogonTimestamp: string;
'msDS-KeyCredentialLink': string;
mail: string;
middleName: string;
preferredLanguage: string;
extensionAttribute2: string;
msExchVersion: string;
msExchRecipientDisplayType: string;
msExchRecipientTypeDetails: string;
extensionAttribute1: string;
msExchMailboxGuid: string;
targetAddress: string;
msExchPoliciesIncluded: string[];
extensionAttribute13: string;
mailNickname: string;
msExchRemoteRecipientType: string;
msExchUMDtmfMap: string[];
};

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

@ -0,0 +1,22 @@
import fastifyCookie from '@fastify/cookie';
import { NestFactory } from '@nestjs/core';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
logger: true,
})
);
await app.register(fastifyCookie, {
secret: process.env.SECRET,
});
await app.listen(process.env.API_PORT || 3000, '0.0.0.0');
}
bootstrap();

View File

@ -0,0 +1,9 @@
export type User = {
displayName: string;
username: string;
department: string;
position: string;
mail: string;
domain: string;
domainName: string;
};

View File

@ -0,0 +1,23 @@
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
import type { User } from './types/user';
@Injectable()
export class UsersCache {
constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}
async getUser(username: string) {
const user = (await this.cacheManager.get(username)) as User;
return user;
}
async addUser(username: string, user: User) {
await this.cacheManager.set(username, user);
}
async deleteUser(username: string) {
if (this.cacheManager.get(username)) {
await this.cacheManager.del(username);
}
}
}

View File

@ -0,0 +1,21 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,20 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable import/no-extraneous-dependencies */
import { Controller, Get, Req, Res } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify';
import { COOKIE_TOKEN_NAME } from '../auth/lib/constants';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get('/get-user')
async getUser(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const token = req.cookies[COOKIE_TOKEN_NAME];
const user = await this.usersService.getUser(token);
return reply.send(user);
}
}

View File

@ -0,0 +1,21 @@
import { CacheModule, Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-ioredis';
import type { RedisOptions } from 'ioredis';
import { UsersCache } from './users.cache';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [
CacheModule.register<RedisOptions>({
store: redisStore,
host: process.env.REDIS_HOST,
port: Number.parseInt(process.env.REDIS_PORT, 10) || 6379,
ttl: Number.parseInt(process.env.CACHE_TTL, 10),
}),
],
controllers: [UsersController],
providers: [UsersService, UsersCache],
exports: [UsersCache],
})
export class UsersModule {}

View File

@ -0,0 +1,19 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,17 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import type { DecodedToken } from '../auth/types/jwt';
import { UsersCache } from './users.cache';
@Injectable()
export class UsersService {
constructor(private readonly usersCache: UsersCache, private readonly jwtService: JwtService) {}
public async getUser(token: string) {
const { username } = this.jwtService.decode(token) as DecodedToken;
const user = await this.usersCache.getUser(username);
return user;
}
}

View File

@ -0,0 +1,23 @@
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import type { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!'));
});

View File

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
apps/api/tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

5767
apps/api/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -1,30 +0,0 @@
## Getting Started
First, run the development server:
```bash
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

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

@ -1,6 +0,0 @@
module.exports = {
reactStrictMode: true,
experimental: {
transpilePackages: ["ui"],
},
};

View File

@ -1,27 +0,0 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev --port 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ui": "*"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"eslint": "7.32.0",
"eslint-config-custom": "*",
"tsconfig": "*",
"@types/node": "^17.0.12",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"typescript": "^4.5.3"
}
}

View File

@ -1,10 +0,0 @@
import { Button } from "ui";
export default function Docs() {
return (
<div>
<h1>Docs</h1>
<Button />
</div>
);
}

View File

@ -1,5 +0,0 @@
{
"extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View File

@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -1,30 +0,0 @@
## Getting Started
First, run the development server:
```bash
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

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

@ -1,6 +0,0 @@
module.exports = {
reactStrictMode: true,
experimental: {
transpilePackages: ["ui"],
},
};

View File

@ -1,27 +0,0 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ui": "*"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"eslint": "7.32.0",
"eslint-config-custom": "*",
"tsconfig": "*",
"@types/node": "^17.0.12",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"typescript": "^4.5.3"
}
}

View File

@ -1,10 +0,0 @@
import { Button } from "ui";
export default function Web() {
return (
<div>
<h1>Web</h1>
<Button />
</div>
);
}

View File

@ -1,5 +0,0 @@
{
"extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,8 @@
module.exports = {
extends: ['next', 'turbo', 'prettier'],
rules: {
'@next/next/no-html-link-for-pages': 'off',
'react/jsx-key': 'off',
},
};

View File

@ -1,7 +1,7 @@
const nest = require('./nest');
const common = require('./common');
module.exports = {
extends: ["next", "turbo", "prettier"],
rules: {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
},
common,
nest,
};

View File

@ -0,0 +1,56 @@
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint/eslint-plugin', 'prettier', 'unicorn'],
extends: [
'prettier',
'airbnb-base',
'airbnb-typescript/base',
'plugin:@typescript-eslint/recommended',
'plugin:unicorn/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'linebreak-style': ['error', 'windows'],
'comma-dangle': 'off',
'@typescript-eslint/comma-dangle': ['off'],
'import/extensions': 'off',
'object-curly-newline': [
'warn',
{
ObjectExpression: 'always',
ObjectPattern: { multiline: true },
ImportDeclaration: 'never',
ExportDeclaration: { multiline: true, minProperties: 3 },
},
],
'lines-between-class-members': 'off',
'@typescript-eslint/lines-between-class-members': ['off'],
indent: 'off',
'@typescript-eslint/indent': ['off'],
'newline-before-return': 'warn',
'@typescript-eslint/consistent-type-imports': 'error',
// Airbnb prefers forEach
'unicorn/no-array-for-each': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/no-null': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/no-array-reduce': 'off',
'unicorn/prefer-module': 'off',
'unicorn/text-encoding-identifier-case': 'off',
'import/no-unresolved': 'warn',
'import/prefer-default-export': 'off',
'class-methods-use-this': 'off',
},
};

View File

@ -4,11 +4,18 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"eslint": "^7.23.0",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "13.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-turbo": "latest",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "7.31.8",
"eslint-config-turbo": "latest"
"eslint-plugin-unicorn": "^45.0.0"
},
"devDependencies": {
"typescript": "^4.7.4"

View File

@ -1,3 +0,0 @@
# `tsconfig`
These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from.

View File

@ -1,20 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"moduleResolution": "node",
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true
},
"exclude": ["node_modules"]
}

View File

@ -1,22 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["src", "next-env.d.ts"],
"exclude": ["node_modules"]
}

View File

@ -1,10 +0,0 @@
{
"name": "tsconfig",
"version": "0.0.0",
"private": true,
"files": [
"base.json",
"nextjs.json",
"react-library.json"
]
}

View File

@ -1,11 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "React Library",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2015"],
"module": "ESNext",
"target": "es6"
}
}

View File

@ -1,4 +0,0 @@
import * as React from "react";
export const Button = () => {
return <button>Boop</button>;
};

View File

@ -1,2 +0,0 @@
import * as React from "react";
export * from "./Button";

View File

@ -1,19 +0,0 @@
{
"name": "ui",
"version": "0.0.0",
"main": "./index.tsx",
"types": "./index.tsx",
"license": "MIT",
"scripts": {
"lint": "TIMING=1 eslint \"**/*.ts*\""
},
"devDependencies": {
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"eslint": "^7.32.0",
"eslint-config-custom": "*",
"react": "^18.2.0",
"tsconfig": "*",
"typescript": "^4.5.2"
}
}

View File

@ -1,5 +0,0 @@
{
"extends": "tsconfig/react-library.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

4649
yarn.lock

File diff suppressed because it is too large Load Diff