chore: NestJS platform api
This commit is contained in:
parent
d4d96eb5cd
commit
eefa6d16bd
|
@ -0,0 +1,25 @@
|
|||
module.exports = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint/eslint-plugin"],
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
// 'plugin:prettier/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",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
# 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
|
||||
|
||||
# ENV
|
||||
.env*
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "cal-platform-api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"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": {
|
||||
"@calcom/prisma": "*",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"nest-winston": "^1.9.4",
|
||||
"nestjs-zod": "^3.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"winston": "^3.11.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { Controller, Get, Version, VERSION_NEUTRAL } from "@nestjs/common";
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
@Get("health")
|
||||
@Version(VERSION_NEUTRAL)
|
||||
getHealth(): "OK" {
|
||||
return "OK";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import appConfig from "@/config/app";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { EndpointsModule } from "@/modules/endpoints-module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { APP_PIPE } from "@nestjs/core";
|
||||
import { ZodValidationPipe } from "nestjs-zod";
|
||||
|
||||
import { AppController } from "./app.controller";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
ignoreEnvFile: true,
|
||||
isGlobal: true,
|
||||
load: [appConfig],
|
||||
}),
|
||||
PrismaModule,
|
||||
EndpointsModule,
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_PIPE,
|
||||
useClass: ZodValidationPipe,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
|
@ -0,0 +1,24 @@
|
|||
import { RequestMethod, VersioningType } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
|
||||
export const bootstrap = (app: NestExpressApplication): NestExpressApplication => {
|
||||
app.enableShutdownHooks();
|
||||
app.enableVersioning({
|
||||
type: VersioningType.URI,
|
||||
prefix: "v",
|
||||
defaultVersion: "1",
|
||||
});
|
||||
|
||||
app.enableCors({
|
||||
origin: "*",
|
||||
methods: ["GET", "HEAD", "POST", "PUT", "OPTIONS"],
|
||||
allowedHeaders: ["Accept", "Authorization", "Content-Type", "Origin"],
|
||||
maxAge: 86_400,
|
||||
});
|
||||
|
||||
app.setGlobalPrefix("api", {
|
||||
exclude: [{ path: "health", method: RequestMethod.GET }],
|
||||
});
|
||||
|
||||
return app;
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
import { AppConfig } from "./type";
|
||||
|
||||
const loadConfig = (): AppConfig => {
|
||||
return {
|
||||
env: {
|
||||
type: (process.env.NODE_ENV as "production" | "development") ?? "development",
|
||||
},
|
||||
api: {
|
||||
port: Number(process.env.API_PORT ?? 5555),
|
||||
},
|
||||
db: {
|
||||
readUrl: process.env.DATABASE_READ_URL ?? "",
|
||||
writeUrl: process.env.DATABSE_WRITE_URL ?? "",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default loadConfig;
|
|
@ -0,0 +1,12 @@
|
|||
export type AppConfig = {
|
||||
env: {
|
||||
type: "production" | "development";
|
||||
};
|
||||
api: {
|
||||
port: number;
|
||||
};
|
||||
db: {
|
||||
readUrl: string;
|
||||
writeUrl: string;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
import { createHash } from "crypto";
|
||||
|
||||
export const hashAPIKey = (apiKey: string): string => createHash("sha256").update(apiKey).digest("hex");
|
|
@ -0,0 +1,48 @@
|
|||
import type { LoggerOptions } from "winston";
|
||||
import { format, transports } from "winston";
|
||||
|
||||
const formattedTimestamp = format.timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
});
|
||||
|
||||
const colorizer = format.colorize({
|
||||
colors: {
|
||||
fatal: "red",
|
||||
error: "red",
|
||||
warn: "yellow",
|
||||
info: "blue",
|
||||
debug: "white",
|
||||
trace: "grey",
|
||||
},
|
||||
});
|
||||
|
||||
const WINSTON_DEV_FORMAT = format.combine(
|
||||
format.errors({ stack: true }),
|
||||
colorizer,
|
||||
formattedTimestamp,
|
||||
format.simple()
|
||||
);
|
||||
const WINSTON_PROD_FORMAT = format.combine(format.errors({ stack: true }), formattedTimestamp, format.json());
|
||||
|
||||
export const loggerConfig = (): LoggerOptions => {
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
return {
|
||||
levels: {
|
||||
fatal: 0,
|
||||
error: 1,
|
||||
warn: 2,
|
||||
info: 3,
|
||||
debug: 4,
|
||||
trace: 5,
|
||||
},
|
||||
level: process.env.LOG_LEVEL ?? "info",
|
||||
format: isProduction ? WINSTON_PROD_FORMAT : WINSTON_DEV_FORMAT,
|
||||
transports: [new transports.Console()],
|
||||
exceptionHandlers: [new transports.Console()],
|
||||
rejectionHandlers: [new transports.Console()],
|
||||
defaultMeta: {
|
||||
service: "cal-platform-api",
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import { AppConfig } from "@/config/type";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import "dotenv/config";
|
||||
import { WinstonModule } from "nest-winston";
|
||||
|
||||
import { bootstrap } from "./app";
|
||||
import { AppModule } from "./app.module";
|
||||
import { loggerConfig } from "./lib/logger";
|
||||
|
||||
const run = async () => {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
logger: WinstonModule.createLogger(loggerConfig()),
|
||||
});
|
||||
const logger = new Logger("App");
|
||||
|
||||
try {
|
||||
bootstrap(app);
|
||||
const port = app.get(ConfigService<AppConfig, true>).get("api.port", { infer: true });
|
||||
await app.listen(port);
|
||||
|
||||
logger.log(`Application started on port: ${port}`);
|
||||
} catch (error) {
|
||||
logger.error("Application crashed", {
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
run().catch((error: Error) => {
|
||||
console.error("Failed to start Cal Platform API", { error: error.stack });
|
||||
process.exit(1);
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import { ApiKeyService } from "@/modules/api-key/api-key.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
providers: [ApiKeyService],
|
||||
exports: [ApiKeyService],
|
||||
})
|
||||
export class ApiKeyModule {}
|
|
@ -0,0 +1,40 @@
|
|||
import { hashAPIKey } from "@/lib/api-key";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Response } from "@/types";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
type ApiKeyInfo = {
|
||||
hashedKey: string;
|
||||
id: string;
|
||||
userId: number;
|
||||
teamId: number | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyService {
|
||||
constructor(private readonly dbRead: PrismaReadService) {}
|
||||
|
||||
private setResponseApiKey = (response: Response, key: ApiKeyInfo) => {
|
||||
response.locals.apiKey = key;
|
||||
};
|
||||
|
||||
async retrieveApiKey(request: Request, response?: Response) {
|
||||
const apiKey = request.get("Authorization")?.replace("Bearer ", "");
|
||||
if (!apiKey) throw new BadRequestException("Invalid API Key");
|
||||
|
||||
const hashedKey = hashAPIKey(apiKey.replace("cal_", ""));
|
||||
|
||||
const apiKeyResult = await this.dbRead.prisma.apiKey.findUnique({
|
||||
where: {
|
||||
hashedKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (response && apiKeyResult) {
|
||||
void this.setResponseApiKey(response, apiKeyResult);
|
||||
}
|
||||
|
||||
return apiKeyResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { ApiKeyService } from "@/modules/api-key/api-key.service";
|
||||
import { UserRepository } from "@/modules/repositories/user/user-repository.service";
|
||||
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Request } from "express";
|
||||
|
||||
class BaseStrategy {
|
||||
success!: (user: unknown) => void;
|
||||
error!: (error: Error) => void;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyAuthStrategy extends PassportStrategy(BaseStrategy, "api-key") {
|
||||
constructor(
|
||||
private readonly apiKeyService: ApiKeyService,
|
||||
private readonly userRepository: UserRepository
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async authenticate(req: Request) {
|
||||
const apiKey = await this.apiKeyService.retrieveApiKey(req);
|
||||
if (!apiKey) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
if (apiKey.expiresAt && new Date() > apiKey.expiresAt) {
|
||||
throw new Error("This apiKey is expired");
|
||||
}
|
||||
|
||||
const user = await this.userRepository.findById(apiKey.userId);
|
||||
this.success(user);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { ApiKeyModule } from "@/modules/api-key/api-key.module";
|
||||
import { ApiKeyAuthStrategy } from "@/modules/auth/auth-api-key.strategy";
|
||||
import { UserModule } from "@/modules/repositories/user/user-repository.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
|
||||
@Module({
|
||||
imports: [PassportModule, ApiKeyModule, UserModule],
|
||||
providers: [ApiKeyAuthStrategy],
|
||||
})
|
||||
export class AuthModule {}
|
|
@ -0,0 +1,43 @@
|
|||
import { CreateBookingDto } from "@/modules/booking/dtos/create-booking";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { BookingRepository } from "@/modules/repositories/booking/booking-repository.service";
|
||||
import { type Response } from "@/types";
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Post,
|
||||
Res,
|
||||
UseGuards,
|
||||
VERSION_NEUTRAL,
|
||||
Version,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
|
||||
@Controller("booking")
|
||||
export class BookingController {
|
||||
private readonly logger = new Logger("BookingController");
|
||||
|
||||
constructor(
|
||||
private readonly dbRead: PrismaReadService,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
private readonly bookingRepository: BookingRepository
|
||||
) {}
|
||||
|
||||
@Post("/")
|
||||
@Version(VERSION_NEUTRAL)
|
||||
@UseGuards(AuthGuard("api-key"))
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async createBooking(@Res({ passthrough: true }) res: Response, @Body() body: CreateBookingDto) {
|
||||
const userId = res.locals.apiKey?.userId;
|
||||
if (!userId) throw new BadRequestException("Invalid API Key User");
|
||||
|
||||
this.logger.log("Created Booking with data " + body);
|
||||
|
||||
return this.bookingRepository.createBooking(userId, body);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { BookingController } from "@/modules/booking/booking.controller";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { BookingRepositoryModule } from "@/modules/repositories/booking/booking-repository.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, BookingRepositoryModule],
|
||||
controllers: [BookingController],
|
||||
})
|
||||
export class BookingModule {}
|
|
@ -0,0 +1,10 @@
|
|||
import { createZodDto } from "nestjs-zod";
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
export const CreateBookingSchema = z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
timezone: z.string(),
|
||||
});
|
||||
|
||||
export class CreateBookingDto extends createZodDto(CreateBookingSchema) {}
|
|
@ -0,0 +1,12 @@
|
|||
import { BookingModule } from "@/modules/booking/booking.module";
|
||||
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [BookingModule],
|
||||
})
|
||||
export class EndpointsModule implements NestModule {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
configure(_consumer: MiddlewareConsumer) {
|
||||
// TODO: apply ratelimits
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Injectable, OnModuleInit } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { PrismaClient, customPrisma } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaReadService implements OnModuleInit {
|
||||
public prisma: PrismaClient;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const dbUrl = configService.get("db.readUrl", { infer: true });
|
||||
|
||||
this.prisma = customPrisma({
|
||||
datasources: {
|
||||
db: {
|
||||
url: dbUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.prisma.$connect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Injectable, OnModuleInit } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { PrismaClient, customPrisma } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaWriteService implements OnModuleInit {
|
||||
public prisma: PrismaClient;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const dbUrl = configService.get("db.writeUrl", { infer: true });
|
||||
|
||||
this.prisma = customPrisma({
|
||||
datasources: {
|
||||
db: {
|
||||
url: dbUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.prisma.$connect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
providers: [PrismaReadService, PrismaWriteService],
|
||||
exports: [PrismaReadService, PrismaWriteService],
|
||||
})
|
||||
export class PrismaModule {}
|
|
@ -0,0 +1,8 @@
|
|||
import { BookingRepository } from "@/modules/repositories/booking/booking-repository.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
providers: [BookingRepository],
|
||||
exports: [BookingRepository],
|
||||
})
|
||||
export class BookingRepositoryModule {}
|
|
@ -0,0 +1,33 @@
|
|||
import { CreateBookingSchema } from "@/modules/booking/dtos/create-booking";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
@Injectable()
|
||||
export class BookingRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createBooking(userId: number, data: z.infer<typeof CreateBookingSchema>) {
|
||||
return this.dbWrite.prisma.booking.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
startTime: new Date(),
|
||||
endTime: new Date(),
|
||||
title: "Test Event",
|
||||
uid: "test-event",
|
||||
attendees: {
|
||||
create: {
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
timeZone: data.timezone,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { UserRepository } from "@/modules/repositories/user/user-repository.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
providers: [UserRepository],
|
||||
exports: [UserRepository],
|
||||
})
|
||||
export class UserModule {}
|
|
@ -0,0 +1,15 @@
|
|||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class UserRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService) {}
|
||||
|
||||
async findById(userId: number) {
|
||||
return this.dbRead.prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import type { Response as BaseResponse } from "express";
|
||||
|
||||
export interface PlatformApiLocals extends Record<string, unknown> {
|
||||
apiKey?: {
|
||||
hashedKey: string;
|
||||
id: string;
|
||||
userId: number;
|
||||
teamId: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export type Response = BaseResponse<unknown, PlatformApiLocals>;
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"incremental": true,
|
||||
"lib": ["ES2022"],
|
||||
"module": "CommonJS",
|
||||
"target": "ES2021",
|
||||
"composite": false,
|
||||
"alwaysStrict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"inlineSourceMap": false,
|
||||
"isolatedModules": false,
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": true,
|
||||
"preserveWatchOutput": true,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"pretty": true,
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "dist",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", ".turbo"],
|
||||
"include": ["src"]
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
"packages/embeds/*",
|
||||
"packages/features/*",
|
||||
"packages/app-store/*",
|
||||
"packages/app-store/ee/*"
|
||||
"packages/app-store/ee/*",
|
||||
"apps/platform/*"
|
||||
],
|
||||
"scripts": {
|
||||
"app-store-cli": "yarn workspace @calcom/app-store-cli",
|
||||
|
|
|
@ -19,6 +19,7 @@ module.exports = {
|
|||
"^~/(.*)$",
|
||||
"^[./]",
|
||||
],
|
||||
importOrderParserPlugins: ["typescript", "decorators-legacy"],
|
||||
importOrderSeparation: true,
|
||||
plugins: [
|
||||
"@trivago/prettier-plugin-sort-imports",
|
||||
|
|
Loading…
Reference in New Issue
Block a user