mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 09:26:41 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -1,12 +1,12 @@
|
|||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'postgresql',
|
||||
schema: './src/db/schema/index.ts',
|
||||
out: './src/db/migrations',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || 'postgresql://manacore:devpassword@localhost:5432/quote',
|
||||
},
|
||||
verbose: true,
|
||||
strict: true,
|
||||
dialect: 'postgresql',
|
||||
schema: './src/db/schema/index.ts',
|
||||
out: './src/db/migrations',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || 'postgresql://manacore:devpassword@localhost:5432/quote',
|
||||
},
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"assets": [],
|
||||
"watchAssets": false
|
||||
}
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"assets": [],
|
||||
"watchAssets": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"name": "@quote/backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"dev": "nest start --watch",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"type-check": "tsc --noEmit",
|
||||
"migration:generate": "drizzle-kit generate",
|
||||
"migration:run": "tsx src/db/migrate.ts",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:seed": "tsx src/db/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.38.3",
|
||||
"postgres": "^3.4.5",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^10.2.3",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
"name": "@quote/backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"dev": "nest start --watch",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"type-check": "tsc --noEmit",
|
||||
"migration:generate": "drizzle-kit generate",
|
||||
"migration:run": "tsx src/db/migrate.ts",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:seed": "tsx src/db/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.38.3",
|
||||
"postgres": "^3.4.5",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^10.2.3",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import { ListModule } from './list/list.module';
|
|||
import { HealthModule } from './health/health.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env',
|
||||
}),
|
||||
DatabaseModule,
|
||||
FavoriteModule,
|
||||
ListModule,
|
||||
HealthModule,
|
||||
],
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env',
|
||||
}),
|
||||
DatabaseModule,
|
||||
FavoriteModule,
|
||||
ListModule,
|
||||
HealthModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -9,30 +9,30 @@ let connection: ReturnType<typeof postgres> | null = null;
|
|||
let db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
export function getConnection(databaseUrl: string) {
|
||||
if (!connection) {
|
||||
connection = postgres(databaseUrl, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
return connection;
|
||||
if (!connection) {
|
||||
connection = postgres(databaseUrl, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
export function getDb(databaseUrl: string) {
|
||||
if (!db) {
|
||||
const conn = getConnection(databaseUrl);
|
||||
db = drizzle(conn, { schema });
|
||||
}
|
||||
return db;
|
||||
if (!db) {
|
||||
const conn = getConnection(databaseUrl);
|
||||
db = drizzle(conn, { schema });
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function closeConnection() {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
connection = null;
|
||||
db = null;
|
||||
}
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
connection = null;
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
|
||||
export type Database = ReturnType<typeof getDb>;
|
||||
|
|
|
|||
|
|
@ -6,23 +6,23 @@ export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
|
|||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: DATABASE_CONNECTION,
|
||||
useFactory: (configService: ConfigService): Database => {
|
||||
const databaseUrl = configService.get<string>('DATABASE_URL');
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
return getDb(databaseUrl);
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
exports: [DATABASE_CONNECTION],
|
||||
providers: [
|
||||
{
|
||||
provide: DATABASE_CONNECTION,
|
||||
useFactory: (configService: ConfigService): Database => {
|
||||
const databaseUrl = configService.get<string>('DATABASE_URL');
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
return getDb(databaseUrl);
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
exports: [DATABASE_CONNECTION],
|
||||
})
|
||||
export class DatabaseModule implements OnModuleDestroy {
|
||||
async onModuleDestroy() {
|
||||
await closeConnection();
|
||||
}
|
||||
async onModuleDestroy() {
|
||||
await closeConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,22 +8,22 @@ const postgres = require('postgres');
|
|||
dotenv.config();
|
||||
|
||||
async function runMigrations() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
|
||||
console.log('Running migrations...');
|
||||
console.log('Running migrations...');
|
||||
|
||||
const sql = postgres(databaseUrl, { max: 1 });
|
||||
const db = drizzle(sql);
|
||||
const sql = postgres(databaseUrl, { max: 1 });
|
||||
const db = drizzle(sql);
|
||||
|
||||
await migrate(db, { migrationsFolder: './src/db/migrations' });
|
||||
await migrate(db, { migrationsFolder: './src/db/migrations' });
|
||||
|
||||
await sql.end();
|
||||
await sql.end();
|
||||
|
||||
console.log('Migrations completed successfully!');
|
||||
console.log('Migrations completed successfully!');
|
||||
}
|
||||
|
||||
runMigrations().catch(console.error);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { pgTable, uuid, timestamp, unique, varchar } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const favorites = pgTable('favorites', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').notNull(),
|
||||
quoteId: varchar('quote_id', { length: 100 }).notNull(), // References static quote ID from shared package
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
uniqueUserQuote: unique().on(table.userId, table.quoteId),
|
||||
}));
|
||||
export const favorites = pgTable(
|
||||
'favorites',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').notNull(),
|
||||
quoteId: varchar('quote_id', { length: 100 }).notNull(), // References static quote ID from shared package
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
uniqueUserQuote: unique().on(table.userId, table.quoteId),
|
||||
})
|
||||
);
|
||||
|
||||
export type Favorite = typeof favorites.$inferSelect;
|
||||
export type NewFavorite = typeof favorites.$inferInsert;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { pgTable, uuid, text, timestamp, jsonb } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const userLists = pgTable('user_lists', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
quoteIds: jsonb('quote_ids').$type<string[]>().default([]), // References static quote IDs from shared package
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').notNull(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
quoteIds: jsonb('quote_ids').$type<string[]>().default([]), // References static quote IDs from shared package
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type UserList = typeof userLists.$inferSelect;
|
||||
|
|
|
|||
|
|
@ -1,79 +1,73 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Headers,
|
||||
UnauthorizedException,
|
||||
ConflictException,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Headers,
|
||||
UnauthorizedException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { FavoriteService } from './favorite.service';
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
|
||||
class CreateFavoriteDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
quoteId!: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
quoteId!: string;
|
||||
}
|
||||
|
||||
// Simple JWT extraction - in production, use proper auth middleware
|
||||
function extractUserId(authHeader?: string): string {
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid authorization header');
|
||||
}
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid authorization header');
|
||||
}
|
||||
|
||||
try {
|
||||
const token = authHeader.substring(7);
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
if (!payload.sub) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return payload.sub;
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
try {
|
||||
const token = authHeader.substring(7);
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
if (!payload.sub) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return payload.sub;
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('favorites')
|
||||
export class FavoriteController {
|
||||
constructor(private readonly favoriteService: FavoriteService) {}
|
||||
constructor(private readonly favoriteService: FavoriteService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Headers('authorization') authHeader: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const favorites = await this.favoriteService.findByUserId(userId);
|
||||
return { favorites };
|
||||
}
|
||||
@Get()
|
||||
async findAll(@Headers('authorization') authHeader: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const favorites = await this.favoriteService.findByUserId(userId);
|
||||
return { favorites };
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Body() dto: CreateFavoriteDto,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
@Post()
|
||||
async create(@Headers('authorization') authHeader: string, @Body() dto: CreateFavoriteDto) {
|
||||
const userId = extractUserId(authHeader);
|
||||
|
||||
// Check if already favorited
|
||||
const exists = await this.favoriteService.exists(userId, dto.quoteId);
|
||||
if (exists) {
|
||||
throw new ConflictException('Quote already in favorites');
|
||||
}
|
||||
// Check if already favorited
|
||||
const exists = await this.favoriteService.exists(userId, dto.quoteId);
|
||||
if (exists) {
|
||||
throw new ConflictException('Quote already in favorites');
|
||||
}
|
||||
|
||||
const favorite = await this.favoriteService.create({
|
||||
userId,
|
||||
quoteId: dto.quoteId,
|
||||
});
|
||||
return { favorite };
|
||||
}
|
||||
const favorite = await this.favoriteService.create({
|
||||
userId,
|
||||
quoteId: dto.quoteId,
|
||||
});
|
||||
return { favorite };
|
||||
}
|
||||
|
||||
@Delete(':quoteId')
|
||||
async delete(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('quoteId') quoteId: string,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
await this.favoriteService.delete(userId, quoteId);
|
||||
return { success: true };
|
||||
}
|
||||
@Delete(':quoteId')
|
||||
async delete(@Headers('authorization') authHeader: string, @Param('quoteId') quoteId: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
await this.favoriteService.delete(userId, quoteId);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { FavoriteController } from './favorite.controller';
|
|||
import { FavoriteService } from './favorite.service';
|
||||
|
||||
@Module({
|
||||
controllers: [FavoriteController],
|
||||
providers: [FavoriteService],
|
||||
exports: [FavoriteService],
|
||||
controllers: [FavoriteController],
|
||||
providers: [FavoriteService],
|
||||
exports: [FavoriteService],
|
||||
})
|
||||
export class FavoriteModule {}
|
||||
|
|
|
|||
|
|
@ -6,28 +6,28 @@ import { favorites, type Favorite, type NewFavorite } from '../db/schema';
|
|||
|
||||
@Injectable()
|
||||
export class FavoriteService {
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
|
||||
async findByUserId(userId: string): Promise<Favorite[]> {
|
||||
return this.db.select().from(favorites).where(eq(favorites.userId, userId));
|
||||
}
|
||||
async findByUserId(userId: string): Promise<Favorite[]> {
|
||||
return this.db.select().from(favorites).where(eq(favorites.userId, userId));
|
||||
}
|
||||
|
||||
async create(data: NewFavorite): Promise<Favorite> {
|
||||
const [favorite] = await this.db.insert(favorites).values(data).returning();
|
||||
return favorite;
|
||||
}
|
||||
async create(data: NewFavorite): Promise<Favorite> {
|
||||
const [favorite] = await this.db.insert(favorites).values(data).returning();
|
||||
return favorite;
|
||||
}
|
||||
|
||||
async delete(userId: string, quoteId: string): Promise<void> {
|
||||
await this.db
|
||||
.delete(favorites)
|
||||
.where(and(eq(favorites.userId, userId), eq(favorites.quoteId, quoteId)));
|
||||
}
|
||||
async delete(userId: string, quoteId: string): Promise<void> {
|
||||
await this.db
|
||||
.delete(favorites)
|
||||
.where(and(eq(favorites.userId, userId), eq(favorites.quoteId, quoteId)));
|
||||
}
|
||||
|
||||
async exists(userId: string, quoteId: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.select()
|
||||
.from(favorites)
|
||||
.where(and(eq(favorites.userId, userId), eq(favorites.quoteId, quoteId)));
|
||||
return result.length > 0;
|
||||
}
|
||||
async exists(userId: string, quoteId: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.select()
|
||||
.from(favorites)
|
||||
.where(and(eq(favorites.userId, userId), eq(favorites.quoteId, quoteId)));
|
||||
return result.length > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { Controller, Get } from '@nestjs/common';
|
|||
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
@Get()
|
||||
check() {
|
||||
return {
|
||||
status: 'ok',
|
||||
service: 'quote-backend',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
@Get()
|
||||
check() {
|
||||
return {
|
||||
status: 'ok',
|
||||
service: 'quote-backend',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ import { Module } from '@nestjs/common';
|
|||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class HealthModule {}
|
||||
|
|
|
|||
|
|
@ -1,141 +1,132 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Headers,
|
||||
UnauthorizedException,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Headers,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ListService } from './list.service';
|
||||
import { IsString, IsNotEmpty, IsOptional, IsArray } from 'class-validator';
|
||||
|
||||
class CreateListDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
|
||||
class UpdateListDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
quoteIds?: string[];
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
quoteIds?: string[];
|
||||
}
|
||||
|
||||
class AddQuoteDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
quoteId!: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
quoteId!: string;
|
||||
}
|
||||
|
||||
// Simple JWT extraction - in production, use proper auth middleware
|
||||
function extractUserId(authHeader?: string): string {
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid authorization header');
|
||||
}
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid authorization header');
|
||||
}
|
||||
|
||||
try {
|
||||
const token = authHeader.substring(7);
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
if (!payload.sub) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return payload.sub;
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
try {
|
||||
const token = authHeader.substring(7);
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
if (!payload.sub) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return payload.sub;
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('lists')
|
||||
export class ListController {
|
||||
constructor(private readonly listService: ListService) {}
|
||||
constructor(private readonly listService: ListService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Headers('authorization') authHeader: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const lists = await this.listService.findByUserId(userId);
|
||||
return { lists };
|
||||
}
|
||||
@Get()
|
||||
async findAll(@Headers('authorization') authHeader: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const lists = await this.listService.findByUserId(userId);
|
||||
return { lists };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.findById(userId, id);
|
||||
return { list };
|
||||
}
|
||||
@Get(':id')
|
||||
async findOne(@Headers('authorization') authHeader: string, @Param('id') id: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.findById(userId, id);
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Body() dto: CreateListDto,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.create({
|
||||
userId,
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
});
|
||||
return { list };
|
||||
}
|
||||
@Post()
|
||||
async create(@Headers('authorization') authHeader: string, @Body() dto: CreateListDto) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.create({
|
||||
userId,
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
});
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateListDto,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.update(userId, id, dto);
|
||||
return { list };
|
||||
}
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateListDto
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.update(userId, id, dto);
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
await this.listService.delete(userId, id);
|
||||
return { success: true };
|
||||
}
|
||||
@Delete(':id')
|
||||
async delete(@Headers('authorization') authHeader: string, @Param('id') id: string) {
|
||||
const userId = extractUserId(authHeader);
|
||||
await this.listService.delete(userId, id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Post(':id/quotes')
|
||||
async addQuote(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: AddQuoteDto,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.addQuoteToList(userId, id, dto.quoteId);
|
||||
return { list };
|
||||
}
|
||||
@Post(':id/quotes')
|
||||
async addQuote(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: AddQuoteDto
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.addQuoteToList(userId, id, dto.quoteId);
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Delete(':id/quotes/:quoteId')
|
||||
async removeQuote(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Param('quoteId') quoteId: string,
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.removeQuoteFromList(userId, id, quoteId);
|
||||
return { list };
|
||||
}
|
||||
@Delete(':id/quotes/:quoteId')
|
||||
async removeQuote(
|
||||
@Headers('authorization') authHeader: string,
|
||||
@Param('id') id: string,
|
||||
@Param('quoteId') quoteId: string
|
||||
) {
|
||||
const userId = extractUserId(authHeader);
|
||||
const list = await this.listService.removeQuoteFromList(userId, id, quoteId);
|
||||
return { list };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { ListController } from './list.controller';
|
|||
import { ListService } from './list.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ListController],
|
||||
providers: [ListService],
|
||||
exports: [ListService],
|
||||
controllers: [ListController],
|
||||
providers: [ListService],
|
||||
exports: [ListService],
|
||||
})
|
||||
export class ListModule {}
|
||||
|
|
|
|||
|
|
@ -6,70 +6,70 @@ import { userLists, type UserList, type NewUserList } from '../db/schema';
|
|||
|
||||
@Injectable()
|
||||
export class ListService {
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
|
||||
async findByUserId(userId: string): Promise<UserList[]> {
|
||||
return this.db.select().from(userLists).where(eq(userLists.userId, userId));
|
||||
}
|
||||
async findByUserId(userId: string): Promise<UserList[]> {
|
||||
return this.db.select().from(userLists).where(eq(userLists.userId, userId));
|
||||
}
|
||||
|
||||
async findById(userId: string, listId: string): Promise<UserList> {
|
||||
const [list] = await this.db
|
||||
.select()
|
||||
.from(userLists)
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)));
|
||||
async findById(userId: string, listId: string): Promise<UserList> {
|
||||
const [list] = await this.db
|
||||
.select()
|
||||
.from(userLists)
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)));
|
||||
|
||||
if (!list) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
return list;
|
||||
}
|
||||
if (!list) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async create(data: NewUserList): Promise<UserList> {
|
||||
const [list] = await this.db.insert(userLists).values(data).returning();
|
||||
return list;
|
||||
}
|
||||
async create(data: NewUserList): Promise<UserList> {
|
||||
const [list] = await this.db.insert(userLists).values(data).returning();
|
||||
return list;
|
||||
}
|
||||
|
||||
async update(
|
||||
userId: string,
|
||||
listId: string,
|
||||
data: Partial<Pick<UserList, 'name' | 'description' | 'quoteIds'>>,
|
||||
): Promise<UserList> {
|
||||
const [list] = await this.db
|
||||
.update(userLists)
|
||||
.set({ ...data, updatedAt: new Date() })
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)))
|
||||
.returning();
|
||||
async update(
|
||||
userId: string,
|
||||
listId: string,
|
||||
data: Partial<Pick<UserList, 'name' | 'description' | 'quoteIds'>>
|
||||
): Promise<UserList> {
|
||||
const [list] = await this.db
|
||||
.update(userLists)
|
||||
.set({ ...data, updatedAt: new Date() })
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)))
|
||||
.returning();
|
||||
|
||||
if (!list) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
return list;
|
||||
}
|
||||
if (!list) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async delete(userId: string, listId: string): Promise<void> {
|
||||
const result = await this.db
|
||||
.delete(userLists)
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)));
|
||||
async delete(userId: string, listId: string): Promise<void> {
|
||||
const result = await this.db
|
||||
.delete(userLists)
|
||||
.where(and(eq(userLists.id, listId), eq(userLists.userId, userId)));
|
||||
|
||||
if (!result) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
throw new NotFoundException('List not found');
|
||||
}
|
||||
}
|
||||
|
||||
async addQuoteToList(userId: string, listId: string, quoteId: string): Promise<UserList> {
|
||||
const list = await this.findById(userId, listId);
|
||||
const quoteIds = list.quoteIds || [];
|
||||
async addQuoteToList(userId: string, listId: string, quoteId: string): Promise<UserList> {
|
||||
const list = await this.findById(userId, listId);
|
||||
const quoteIds = list.quoteIds || [];
|
||||
|
||||
if (!quoteIds.includes(quoteId)) {
|
||||
quoteIds.push(quoteId);
|
||||
}
|
||||
if (!quoteIds.includes(quoteId)) {
|
||||
quoteIds.push(quoteId);
|
||||
}
|
||||
|
||||
return this.update(userId, listId, { quoteIds });
|
||||
}
|
||||
return this.update(userId, listId, { quoteIds });
|
||||
}
|
||||
|
||||
async removeQuoteFromList(userId: string, listId: string, quoteId: string): Promise<UserList> {
|
||||
const list = await this.findById(userId, listId);
|
||||
const quoteIds = (list.quoteIds || []).filter((id) => id !== quoteId);
|
||||
return this.update(userId, listId, { quoteIds });
|
||||
}
|
||||
async removeQuoteFromList(userId: string, listId: string, quoteId: string): Promise<UserList> {
|
||||
const list = await this.findById(userId, listId);
|
||||
const quoteIds = (list.quoteIds || []).filter((id) => id !== quoteId);
|
||||
return this.update(userId, listId, { quoteIds });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,36 +3,36 @@ import { ValidationPipe } from '@nestjs/common';
|
|||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Enable CORS for mobile and web apps
|
||||
app.enableCors({
|
||||
origin: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5177',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001', // Mana Core Auth
|
||||
],
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
// Enable CORS for mobile and web apps
|
||||
app.enableCors({
|
||||
origin: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5177',
|
||||
'http://localhost:8081',
|
||||
'exp://localhost:8081',
|
||||
'http://localhost:3001', // Mana Core Auth
|
||||
],
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
forbidNonWhitelisted: true,
|
||||
}),
|
||||
);
|
||||
// Enable validation
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
forbidNonWhitelisted: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Set global prefix for API routes
|
||||
app.setGlobalPrefix('api');
|
||||
// Set global prefix for API routes
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
const port = process.env.PORT || 3007;
|
||||
await app.listen(port);
|
||||
console.log(`Quote backend running on http://localhost:${port}`);
|
||||
const port = process.env.PORT || 3007;
|
||||
await app.listen(port);
|
||||
console.log(`Quote backend running on http://localhost:${port}`);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./src",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./src",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue