feat: add GET /api/v2/scenes endpoint
- load.controller.ts exposing listScenes() - adjusted AppModule to register LoadController - multi-stage Dockerfile now copies compiled dist/ - synced package-lock.json and installed redis types - fixed iterator usage in storage.service
This commit is contained in:
parent
08caa4cefc
commit
546f321ff0
@ -1,3 +1,18 @@
|
|||||||
|
# ignore everything by default
|
||||||
*
|
*
|
||||||
!package*.json
|
|
||||||
!dist
|
# keep package manifests so we can install deps
|
||||||
|
!package.json
|
||||||
|
!package-lock.json
|
||||||
|
|
||||||
|
# keep your Nest build config
|
||||||
|
!nest-cli.json
|
||||||
|
!tsconfig.json
|
||||||
|
!tsconfig.build.json
|
||||||
|
|
||||||
|
# keep source code
|
||||||
|
!src/**
|
||||||
|
|
||||||
|
# ignore node_modules and any pre-existing dist output
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
27
Dockerfile.override
Normal file
27
Dockerfile.override
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# --- Builder stage ----------------------------------------------------------
|
||||||
|
FROM node:16-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dev+prod dependencies and build TS
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Runtime stage ----------------------------------------------------------
|
||||||
|
FROM node:16-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Bring in package manifests so npm can locate the start script
|
||||||
|
COPY --from=builder /app/package*.json ./
|
||||||
|
|
||||||
|
# Bring in only production deps and compiled output
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
||||||
|
# Drop to non-root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["npm", "run", "start:prod"]
|
@ -34,7 +34,8 @@
|
|||||||
"nanoid": "^3.1.25",
|
"nanoid": "^3.1.25",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0"
|
"rxjs": "^7.2.0",
|
||||||
|
"redis": "^4.6.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^8.0.0",
|
"@nestjs/cli": "^8.0.0",
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
||||||
import { RawParserMiddleware } from './raw-parser.middleware';
|
import { RawParserMiddleware } from './raw-parser.middleware';
|
||||||
import { ScenesController } from './scenes/scenes.controller';
|
import { ScenesController } from './scenes/scenes.controller';
|
||||||
|
import { LoadController } from './scenes/load.controller';
|
||||||
import { StorageService } from './storage/storage.service';
|
import { StorageService } from './storage/storage.service';
|
||||||
import { RoomsController } from './rooms/rooms.controller';
|
import { RoomsController } from './rooms/rooms.controller';
|
||||||
import { FilesController } from './files/files.controller';
|
import { FilesController } from './files/files.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
controllers: [ScenesController, RoomsController, FilesController],
|
controllers: [
|
||||||
|
ScenesController,
|
||||||
|
LoadController,
|
||||||
|
RoomsController,
|
||||||
|
FilesController,
|
||||||
|
],
|
||||||
providers: [StorageService],
|
providers: [StorageService],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
60
src/scenes/load.controller.ts
Normal file
60
src/scenes/load.controller.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Controller, Get, Logger, InternalServerErrorException } from '@nestjs/common';
|
||||||
|
import { createClient } from 'redis';
|
||||||
|
import { StorageNamespace } from 'src/storage/storage.service';
|
||||||
|
|
||||||
|
@Controller('scenes')
|
||||||
|
export class LoadController {
|
||||||
|
private readonly logger = new Logger(LoadController.name);
|
||||||
|
|
||||||
|
// Initialize Redis client once
|
||||||
|
private client = createClient({ url: process.env.STORAGE_URI });
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client.on('error', (err) => {
|
||||||
|
this.logger.error('Redis client error', err);
|
||||||
|
});
|
||||||
|
this.client.connect().catch((err) => {
|
||||||
|
this.logger.error('Failed to connect Redis client', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async listScenes() {
|
||||||
|
try {
|
||||||
|
const prefix = `${StorageNamespace.SCENES}:`;
|
||||||
|
|
||||||
|
// 1) list all keys
|
||||||
|
const keys = await this.client.keys(`${prefix}*`);
|
||||||
|
this.logger.debug(`Found ${keys.length} scene keys`);
|
||||||
|
|
||||||
|
// 2) pipeline a GET for each key
|
||||||
|
const pipeline = this.client.multi();
|
||||||
|
for (const k of keys) {
|
||||||
|
pipeline.get(k);
|
||||||
|
}
|
||||||
|
// pipeline.exec() on redis v4 returns an array of reply values
|
||||||
|
const raws = (await pipeline.exec()) as (string | null)[];
|
||||||
|
|
||||||
|
// 3) build summaries
|
||||||
|
const scenes = keys
|
||||||
|
.map((fullKey, idx) => {
|
||||||
|
const raw = raws[idx];
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(raw);
|
||||||
|
const id = fullKey.slice(prefix.length);
|
||||||
|
return { id, created: payload.created, updated: payload.updated };
|
||||||
|
} catch {
|
||||||
|
this.logger.warn(`Skipping invalid JSON at ${fullKey}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((s) => s !== null);
|
||||||
|
|
||||||
|
return scenes;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Error listing scenes', err as any);
|
||||||
|
throw new InternalServerErrorException('Failed to list scenes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user