Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

package upgrade: OpenAPI stack #47

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,51 +41,51 @@
}
},
"dependencies": {
"@kir-dev/passport-authsch": "^2.1.0",
"@nestjs/common": "^10.4.1",
"@nestjs/core": "^10.4.1",
"@kir-dev/passport-authsch": "^2.2.1",
"@nestjs/common": "^10.4.15",
"@nestjs/core": "^10.4.15",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.4.1",
"@nestjs/swagger": "^7.4.0",
"@prisma/client": "^5.19.1",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/swagger": "^8.1.0",
"@prisma/client": "^5.22.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"dotenv": "^16.4.7",
"env-var": "^7.5.0",
"express": "^4.21.0",
"express": "^4.21.2",
"nestjs-prisma": "^0.23.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"prisma": "^5.19.1",
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"yaml": "^2.5.1"
"yaml": "^2.6.1"
},
"devDependencies": {
"@nestjs/cli": "^10.4.5",
"@nestjs/schematics": "^10.1.4",
"@nestjs/testing": "^10.4.1",
"@nestjs/cli": "^10.4.9",
"@nestjs/schematics": "^10.2.3",
"@nestjs/testing": "^10.4.15",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.13",
"@types/node": "^20.16.5",
"@types/jest": "^29.5.14",
"@types/node": "^20.17.9",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@vercel/style-guide": "^6.0.0",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.6.2",
"webpack": "^5.94.0"
"typescript": "^5.7.2",
"webpack": "^5.97.1"
}
}
2 changes: 1 addition & 1 deletion backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ApiController } from '@/utils/controller.decorator';
import { AuthService } from './auth.service';
import { UserDto } from './entities/user.entity';

@ApiController('auth')
@ApiController('auth', { authStrategy: 'NOT_ENFORCED' })
export class AuthController {
constructor(private readonly authService: AuthService) {}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/group/group.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UpdateGroupDto,
} from './dto/group.dto';

@ApiController('group')
@ApiController('group', { authStrategy: 'ENFORCED' })
export class GroupController {
constructor(private readonly groupService: GroupService) {}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/ping/ping.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ApiController } from '@/utils/controller.decorator';

import { Ping } from './entities/ping.entity';

@ApiController('ping', { withAuth: false })
@ApiController('ping', { authStrategy: 'UNRESTRICTED' })
export class PingController {
/**
* # Health check endpoint<br>
Expand Down
80 changes: 65 additions & 15 deletions backend/src/utils/controller.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,90 @@
import { applyDecorators, Controller } from '@nestjs/common';
import { applyDecorators, Controller, UseGuards } from '@nestjs/common';
import {
ApiForbiddenResponse,
ApiInternalServerErrorResponse,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { JwtGuard } from '@/auth/guards/jwt.guard';

import {
AxiosErrorDto,
ForbiddenErrorDto,
InternalServerErrorDto,
UnauthorizedErrorDto,
} from './errors.dto';

export interface ApiControllerOptions {
/**
* Strategy for authentication

* ## options
* - 'UNRESTRICTED' - no authentication required
* - 'NOT_ENFORCED' - authentication reponse types are added, but authentication is not enforced
* - 'ENFORCED' - authentication is enforced, request is rejected if not authenticated
*/
authStrategy?: 'UNRESTRICTED' | 'NOT_ENFORCED' | 'ENFORCED';
}

export function ApiController(
name: string,
{ withAuth = true } = {},
{ authStrategy = 'ENFORCED' }: ApiControllerOptions = {},
): ClassDecorator {
return applyDecorators(
const decorators = [];
decorators.push(
Controller(name),
ApiTags(name),
ApiInternalServerErrorResponse({
type: AxiosErrorDto<InternalServerErrorDto>,
description: 'Internal Server Error',
example: {
response: {
data: {
statusCode: 500,
error: 'Internal Server Error',
message: 'Human readable error',
},
},
status: 500,
},
}),
...(withAuth
? [
ApiUnauthorizedResponse({
type: AxiosErrorDto<UnauthorizedErrorDto>,
description: 'Unauthorized',
}),
ApiForbiddenResponse({
type: AxiosErrorDto<ForbiddenErrorDto>,
description: 'Forbidden',
}),
]
: []),
);
if (authStrategy !== 'UNRESTRICTED') {
decorators.push(
ApiUnauthorizedResponse({
type: AxiosErrorDto<UnauthorizedErrorDto>,
description: 'Unauthorized',
example: {
response: {
data: {
statusCode: 401,
error: 'Unauthorized',
message: 'Human readable error',
},
},
status: 401,
},
}),
ApiForbiddenResponse({
type: AxiosErrorDto<ForbiddenErrorDto>,
description: 'Forbidden',
example: {
response: {
data: {
statusCode: 403,
error: 'Forbidden',
message: 'Human readable error',
},
},
status: 403,
},
}),
);
}
if (authStrategy === 'ENFORCED') {
decorators.push(UseGuards(JwtGuard));
}

return applyDecorators(...decorators);
}
3 changes: 2 additions & 1 deletion frontend/app/debuginfo/debug-client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import { axiosInstance } from '@kubb/swagger-client/client';

import { axiosInstance } from '@kubb/plugin-client/client';

export function PekClientDebug() {
return <p>{axiosInstance.defaults.baseURL ?? ''}</p>;
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// import './patch-server-axios';
import { axiosInstance } from '@kubb/swagger-client/client';
import { axiosInstance } from '@kubb/plugin-client/client';
import { cookies } from 'next/headers';

axiosInstance.interceptors.request.use((config) => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/patch-server-axios.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { axiosInstance } from '@kubb/swagger-client/client';
import { axiosInstance } from '@kubb/plugin-client/client';
import { cookies } from 'next/headers';

import { getBackend } from '@/lib/get-backend';
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { axiosInstance } from '@kubb/swagger-client/client';
import { axiosInstance } from '@kubb/plugin-client/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/client-side-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function ClientSideProfile() {
const { data: me, status, error } = useAuthMe({ query: { retry: false } });
if (status === 'pending') return <h1>Loading...</h1>;
if (status === 'error' && error.status === 401) return <h1>Client: Anonymous</h1>;
if (status === 'error') return <h1>Something went wrong {error.response.data.message}</h1>;
if (status === 'error') return <h1>Something went wrong {error.response?.data?.message}</h1>;

return <h1>Client: {me.name}</h1>;
}
37 changes: 0 additions & 37 deletions frontend/kubb.config.bundled_ismzvejjn3f.mjs

This file was deleted.

23 changes: 4 additions & 19 deletions frontend/kubb.config.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import { defineConfig } from '@kubb/core';
import { pluginClient } from '@kubb/plugin-client';
import { pluginOas } from '@kubb/plugin-oas';
import { pluginClient } from '@kubb/swagger-client';
import { pluginTanstackQuery } from '@kubb/swagger-tanstack-query';
import { pluginTs } from '@kubb/swagger-ts';
import { AxiosError } from 'axios';
import { pluginReactQuery } from '@kubb/plugin-react-query';
import { pluginTs } from '@kubb/plugin-ts';

export default defineConfig(() => {
return {
root: '.',

input: {
path: '../openapi.yaml',
},
output: {
clean: true,
path: './pek-api',
},
plugins: [
pluginOas(),
pluginTs(),
pluginClient(),
pluginTanstackQuery({
framework: 'react',
queryOptions: {
retry: (count: number, error: Error) => {
if (error instanceof AxiosError && error.response?.status === 401) return false;
return count <= 2;
},
},
}),
],
plugins: [pluginOas(), pluginTs(), pluginClient(), pluginReactQuery()],
};
});
40 changes: 19 additions & 21 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,41 @@
"lint:fix": "eslint --fix --ext .ts,.tsx .",
"lint": "eslint --ext .ts,.tsx .",
"generate": "npx kubb generate",
"generate:watch": "nodemon --watch ../openapi.yaml --exec \"yarn generate\""
"generate:watch": "npx kubb generate --watch"
},
"dependencies": {
"@kubb/cli": "^2.26.3",
"@kubb/core": "^2.26.3",
"@kubb/plugin-oas": "^2.26.3",
"@kubb/swagger": "^2.26.3",
"@kubb/swagger-client": "^2.26.3",
"@kubb/swagger-tanstack-query": "^2.26.3",
"@kubb/swagger-ts": "^2.26.3",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@kubb/cli": "^3.1.0",
"@kubb/core": "^3.1.0",
"@kubb/plugin-client": "^3.1.0",
"@kubb/plugin-oas": "^3.1.0",
"@kubb/plugin-react-query": "^3.1.0",
"@kubb/plugin-ts": "^3.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.56.2",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"@tanstack/react-query": "^5.62.3",
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.441.0",
"lucide-react": "^0.468.0",
"next": "14.2.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.5.2",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.16.5",
"@types/node": "^20.17.9",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vercel/style-guide": "^6.0.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.11",
"eslint-plugin-react": "^7.36.1",
"nodemon": "^3.1.4",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.11",
"eslint": "^8.57.1",
"eslint-config-next": "14.2.20",
"eslint-plugin-react": "^7.37.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.16",
"typescript": "~5.6.2"
}
}
Loading