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:
DylanBanta 2025-05-25 21:49:34 -04:00
parent 08caa4cefc
commit 546f321ff0
5 changed files with 113 additions and 4 deletions

View File

@ -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
View 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"]

View File

@ -34,7 +34,8 @@
"nanoid": "^3.1.25",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
"rxjs": "^7.2.0",
"redis": "^4.6.5"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",

View File

@ -1,13 +1,19 @@
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { RawParserMiddleware } from './raw-parser.middleware';
import { ScenesController } from './scenes/scenes.controller';
import { LoadController } from './scenes/load.controller';
import { StorageService } from './storage/storage.service';
import { RoomsController } from './rooms/rooms.controller';
import { FilesController } from './files/files.controller';
@Module({
imports: [],
controllers: [ScenesController, RoomsController, FilesController],
controllers: [
ScenesController,
LoadController,
RoomsController,
FilesController,
],
providers: [StorageService],
})
export class AppModule {

View 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');
}
}
}