Add initial baby-names service
This commit is contained in:
@@ -33,6 +33,7 @@ import { HoardingModule } from './hoarding/hoarding.module';
|
||||
import { NamesModule } from './names/names.module';
|
||||
import { AssemblyAiModule } from './assembly-ai/assembly-ai.module';
|
||||
import { ClaudeModule } from './claude/claude.module';
|
||||
import { BabyNamesModule } from './baby-names/baby-names.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -112,6 +113,7 @@ import { ClaudeModule } from './claude/claude.module';
|
||||
NamesModule,
|
||||
AssemblyAiModule,
|
||||
ClaudeModule,
|
||||
BabyNamesModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
|
80
src/baby-names/baby-names.controller.ts
Normal file
80
src/baby-names/baby-names.controller.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Redirect,
|
||||
Render,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { BabyNamesService } from './baby-names.service';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
@Controller('baby-names')
|
||||
@ApiTags('baby-names')
|
||||
export class BabyNamesController {
|
||||
constructor(private readonly babyNamesService: BabyNamesService) {}
|
||||
|
||||
@Get()
|
||||
@Render('baby-names/index')
|
||||
async index(@Req() request: Request) {
|
||||
const key = request.cookies['baby-names-key'] || null;
|
||||
return { previousKey: key };
|
||||
}
|
||||
|
||||
@Get('form')
|
||||
@Render('baby-names/form')
|
||||
async form(@Req() request: Request) {
|
||||
const { key } = request.query;
|
||||
if (!key) {
|
||||
throw new Error("Missing 'key' field");
|
||||
}
|
||||
const currentIndex = await this.babyNamesService.getCurrentNumber(
|
||||
key as string,
|
||||
);
|
||||
return {
|
||||
key: key,
|
||||
name: this.babyNamesService.nameList[currentIndex],
|
||||
index: currentIndex,
|
||||
message: request.query.message || null,
|
||||
};
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@Redirect('/baby-names', 302)
|
||||
async submit(
|
||||
@Body()
|
||||
body: {
|
||||
key: string;
|
||||
name: string;
|
||||
nameindex: string;
|
||||
opinion: string;
|
||||
},
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const { key, name, opinion, nameindex } = body;
|
||||
if (!key) {
|
||||
throw new Error("Missing 'key' field");
|
||||
}
|
||||
await this.babyNamesService.addUserScore(key, name, parseInt(opinion, 10));
|
||||
await this.babyNamesService.writeUserNumber(
|
||||
key,
|
||||
parseInt(nameindex, 10) + 1,
|
||||
);
|
||||
res.cookie('baby-names-key', key, { maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 days
|
||||
return {
|
||||
url: `/baby-names/form?key=${key}&message=Logged ${name} as ${opinion}`,
|
||||
};
|
||||
}
|
||||
|
||||
@Get('data/names.json')
|
||||
async getNamesData() {
|
||||
return {
|
||||
names: this.babyNamesService.nameList,
|
||||
nameCount: this.babyNamesService.nameCountMap,
|
||||
};
|
||||
}
|
||||
}
|
13
src/baby-names/baby-names.module.ts
Normal file
13
src/baby-names/baby-names.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BabyNamesController } from './baby-names.controller';
|
||||
import { KvService } from 'src/kv/kv.service';
|
||||
import { MinioService } from 'src/minio/minio.service';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { BabyNamesService } from './baby-names.service';
|
||||
|
||||
@Module({
|
||||
providers: [KvService, MinioService, BabyNamesService],
|
||||
imports: [],
|
||||
controllers: [BabyNamesController],
|
||||
})
|
||||
export class BabyNamesModule {}
|
100
src/baby-names/baby-names.service.ts
Normal file
100
src/baby-names/baby-names.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
import { KvService } from 'src/kv/kv.service';
|
||||
import { MinioService } from 'src/minio/minio.service';
|
||||
|
||||
export interface Name {
|
||||
name: string;
|
||||
year: number;
|
||||
gender: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface NameMap {
|
||||
[key: string]: Name[];
|
||||
}
|
||||
|
||||
export type NameList = string[];
|
||||
|
||||
export interface NameCountMap {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BabyNamesService {
|
||||
public nameList: NameList = [];
|
||||
public nameCountMap: NameCountMap = {};
|
||||
private readonly kvNamespace = 'baby-names';
|
||||
|
||||
constructor(private readonly minioService: MinioService) {
|
||||
this.refreshNames();
|
||||
}
|
||||
|
||||
private async refreshNames() {
|
||||
this.nameCountMap = (
|
||||
await axios.get('https://cache.sh/baby-name-data/namecount.json')
|
||||
).data;
|
||||
this.nameList = (
|
||||
await axios.get('https://cache.sh/baby-name-data/namecount-list.json')
|
||||
).data;
|
||||
}
|
||||
|
||||
public async getCurrentNumber(userKey: string): Promise<number> {
|
||||
const currentKey = await this.minioService
|
||||
.getBuffer(
|
||||
this.minioService.defaultBucketName,
|
||||
`baby-names/${userKey}-current`,
|
||||
)
|
||||
.then((buffer) => buffer.toString())
|
||||
.catch(() => null);
|
||||
if (currentKey === null) {
|
||||
await this.writeUserNumber(userKey, 0);
|
||||
}
|
||||
const currentNumber = parseInt(currentKey || '0', 10);
|
||||
|
||||
return currentNumber + 1;
|
||||
}
|
||||
|
||||
public async writeUserNumber(userKey: string, number: number): Promise<void> {
|
||||
await this.minioService.uploadBuffer(
|
||||
this.minioService.defaultBucketName,
|
||||
`baby-names/${userKey}-current`,
|
||||
Buffer.from(number.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
public async getUserScores(userKey: string): Promise<NameCountMap> {
|
||||
const scoresKey = await this.minioService
|
||||
.getBuffer(
|
||||
this.minioService.defaultBucketName,
|
||||
`baby-names/${userKey}-scores`,
|
||||
)
|
||||
.then((buffer) => buffer.toString())
|
||||
.catch(() => null);
|
||||
if (scoresKey === null) {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(scoresKey);
|
||||
}
|
||||
|
||||
public async saveUserScores(
|
||||
userKey: string,
|
||||
scores: NameCountMap,
|
||||
): Promise<void> {
|
||||
await this.minioService.uploadBuffer(
|
||||
this.minioService.defaultBucketName,
|
||||
`baby-names/${userKey}-scores`,
|
||||
Buffer.from(JSON.stringify(scores)),
|
||||
);
|
||||
}
|
||||
|
||||
public async addUserScore(
|
||||
userKey: string,
|
||||
name: string,
|
||||
score: number,
|
||||
): Promise<void> {
|
||||
const scores = await this.getUserScores(userKey);
|
||||
scores[name] = score;
|
||||
await this.saveUserScores(userKey, scores);
|
||||
}
|
||||
}
|
@@ -6,11 +6,7 @@ import { BullModule } from '@nestjs/bull';
|
||||
|
||||
@Module({
|
||||
providers: [KvService, MinioService],
|
||||
imports: [
|
||||
BullModule.registerQueue({
|
||||
name: 'kv',
|
||||
}),
|
||||
],
|
||||
imports: [],
|
||||
controllers: [KvController],
|
||||
})
|
||||
export class KvModule {}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Queue } from 'bull';
|
||||
import { UploadedObjectInfo } from 'minio/dist/main/internal/type';
|
||||
import { MinioService } from 'src/minio/minio.service';
|
||||
|
||||
@@ -12,10 +10,7 @@ export class KvService {
|
||||
private readonly kvMetadataPath = `${this.kvPrefix}/${this.kvMetadataFileName}`;
|
||||
private readonly logger: Logger = new Logger(KvService.name);
|
||||
|
||||
constructor(
|
||||
private readonly minioService: MinioService,
|
||||
@InjectQueue('kv') private kvProcessingQueue: Queue,
|
||||
) {}
|
||||
constructor(private readonly minioService: MinioService) {}
|
||||
|
||||
public generateFilePath(namespace: string, key: string): string {
|
||||
return `${this.kvPrefix}/${namespace}/${key}`;
|
||||
@@ -123,7 +118,6 @@ export class KvService {
|
||||
};
|
||||
await this.setMetadataFile(Buffer.from(JSON.stringify(metadata)));
|
||||
this.logger.verbose(`Claimed namespace ${namespace}`);
|
||||
this.kvProcessingQueue.add('namespaceModeration', metadata[namespace]);
|
||||
return metadata[namespace];
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { AppModule } from './app.module';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'path';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
@@ -20,6 +21,7 @@ async function bootstrap() {
|
||||
app.enableCors({ origin: '*' });
|
||||
app.useBodyParser('json', { limit: '50mb' });
|
||||
app.useBodyParser('text', { limit: '50mb' });
|
||||
app.use(cookieParser());
|
||||
app.disable('x-powered-by');
|
||||
await app.listen(3000);
|
||||
}
|
||||
|
Reference in New Issue
Block a user