Skip to content

Commit

Permalink
AppController with home and ping routes (#50)
Browse files Browse the repository at this point in the history
* AppController with home and ping routes

* Allow Bearer authorization header

* Unify unauthorized error message

* Fix build error

* Fix CI

* remove lambda api feature
  • Loading branch information
tomitheninja authored Dec 9, 2024
1 parent ab54f61 commit 840d93b
Show file tree
Hide file tree
Showing 38 changed files with 411 additions and 294 deletions.
1 change: 1 addition & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
with:
package-manager: yarn
node-version: 20
working-directory-script: ${{matrix.component}}

docker-publish:
needs: [analysis]
Expand Down
23 changes: 23 additions & 0 deletions backend/src/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Test, TestingModule } from '@nestjs/testing';

import { AppController } from './app.controller';

describe('AppController', () => {
let controller: AppController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AppController],
}).compile();

controller = module.get<AppController>(AppController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});

it('Send ping', () => {
expect(controller.ping()).toBe('pong');
});
});
32 changes: 32 additions & 0 deletions backend/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Controller, Get, Redirect, VERSION_NEUTRAL } from '@nestjs/common';
import { ApiFoundResponse, ApiResponse, ApiTags } from '@nestjs/swagger';

@ApiTags('default')
@Controller({ version: [VERSION_NEUTRAL] })
export class AppController {
@Get('/')
@Redirect('/api', 302)
@ApiFoundResponse({
description: 'Redirects to the API documentation.',
})
home() {}

/**
* # Health check endpoint<br>
*
* This endpoint is a simple health check API designed to confirm that the server is operational.
*
* When accessed, it returns a straightforward response indicating that the service is up and running.
*
*/
@Get('/ping')
@ApiResponse({
status: 200,
description: 'Pong',
type: String,
example: 'pong',
})
ping(): string {
return 'pong';
}
}
11 changes: 3 additions & 8 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module';
import { GroupModule } from './group/group.module';
import { PingModule } from './ping/ping.module';

@Module({
imports: [
PrismaModule.forRoot({ isGlobal: true }),
PingModule,
AuthModule,
GroupModule,
],
controllers: [],
imports: [PrismaModule.forRoot({ isGlobal: true }), AuthModule, GroupModule],
controllers: [AppController],
providers: [],
})
export class AppModule {}
5 changes: 3 additions & 2 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function bootstrap(): Promise<{
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app
.setGlobalPrefix('api')
.setGlobalPrefix('api', { exclude: ['/', '/ping'] })
.enableVersioning({ type: VersioningType.URI, defaultVersion: '4' })
.enableCors({
origin: FRONTEND_CALLBACK,
Expand All @@ -39,11 +39,12 @@ export async function bootstrap(): Promise<{
'Source Code (GitHub)',
'https://github.com/kir-dev/pek-infinity',
)
.addBearerAuth()
.addCookieAuth('jwt')
.build();
const document = SwaggerModule.createDocument(app, config, {
operationIdFactory: (controller, method, version) =>
`${capitalize(controller.replace('Controller', ''))}${capitalize(method)}${version === 'v4' ? '' : version}`,
`${capitalize(controller.replace('Controller', ''))}${capitalize(method)}${!version || version === 'v4' ? '' : version}`,
});
SwaggerModule.setup('api', app, document);

Expand Down
4 changes: 2 additions & 2 deletions backend/src/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Request } from 'express';
import { ExtractJwt, Strategy } from 'passport-jwt';

import { UserDto } from '@/auth/entities/user.entity';
Expand All @@ -12,7 +11,8 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(req: Request) => extractJwtTokenFromCookie(req),
ExtractJwt.fromAuthHeaderAsBearerToken(),
extractJwtTokenFromCookie,
]),
ignoreExpiration: false,
secretOrKey: JWT_SECRET,
Expand Down
3 changes: 0 additions & 3 deletions backend/src/ping/entities/ping.entity.ts

This file was deleted.

24 changes: 0 additions & 24 deletions backend/src/ping/ping.controller.spec.ts

This file was deleted.

21 changes: 0 additions & 21 deletions backend/src/ping/ping.controller.ts

This file was deleted.

8 changes: 0 additions & 8 deletions backend/src/ping/ping.module.ts

This file was deleted.

14 changes: 0 additions & 14 deletions backend/src/ping/ping.service.ts

This file was deleted.

4 changes: 3 additions & 1 deletion backend/src/utils/auth.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ export function getHostFromUrl(url: string): string {
return hostWithPort.split(':')[0];
}

export const NO_AUTH_TOKEN_ERROR = 'JWT cookie or Bearer token not found';

export function extractJwtTokenFromCookie(req: Request): string {
const jwtCookie = getCookiesAsObject(req).jwt;
if (!jwtCookie) {
throw new UnauthorizedException('JWT cookie not found');
throw new UnauthorizedException(NO_AUTH_TOKEN_ERROR);
}
return jwtCookie;
}
Expand Down
4 changes: 4 additions & 0 deletions backend/src/utils/controller.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { applyDecorators, Controller } from '@nestjs/common';
import {
ApiBearerAuth,
ApiCookieAuth,
ApiForbiddenResponse,
ApiInternalServerErrorResponse,
ApiTags,
Expand Down Expand Up @@ -34,6 +36,8 @@ export function ApiController(
type: AxiosErrorDto<ForbiddenErrorDto>,
description: 'Forbidden',
}),
ApiBearerAuth(),
ApiCookieAuth('jwt'),
]
: []),
);
Expand Down
6 changes: 4 additions & 2 deletions backend/src/utils/errors.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';

import { NO_AUTH_TOKEN_ERROR } from './auth.utils';

@ApiExtraModels()
export class UnauthorizedErrorDto {
@ApiProperty({ enum: ['JWT cookie not found'] })
message = 'JWT cookie not found';
@ApiProperty({ enum: ['Authorization token not found'] })
message = NO_AUTH_TOKEN_ERROR;
@ApiProperty({ enum: [401] })
statusCode = 401;

Expand Down
12 changes: 6 additions & 6 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// import './patch-server-axios';
import 'server-only';
import './globals.css';

import { axiosInstance } from '@kubb/swagger-client/client';
import { cookies } from 'next/headers';

// Reinitialize the server side axios instance with the correct values
axiosInstance.interceptors.request.use((config) => {
config.headers.cookie = cookies().toString();
if (config.url?.startsWith('/api/v')) {
const baseURL = getBackend({ preferredNetwork: 'private' });
config.url = `${baseURL}${config.url}`;
if (config.url && new RegExp('^/api/v4|/ping').test(config.url)) {
config.url = getBackend({ preferredNetwork: 'private' }) + config.url;
}
return config;
});

import './globals.css';

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';

Expand Down
4 changes: 2 additions & 2 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { AuthButton } from '@/components/auth-button';
import { ClientSideProfile } from '@/components/client-side-profile';
import { Navbar } from '@/components/navbar';
import { ServerSideProfile } from '@/components/server-side-profile';
import { pingSend } from '@/pek-api';
import { appPing } from '@/pek-api/clients';

export default async function Home() {
const { ping } = await pingSend();
const ping = await appPing();
return (
<>
<Navbar />
Expand Down
2 changes: 1 addition & 1 deletion frontend/kubb.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig(() => {
plugins: [
pluginOas(),
pluginTs(),
pluginClient(),
pluginClient(), // Consider adding `{ output: { path: 'axios' } }`
pluginTanstackQuery({
framework: 'react',
queryOptions: {
Expand Down
5 changes: 3 additions & 2 deletions frontend/lib/get-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export function getBackend({ preferredNetwork }: { preferredNetwork: 'private' |
const PUBLIC_API = process.env.NEXT_PUBLIC_API_URL;

const hasPrivateApi = Boolean(PRIVATE_API);
const componentRequestedPrivateApi = preferredNetwork === 'private';

if (hasPrivateApi && componentRequestedPrivateApi) return PRIVATE_API!;
if (preferredNetwork === 'private' && hasPrivateApi) {
return PRIVATE_API!;
}

// If this is a vercel preview, use the compiled backend via page router
if (process.env.VERCEL_ENV === 'preview') {
Expand Down
19 changes: 10 additions & 9 deletions frontend/pages/api/[[...slug]].ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res.status(403).json({ message: 'Lambda API is disabled in this environment' });
return;
}
const { bootstrap } = await import('backend/dist/app.js');
const { app } = await bootstrap();
const server = (await app.init()).getHttpAdapter().getInstance();
res.status(501).json({ message: 'Not implemented' });
// const { bootstrap } = await import('backend/dist/app.js');
// const { app } = await bootstrap();
// const server = (await app.init()).getHttpAdapter().getInstance();

return new Promise<void>((resolve) => {
res.on('finish', () => {
resolve();
});
// return new Promise<void>((resolve) => {
// res.on('finish', () => {
// resolve();
// });

server(req, res);
});
// server(req, res);
// });
}
11 changes: 11 additions & 0 deletions frontend/pek-api/clients/appHome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import client from "@kubb/swagger-client/client";
import type { ResponseConfig } from "@kubb/swagger-client/client";
import type { AppHomeQueryResponse } from "../types/AppHome";

/**
* @link /
*/
export async function appHome(options: Partial<Parameters<typeof client>[0]> = {}): Promise<ResponseConfig<AppHomeQueryResponse>["data"]> {
const res = await client<AppHomeQueryResponse>({ method: "get", url: `/`, ...options });
return res.data;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import client from "@kubb/swagger-client/client";
import type { ResponseConfig } from "@kubb/swagger-client/client";
import type { PingSendQueryResponse } from "../types/PingSend";
import type { AppPingQueryResponse } from "../types/AppPing";

/**
* @description # Health check endpoint<br>This endpoint is a simple health check API designed to confirm that the server is operational.When accessed, it returns a straightforward response indicating that the service is up and running.
* @link /api/v4/ping
* @link /ping
*/
export async function pingSend(options: Partial<Parameters<typeof client>[0]> = {}): Promise<ResponseConfig<PingSendQueryResponse>["data"]> {
const res = await client<PingSendQueryResponse>({ method: "get", url: `/api/v4/ping`, ...options });
export async function appPing(options: Partial<Parameters<typeof client>[0]> = {}): Promise<ResponseConfig<AppPingQueryResponse>["data"]> {
const res = await client<AppPingQueryResponse>({ method: "get", url: `/ping`, ...options });
return res.data;
}
5 changes: 3 additions & 2 deletions frontend/pek-api/clients/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./appHome";
export * from "./appPing";
export * from "./authLogin";
export * from "./authLogout";
export * from "./authMe";
Expand All @@ -6,5 +8,4 @@ export * from "./groupCreate";
export * from "./groupFindAll";
export * from "./groupFindOne";
export * from "./groupRemove";
export * from "./groupUpdate";
export * from "./pingSend";
export * from "./groupUpdate";
8 changes: 6 additions & 2 deletions frontend/pek-api/clients/operations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export const operations = {
"PingSend": {
"path": "/api/v4/ping",
"AppHome": {
"path": "/",
"method": "get"
},
"AppPing": {
"path": "/ping",
"method": "get"
},
"AuthLogin": {
Expand Down
Loading

0 comments on commit 840d93b

Please sign in to comment.