├── backend ├── .dockerignore ├── public │ └── assets │ │ └── css │ │ └── input.css ├── .prettierrc ├── tsconfig.build.json ├── prisma │ └── migrations │ │ ├── migration_lock.toml │ │ ├── 20251126175752_add_oncascade_to_city_postal_code │ │ └── migration.sql │ │ ├── 20251126131743_drop_country_table │ │ └── migration.sql │ │ └── 20251126134859_create_cities_table │ │ └── migration.sql ├── src │ ├── app.service.ts │ ├── features │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── crons │ │ │ │ ├── clear-expired-sessions.cron.ts │ │ │ │ └── clear-expired-verification-tokens.cron.ts │ │ │ ├── validators │ │ │ │ └── auth.validators.ts │ │ │ ├── auth.module.ts │ │ │ └── strategies │ │ │ │ └── jwt.strategy.ts │ │ ├── country │ │ │ ├── country.module.ts │ │ │ └── controllers │ │ │ │ └── country.controller.ts │ │ ├── customer │ │ │ ├── validators │ │ │ │ ├── customer.customer.validators.ts │ │ │ │ └── customer.validators.ts │ │ │ ├── customer.module.ts │ │ │ ├── customer.service.ts │ │ │ └── controllers │ │ │ │ └── customer.customer.controller.ts │ │ ├── demo │ │ │ ├── commands │ │ │ │ ├── impl │ │ │ │ │ └── kill-dragon.command.ts │ │ │ │ └── handlers │ │ │ │ │ └── kill-dragon.handler.ts │ │ │ ├── demo.module.ts │ │ │ ├── dto │ │ │ │ └── demo.dto.ts │ │ │ ├── demo.consumer.ts │ │ │ └── validators │ │ │ │ └── demo.validators.ts │ │ ├── media │ │ │ ├── media.module.ts │ │ │ ├── crons │ │ │ │ └── delete-orphan-medias.cron.ts │ │ │ ├── controllers │ │ │ │ └── media.controller.ts │ │ │ └── media.consumer.ts │ │ ├── application │ │ │ ├── application.module.ts │ │ │ └── services │ │ │ │ ├── ffmpeg.service.ts │ │ │ │ ├── brevo.service.ts │ │ │ │ ├── files.service.ts │ │ │ │ ├── pdf.service.ts │ │ │ │ └── database.service.ts │ │ └── cli │ │ │ ├── commands │ │ │ ├── basic.command.ts │ │ │ ├── create-admin-account.command.ts │ │ │ ├── generate-jwt-keys.command.ts │ │ │ └── seed.command.ts │ │ │ ├── cli.module.ts │ │ │ └── seeders │ │ │ ├── fake-data.seeder.ts │ │ │ ├── cities.seeder.ts │ │ │ └── users.seeder.ts │ ├── cli.ts │ ├── core │ │ ├── logger │ │ │ ├── logger.module.ts │ │ │ ├── logger.config.ts │ │ │ └── README.md │ │ ├── types │ │ │ └── request.d.ts │ │ ├── decorators │ │ │ ├── roles.decorator.ts │ │ │ └── allow-anonymous.decorator.ts │ │ ├── guards │ │ │ ├── throttler-behind-proxy.guard.ts │ │ │ ├── jwt-auth.guard.ts │ │ │ └── roles.guard.ts │ │ ├── exceptions │ │ │ ├── oauth-redirect.exception.ts │ │ │ └── validation.exception.ts │ │ ├── filters │ │ │ ├── oauth-redirect.filter.ts │ │ │ └── unhandled-exceptions.filter.ts │ │ └── pipes │ │ │ ├── trim-strings.pipe.ts │ │ │ └── zod-validation.pipe.ts │ ├── app.controller.ts │ ├── instrument.ts │ ├── app.controller.spec.ts │ ├── main.ts │ └── app.module.ts ├── prisma.config.ts ├── test │ ├── jest-e2e.json │ ├── app.e2e-spec.ts │ ├── helpers │ │ └── test-database.helper.ts │ └── fixtures │ │ ├── test-users.fixture.ts │ │ └── test-cities.fixture.ts ├── nest-cli.json ├── tsconfig.json ├── views │ └── invoice.hbs ├── .gitignore ├── eslint.config.mjs └── Dockerfile ├── frontend ├── .dockerignore ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── (browse) │ │ │ ├── page.tsx │ │ │ ├── header.tsx │ │ │ ├── layout.tsx │ │ │ └── customer-area │ │ │ │ └── forms │ │ │ │ └── combobox-examples │ │ │ │ └── page.tsx │ │ ├── (workspaces) │ │ │ └── admin-area │ │ │ │ ├── page.tsx │ │ │ │ ├── _components │ │ │ │ ├── nav-secondary.tsx │ │ │ │ ├── nav-main.tsx │ │ │ │ └── nav-documents.tsx │ │ │ │ ├── content.tsx │ │ │ │ └── layout.tsx │ │ ├── providers.tsx │ │ ├── auth │ │ │ ├── forgot-password │ │ │ │ ├── page.tsx │ │ │ │ ├── verify │ │ │ │ │ └── page.tsx │ │ │ │ └── reset │ │ │ │ │ └── page.tsx │ │ │ ├── signin │ │ │ │ └── page.tsx │ │ │ └── customer │ │ │ │ └── signup │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── assets │ │ └── images │ │ │ └── logo.png │ ├── lib │ │ ├── utils.ts │ │ ├── query-client.ts │ │ ├── luni-auth │ │ │ ├── luni-auth.server.ts │ │ │ └── luni-auth.provider.tsx │ │ ├── wolfios │ │ │ ├── wolfios.server.ts │ │ │ └── wolfios.ts │ │ ├── date.ts │ │ └── handle-api-errors.ts │ ├── components │ │ ├── ui │ │ │ ├── centered-loading-spinner.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── no-ssr.tsx │ │ │ ├── label.tsx │ │ │ ├── separator.tsx │ │ │ ├── textarea.tsx │ │ │ ├── loading-spinner.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── query-error-message.tsx │ │ │ ├── input.tsx │ │ │ ├── container.tsx │ │ │ ├── sonner.tsx │ │ │ ├── avatar.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── password-input.tsx │ │ │ ├── toggle.tsx │ │ │ ├── badge.tsx │ │ │ ├── popover.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── tabs.tsx │ │ │ ├── card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── table.tsx │ │ │ └── button.tsx │ │ └── project │ │ │ └── user-avatar.tsx │ ├── hooks │ │ ├── use-mobile.ts │ │ ├── use-app-store.ts │ │ ├── use-breadcrumb.tsx │ │ └── use-debounced-value.ts │ └── middleware.ts ├── public │ ├── vercel.svg │ ├── window.svg │ ├── file.svg │ ├── globe.svg │ └── next.svg ├── next.config.ts ├── postcss.config.mjs ├── components.json ├── .gitignore ├── eslint.config.mjs ├── tsconfig.json ├── Dockerfile ├── README.md ├── .cursorrules └── package.json ├── caddy ├── Dockerfile └── Caddyfile ├── .gitignore ├── compose.override.yml ├── Makefile ├── compose.yml └── .env.example /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | logs -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /backend/public/assets/css/input.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /caddy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM caddy:2.10-alpine 2 | 3 | COPY Caddyfile /etc/caddy/Caddyfile -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostlexly/ultimate-typescript-starter-kit/HEAD/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostlexly/ultimate-typescript-starter-kit/HEAD/frontend/src/assets/images/logo.png -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "prisma.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.Dockerfile 2 | *.docker 3 | .idea/ 4 | .claude/ 5 | .php_cs.cache 6 | logs/ 7 | .DS_Store 8 | .env 9 | .env.local 10 | .env.prod 11 | node_modules 12 | -------------------------------------------------------------------------------- /backend/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" 4 | -------------------------------------------------------------------------------- /frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /backend/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | "@tailwindcss/postcss": {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/features/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const authConstants = { 2 | accessTokenExpirationMinutes: 15, // 15 minutes 3 | refreshTokenExpirationMinutes: 60 * 24 * 60, // 60 days 4 | passwordResetTokenExpirationHours: 1, 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/app/(browse)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from "@/components/ui/container"; 2 | 3 | export default function Home() { 4 | return ( 5 | This is your home page content. 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { CommandFactory } from 'nest-commander'; 2 | import { CliModule } from './features/cli/cli.module'; 3 | 4 | async function bootstrap() { 5 | await CommandFactory.run(CliModule, ['warn', 'error', 'debug', 'verbose']); 6 | } 7 | 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /backend/prisma.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'prisma/config'; 2 | 3 | export default defineConfig({ 4 | schema: 'prisma/schema.prisma', 5 | migrations: { 6 | path: 'prisma/migrations', 7 | }, 8 | datasource: { 9 | url: process.env.APP_DATABASE_CONNECTION_URL ?? '', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /backend/src/core/logger/logger.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { WinstonModule } from 'nest-winston'; 3 | import { createWinstonConfig } from './logger.config'; 4 | 5 | @Module({ 6 | imports: [WinstonModule.forRoot(createWinstonConfig())], 7 | exports: [WinstonModule], 8 | }) 9 | export class LoggerModule {} 10 | -------------------------------------------------------------------------------- /frontend/src/components/ui/centered-loading-spinner.tsx: -------------------------------------------------------------------------------- 1 | import { LoadingSpinner } from "./loading-spinner"; 2 | 3 | const CenteredLoadingSpinner = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export { CenteredLoadingSpinner }; 12 | -------------------------------------------------------------------------------- /backend/prisma/migrations/20251126175752_add_oncascade_to_city_postal_code/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "CityPostalCode" DROP CONSTRAINT "CityPostalCode_cityId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "CityPostalCode" ADD CONSTRAINT "CityPostalCode_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | }, 9 | "moduleNameMapper": { 10 | "^src/(.*)$": "/../src/$1", 11 | "^(\\.{1,2}/.*)\\.js$": "$1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/(browse)/header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { DesktopHeader } from "./_components/desktop-header"; 4 | import { MobileHeader } from "./_components/mobile-header"; 5 | 6 | const Header = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export { Header }; 17 | -------------------------------------------------------------------------------- /frontend/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /backend/prisma/migrations/20251126131743_drop_country_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `Country` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "Customer" DROP CONSTRAINT "Customer_countryCode_fkey"; 9 | 10 | -- DropTable 11 | DROP TABLE "Country"; 12 | -------------------------------------------------------------------------------- /backend/src/features/country/country.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CountryController } from './controllers/country.controller'; 3 | import { CountryService } from './country.service'; 4 | 5 | @Module({ 6 | controllers: [CountryController], 7 | providers: [CountryService], 8 | exports: [CountryService], 9 | }) 10 | export class CountryModule {} 11 | -------------------------------------------------------------------------------- /backend/src/features/customer/validators/customer.customer.validators.ts: -------------------------------------------------------------------------------- 1 | import z from 'zod'; 2 | 3 | export const customerCustomerInformationsSchema = z.object({ 4 | body: z.object({ 5 | countryCode: z.string().min(2).max(2), 6 | city: z.uuid(), 7 | }), 8 | }); 9 | export type CustomerCustomerInformationsDto = z.infer< 10 | typeof customerCustomerInformationsSchema 11 | >; 12 | -------------------------------------------------------------------------------- /frontend/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/core/types/request.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace Express { 3 | interface Request { 4 | // Get the real ip address from cloudflare or other proxies 5 | clientIp?: string; 6 | } 7 | 8 | interface User { 9 | sessionId: string; 10 | role: string; 11 | accountId: string; 12 | email: string; 13 | } 14 | } 15 | } 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /frontend/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/core/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { Role } from 'src/generated/prisma/client'; 3 | 4 | export const ROLES_KEY = 'Roles'; 5 | 6 | /** 7 | * @description Decorator to mark a route as requiring a specific role 8 | * @param roles - The roles required to access the route 9 | */ 10 | export const Roles = (roles: Role[]) => SetMetadata(ROLES_KEY, roles); 11 | -------------------------------------------------------------------------------- /backend/src/core/guards/throttler-behind-proxy.guard.ts: -------------------------------------------------------------------------------- 1 | import { ThrottlerGuard } from '@nestjs/throttler'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | @Injectable() 5 | export class ThrottlerBehindProxyGuard extends ThrottlerGuard { 6 | protected getTracker(req: Record) { 7 | return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/app/(workspaces)/admin-area/page.tsx: -------------------------------------------------------------------------------- 1 | import { wolfiosServer } from "@/lib/wolfios/wolfios.server"; 2 | import { Content } from "./content"; 3 | 4 | export default async function Page() { 5 | await wolfiosServer.get("/api/demos/protected-route").then((res) => { 6 | console.log(res.data); 7 | console.log("A protected route has been successfully accessed."); 8 | }); 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | import { AllowAnonymous } from './core/decorators/allow-anonymous.decorator'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private appService: AppService) {} 8 | 9 | @Get() 10 | @AllowAnonymous() 11 | getHello(): string { 12 | return this.appService.getHello(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/instrument.ts: -------------------------------------------------------------------------------- 1 | // Import with `const Sentry = require("@sentry/nestjs");` if you are using CJS 2 | import * as Sentry from '@sentry/nestjs'; 3 | 4 | Sentry.init({ 5 | dsn: 'https://8a784865ef631facda2dc3c4c341b71b@o4509033579020288.ingest.de.sentry.io/4509967198191696', 6 | // Setting this option to true will send default PII data to Sentry. 7 | // For example, automatic IP address collection on events 8 | sendDefaultPii: true, 9 | }); 10 | -------------------------------------------------------------------------------- /backend/src/core/exceptions/oauth-redirect.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class OAuthRedirectException extends HttpException { 4 | constructor( 5 | public readonly redirectUrl: string, 6 | public readonly errorCode: string, 7 | ) { 8 | super( 9 | { 10 | redirectUrl, 11 | errorCode, 12 | }, 13 | HttpStatus.FOUND, // 302 redirect 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/features/demo/commands/impl/kill-dragon.command.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '@nestjs/cqrs'; 2 | 3 | export class KillDragonCommand extends Command<{ 4 | dragonId: string; 5 | heroId: string; 6 | killed: boolean; 7 | }> { 8 | heroId: string; 9 | dragonId: string; 10 | 11 | constructor(data: { heroId: string; dragonId: string }) { 12 | super(); 13 | this.heroId = data.heroId; 14 | this.dragonId = data.dragonId; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/components/ui/no-ssr.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | const NoSsr = ({ 6 | children, 7 | fallback, 8 | }: { 9 | children: React.ReactNode; 10 | fallback?: React.ReactNode; 11 | }) => { 12 | const [isMounted, setIsMounted] = useState(false); 13 | 14 | useEffect(() => { 15 | setIsMounted(true); 16 | }, []); 17 | 18 | return isMounted ? children : fallback; 19 | }; 20 | 21 | export { NoSsr }; 22 | -------------------------------------------------------------------------------- /backend/src/features/demo/commands/handlers/kill-dragon.handler.ts: -------------------------------------------------------------------------------- 1 | import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; 2 | import { KillDragonCommand } from '../impl/kill-dragon.command'; 3 | 4 | @CommandHandler(KillDragonCommand) 5 | export class KillDragonHandler implements ICommandHandler { 6 | async execute({ dragonId, heroId }: KillDragonCommand) { 7 | await new Promise((resolve) => setTimeout(resolve, 1000)); 8 | 9 | return { dragonId, heroId, killed: true }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true, 7 | "assets": [ 8 | { 9 | "include": "**/*.json", 10 | "exclude": "node_modules/**", 11 | "watchAssets": true 12 | }, 13 | { 14 | "include": "**/*.csv", 15 | "exclude": "node_modules/**", 16 | "watchAssets": true 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/features/demo/demo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DemoController } from './controllers/demo.controller'; 3 | import { BullModule } from '@nestjs/bullmq'; 4 | import { DemoConsumer } from './demo.consumer'; 5 | import { KillDragonHandler } from './commands/handlers/kill-dragon.handler'; 6 | 7 | @Module({ 8 | imports: [ 9 | BullModule.registerQueue({ 10 | name: 'demo', 11 | }), 12 | ], 13 | providers: [DemoConsumer, KillDragonHandler], 14 | controllers: [DemoController], 15 | }) 16 | export class DemoModule {} 17 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/assets/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/features/country/controllers/country.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Query } from '@nestjs/common'; 2 | import { AllowAnonymous } from 'src/core/decorators/allow-anonymous.decorator'; 3 | import { CountryService } from '../country.service'; 4 | 5 | @Controller('countries') 6 | @AllowAnonymous() 7 | export class CountryController { 8 | constructor(private readonly countryService: CountryService) {} 9 | 10 | @Get() 11 | getCountries(@Query('language') language: string = 'fr') { 12 | return this.countryService.getAllCountries(language); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/core/decorators/allow-anonymous.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const ALLOW_ANONYMOUS_KEY = 'allowAnonymous'; 4 | 5 | /** 6 | * @description Decorator to mark a route to allow anonymous access 7 | * AllowAnonymous transporte qu’un booléen constant. 8 | * Avec SetMetadata, on peut écrire @AllowAnonymous() sans passer d’argument. 9 | * Si on utilisait Reflector.createDecorator() , cela exigerait typiquement @AllowAnonymous(true). 10 | */ 11 | export const AllowAnonymous = () => SetMetadata(ALLOW_ANONYMOUS_KEY, true); 12 | -------------------------------------------------------------------------------- /frontend/src/app/(browse)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "@/assets/styles/globals.css"; 3 | import { Footer } from "./footer"; 4 | import { Header } from "./header"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Create Next App", 8 | description: "Generated by create next app", 9 | }; 10 | 11 | const DefaultLayout = ({ children }: { children: React.ReactNode }) => { 12 | return ( 13 | <> 14 |
15 | 16 | {children} 17 | 18 |