feat: Backend typing and openapi schema & codegen

This commit is contained in:
Aleksi Lassila
2024-03-26 23:01:11 +02:00
parent b29907c0e2
commit 7318a0fa99
22 changed files with 376 additions and 179 deletions

View File

@@ -14,6 +14,7 @@
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/swagger": "^7.3.0",
"reflect-metadata": "^0.2.1",
"rxjs": "^7.8.1",
"sqlite3": "^5.1.7",
@@ -1604,6 +1605,11 @@
"node": ">=8"
}
},
"node_modules/@microsoft/tsdoc": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
"integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
},
"node_modules/@nestjs/cli": {
"version": "10.3.2",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz",
@@ -1811,6 +1817,25 @@
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0"
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz",
"integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==",
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
"class-transformer": "^0.4.0 || ^0.5.0",
"class-validator": "^0.13.0 || ^0.14.0",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/platform-express": {
"version": "10.3.5",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.5.tgz",
@@ -1884,6 +1909,38 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.2.5.tgz",
"integrity": "sha512-l6qtdDPIkmAmzEO6egquYDfqQGPMRNGjYtrU13HAXb3YSRrt7HSb1sJY0pKp6o2bAa86tSB6iwaW2JbthPKr7Q=="
},
"node_modules/@nestjs/swagger": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.0.tgz",
"integrity": "sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==",
"dependencies": {
"@microsoft/tsdoc": "^0.14.2",
"@nestjs/mapped-types": "2.0.5",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "3.2.0",
"swagger-ui-dist": "5.11.2"
},
"peerDependencies": {
"@fastify/static": "^6.0.0 || ^7.0.0",
"@nestjs/common": "^9.0.0 || ^10.0.0",
"@nestjs/core": "^9.0.0 || ^10.0.0",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"@fastify/static": {
"optional": true
},
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/testing": {
"version": "10.3.5",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.5.tgz",
@@ -3043,8 +3100,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/array-flatten": {
"version": "1.1.1",
@@ -6754,7 +6810,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -6947,8 +7002,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.includes": {
"version": "4.3.0",
@@ -9361,6 +9415,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz",
"integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A=="
},
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",

View File

@@ -25,6 +25,7 @@
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/swagger": "^7.3.0",
"reflect-metadata": "^0.2.1",
"rxjs": "^7.8.1",
"sqlite3": "^5.1.7",

View File

@@ -1,22 +0,0 @@
import { Test, TestingModule } 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

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

View File

@@ -1,7 +0,0 @@
import { AuthGuard } from './auth.guard';
describe('AuthGuard', () => {
it('should be defined', () => {
expect(new AuthGuard()).toBeDefined();
});
});

View File

@@ -1,16 +1,26 @@
import {
CanActivate,
createParamDecorator,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { JWT_SECRET } from '../consts';
import { AuthUser } from './auth.service';
import { AccessTokenPayload } from './auth.service';
import { User } from '../user/user.entity';
import { UserService } from '../user/user.service';
export const GetUser = createParamDecorator((data, req): User => {
return req.user;
});
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
constructor(
private jwtService: JwtService,
private userService: UserService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
@@ -19,12 +29,15 @@ export class AuthGuard implements CanActivate {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
const payload: AccessTokenPayload = await this.jwtService.verifyAsync(
token,
{
secret: JWT_SECRET,
});
// 💡 We're assigning the payload to the request object here
// so that we can access it in our route handlers
request['user'] = payload as AuthUser;
},
);
if (payload.sub) {
request['user'] = await this.userService.findOne(payload.sub);
}
} catch {
throw new UnauthorizedException();
}

View File

@@ -1,18 +0,0 @@
import { Test, TestingModule } 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

@@ -2,10 +2,8 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
interface AccessTokenPayload {
export interface AccessTokenPayload {
sub: string;
name: string;
isAdmin: boolean;
}
@Injectable()
@@ -29,8 +27,6 @@ export class AuthService {
const payload: AccessTokenPayload = {
sub: user.id,
name: user.name,
isAdmin: user.isAdmin,
};
return {
@@ -38,8 +34,3 @@ export class AuthService {
};
}
}
export type AuthUser = {
id: string;
name: string;
isAdmin: boolean;
};

View File

@@ -0,0 +1,19 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import 'reflect-metadata';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import * as fs from 'fs';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
const config = new DocumentBuilder().build();
const document = SwaggerModule.createDocument(app, config, {
deepScanRoutes: true,
});
SwaggerModule.setup('openapi', app, document);
fs.writeFileSync('./swagger-spec.json', JSON.stringify(document));
}
bootstrap();

View File

@@ -1,10 +1,21 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import 'reflect-metadata';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import * as fs from 'fs';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
const config = new DocumentBuilder().build();
const document = SwaggerModule.createDocument(app, config, {
deepScanRoutes: true,
});
SwaggerModule.setup('openapi', app, document);
fs.writeFileSync('./swagger-spec.json', JSON.stringify(document));
await app.listen(3000);
}
bootstrap();

View File

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

View File

@@ -8,55 +8,65 @@ import {
Param,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import { UserService } from './user.service';
import { AuthGuard } from '../auth/auth.guard';
import { AuthUser } from 'src/auth/auth.service';
import { AuthGuard, GetUser } from '../auth/auth.guard';
import { ApiNotFoundResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { CreateUserDto, UserDto } from './user.dto';
import { User } from './user.entity';
@ApiTags('user')
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@UseGuards(AuthGuard)
@Get()
async getProfile(@Request() req) {
const user = await this.userService.findOne((req.user as AuthUser).id);
@ApiNotFoundResponse({ description: 'User not found' })
@ApiOkResponse({ description: 'User found', type: UserDto })
async getProfile(@GetUser() user: User): Promise<UserDto> {
if (!user) {
throw new NotFoundException();
}
return user;
return UserDto.fromEntity(user);
}
@UseGuards(AuthGuard)
@Get(':id')
async findById(@Param('id') id: string) {
@ApiOkResponse({ description: 'User found', type: UserDto })
@ApiNotFoundResponse({ description: 'User not found' })
async findById(
@Param('id') id: string,
@GetUser() callerUser: User,
): Promise<UserDto> {
if (!callerUser.isAdmin && callerUser.id !== id) {
throw new NotFoundException();
}
const user = await this.userService.findOne(id);
if (!user) {
throw new NotFoundException();
}
return user;
return UserDto.fromEntity(user);
}
@HttpCode(HttpStatus.OK)
@Post()
async create(
@Body()
userCreateDto: {
name: string;
password: string;
isAdmin?: boolean;
},
userCreateDto: CreateUserDto,
) {
const canCreateAdmin = await this.userService.noPreviousAdmins();
return this.userService.create(
const user = await this.userService.create(
userCreateDto.name,
userCreateDto.password,
canCreateAdmin && userCreateDto.isAdmin,
);
return UserDto.fromEntity(user);
}
}

View File

@@ -0,0 +1,21 @@
import { OmitType, PickType } from '@nestjs/swagger';
import { User } from './user.entity';
export class UserDto extends OmitType(User, ['password'] as const) {
static fromEntity(entity: User): UserDto {
return {
id: entity.id,
name: entity.name,
isAdmin: entity.isAdmin,
settings: entity.settings,
};
}
}
export class CreateUserDto extends PickType(User, [
'name',
'password',
'isAdmin',
] as const) {}
export class UpdateUserDto extends OmitType(User, ['id'] as const) {}

View File

@@ -1,6 +1,60 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
const DEFAULT_SETTINGS = {
export class SonarrSettings {
@ApiProperty({ required: true })
apiKey: string;
@ApiProperty({ required: true })
baseUrl: string;
@ApiProperty({ required: true })
qualityProfileId: number;
@ApiProperty({ required: true })
rootFolderPath: string;
@ApiProperty({ required: true })
languageProfileId: number;
}
export class RadarrSettings {
@ApiProperty({ required: true })
apiKey: string;
@ApiProperty({ required: true })
baseUrl: string;
@ApiProperty({ required: true })
qualityProfileId: number;
@ApiProperty({ required: true })
rootFolderPath: string;
}
export class JellyfinSettings {
@ApiProperty({ required: true })
apiKey: string;
@ApiProperty({ required: true })
baseUrl: string;
@ApiProperty({ required: true })
userId: string;
}
export class Settings {
@ApiProperty({ required: true })
autoplayTrailers: boolean;
@ApiProperty({ required: true })
language: string;
@ApiProperty({ required: true })
animationDuration: number;
// discover: {
// region: string,
// excludeLibraryItems: true,
// includedLanguages: 'en'
// },
@ApiProperty({ required: true, type: SonarrSettings })
sonarr: SonarrSettings;
@ApiProperty({ required: true, type: RadarrSettings })
radarr: RadarrSettings;
@ApiProperty({ required: true, type: JellyfinSettings })
jellyfin: JellyfinSettings;
}
const DEFAULT_SETTINGS: Settings = {
autoplayTrailers: true,
language: 'en',
animationDuration: 300,
@@ -31,45 +85,23 @@ const DEFAULT_SETTINGS = {
@Entity()
export class User {
@ApiProperty({ required: true })
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({ required: true })
@Column({ unique: true })
name: string;
@ApiProperty({ required: true })
@Column()
password: string;
@ApiProperty({ required: true })
@Column()
isAdmin: boolean = false;
@ApiProperty({ required: true, type: Settings })
@Column('json', { default: JSON.stringify(DEFAULT_SETTINGS) })
settings: {
autoplayTrailers: boolean;
language: string;
animationDuration: number;
// discover: {
// region: string,
// excludeLibraryItems: true,
// includedLanguages: 'en'
// },
sonarr: {
apiKey: string;
baseUrl: string;
qualityProfileId: number;
rootFolderPath: string;
languageProfileId: number;
};
radarr: {
apiKey: string;
baseUrl: string;
qualityProfileId: number;
rootFolderPath: string;
};
jellyfin: {
apiKey: string;
baseUrl: string;
userId: string;
};
} = DEFAULT_SETTINGS;
settings = DEFAULT_SETTINGS;
}

View File

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

View File

@@ -0,0 +1 @@
{"openapi":"3.0.0","paths":{"/api/user":{"get":{"operationId":"UserController_getProfile","parameters":[],"responses":{"200":{"description":"User found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDto"}}}},"404":{"description":"User not found"}},"tags":["user"]},"post":{"operationId":"UserController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"200":{"description":""}},"tags":["user"]}},"/api/user/{id}":{"get":{"operationId":"UserController_findById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"User found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDto"}}}},"404":{"description":"User not found"}},"tags":["user"]}},"/api/auth":{"post":{"operationId":"AuthController_signIn","parameters":[],"responses":{"200":{"description":""}}}},"/api/auth/profile":{"get":{"operationId":"AuthController_getProfile","parameters":[],"responses":{"200":{"description":""}}}},"/api":{"get":{"operationId":"AppController_getHello","parameters":[],"responses":{"200":{"description":""}}}}},"info":{"title":"","description":"","version":"1.0.0","contact":{}},"tags":[],"servers":[],"components":{"schemas":{"SonarrSettings":{"type":"object","properties":{"apiKey":{"type":"string"},"baseUrl":{"type":"string"},"qualityProfileId":{"type":"number"},"rootFolderPath":{"type":"string"},"languageProfileId":{"type":"number"}},"required":["apiKey","baseUrl","qualityProfileId","rootFolderPath","languageProfileId"]},"RadarrSettings":{"type":"object","properties":{"apiKey":{"type":"string"},"baseUrl":{"type":"string"},"qualityProfileId":{"type":"number"},"rootFolderPath":{"type":"string"}},"required":["apiKey","baseUrl","qualityProfileId","rootFolderPath"]},"JellyfinSettings":{"type":"object","properties":{"apiKey":{"type":"string"},"baseUrl":{"type":"string"},"userId":{"type":"string"}},"required":["apiKey","baseUrl","userId"]},"Settings":{"type":"object","properties":{"autoplayTrailers":{"type":"boolean"},"language":{"type":"string"},"animationDuration":{"type":"number"},"sonarr":{"$ref":"#/components/schemas/SonarrSettings"},"radarr":{"$ref":"#/components/schemas/RadarrSettings"},"jellyfin":{"$ref":"#/components/schemas/JellyfinSettings"}},"required":["autoplayTrailers","language","animationDuration","sonarr","radarr","jellyfin"]},"UserDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"isAdmin":{"type":"boolean"},"settings":{"$ref":"#/components/schemas/Settings"}},"required":["id","name","isAdmin","settings"]},"CreateUserDto":{"type":"object","properties":{"name":{"type":"string"},"password":{"type":"string"},"isAdmin":{"type":"boolean"}},"required":["name","password","isAdmin"]}}}}

8
package-lock.json generated
View File

@@ -27,7 +27,7 @@
"eslint-plugin-svelte": "^2.35.1",
"hls.js": "^1.4.14",
"openapi-fetch": "^0.8.2",
"openapi-typescript": "^6.7.3",
"openapi-typescript": "^6.7.5",
"postcss": "^8.4.35",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
@@ -4877,9 +4877,9 @@
}
},
"node_modules/openapi-typescript": {
"version": "6.7.3",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.3.tgz",
"integrity": "sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==",
"version": "6.7.5",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.5.tgz",
"integrity": "sha512-ZD6dgSZi0u1QCP55g8/2yS5hNJfIpgqsSGHLxxdOjvY7eIrXzj271FJEQw33VwsZ6RCtO/NOuhxa7GBWmEudyA==",
"dev": true,
"dependencies": {
"ansi-colors": "^4.1.3",

View File

@@ -38,7 +38,7 @@
"eslint-plugin-svelte": "^2.35.1",
"hls.js": "^1.4.14",
"openapi-fetch": "^0.8.2",
"openapi-typescript": "^6.7.3",
"openapi-typescript": "^6.7.5",
"postcss": "^8.4.35",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",

View File

@@ -0,0 +1,137 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/api/user": {
get: operations["UserController_getProfile"];
post: operations["UserController_create"];
};
"/api/user/{id}": {
get: operations["UserController_findById"];
};
"/api/auth": {
post: operations["AuthController_signIn"];
};
"/api/auth/profile": {
get: operations["AuthController_getProfile"];
};
"/api": {
get: operations["AppController_getHello"];
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
SonarrSettings: {
apiKey: string;
baseUrl: string;
qualityProfileId: number;
rootFolderPath: string;
languageProfileId: number;
};
RadarrSettings: {
apiKey: string;
baseUrl: string;
qualityProfileId: number;
rootFolderPath: string;
};
JellyfinSettings: {
apiKey: string;
baseUrl: string;
userId: string;
};
Settings: {
autoplayTrailers: boolean;
language: string;
animationDuration: number;
sonarr: components["schemas"]["SonarrSettings"];
radarr: components["schemas"]["RadarrSettings"];
jellyfin: components["schemas"]["JellyfinSettings"];
};
UserDto: {
id: string;
name: string;
isAdmin: boolean;
settings: components["schemas"]["Settings"];
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
UserController_getProfile: {
responses: {
/** @description User found */
200: {
content: {
"application/json": components["schemas"]["UserDto"];
};
};
/** @description User not found */
404: {
content: never;
};
};
};
UserController_create: {
responses: {
200: {
content: never;
};
};
};
UserController_findById: {
parameters: {
path: {
id: string;
};
};
responses: {
/** @description User found */
200: {
content: {
"application/json": components["schemas"]["UserDto"];
};
};
/** @description User not found */
404: {
content: never;
};
};
};
AuthController_signIn: {
responses: {
200: {
content: never;
};
};
};
AuthController_getProfile: {
responses: {
200: {
content: never;
};
};
};
AppController_getHello: {
responses: {
200: {
content: never;
};
};
};
}

View File

@@ -1,8 +1,10 @@
import './app.css'
import App from './App.svelte'
import './app.css';
import App from './App.svelte';
const app = new App({
target: document.getElementById('app'),
})
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
target: document.getElementById('app')
});
export default app
export default app;

View File

@@ -14,7 +14,8 @@
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
// "paths": {
"noUncheckedIndexedAccess": true
// "paths": {
// "$lib/*": ["src/lib/*"],
// }
},