Add baby name cards
This commit is contained in:
@@ -2,14 +2,17 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
|
Param,
|
||||||
Post,
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
Redirect,
|
Redirect,
|
||||||
Render,
|
Render,
|
||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { BabyNamesService } from './baby-names.service';
|
import { BabyNamesService, Cards } from './baby-names.service';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
@Controller('baby-names')
|
@Controller('baby-names')
|
||||||
@@ -31,20 +34,44 @@ export class BabyNamesController {
|
|||||||
if (!key) {
|
if (!key) {
|
||||||
throw new Error("Missing 'key' field");
|
throw new Error("Missing 'key' field");
|
||||||
}
|
}
|
||||||
const currentIndex = await this.babyNamesService.getCurrentNumber(
|
|
||||||
key as string,
|
|
||||||
);
|
|
||||||
const name = this.babyNamesService.nameList[currentIndex];
|
|
||||||
const synonyms = this.babyNamesService.getSynonyms(name);
|
|
||||||
return {
|
return {
|
||||||
key: key,
|
...(await this.babyNamesService.getUserCurrentName(key as string)),
|
||||||
name,
|
|
||||||
index: currentIndex,
|
|
||||||
message: request.query.message || null,
|
message: request.query.message || null,
|
||||||
synonyms: synonyms.join(', '),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('.json')
|
||||||
|
@ApiConsumes('application/json')
|
||||||
|
async submitJson(
|
||||||
|
@Body()
|
||||||
|
body: {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
nameindex: number;
|
||||||
|
opinion: number;
|
||||||
|
pronunciation: number;
|
||||||
|
spelling: number;
|
||||||
|
comment: string;
|
||||||
|
},
|
||||||
|
@Res() res: Response,
|
||||||
|
) {
|
||||||
|
const { key, name, opinion, nameindex, pronunciation, spelling, comment } =
|
||||||
|
body;
|
||||||
|
if (!key) {
|
||||||
|
res.status(400).json({ error: "Missing 'key' field" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.babyNamesService.addUserScore(key, name, {
|
||||||
|
opinion,
|
||||||
|
pronunciation,
|
||||||
|
spelling,
|
||||||
|
comment: comment || '',
|
||||||
|
});
|
||||||
|
await this.babyNamesService.writeUserNumber(key, nameindex + 1);
|
||||||
|
res.cookie('baby-names-key', key, { maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 days
|
||||||
|
res.json({ status: 'ok', nextIndex: nameindex + 1 });
|
||||||
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@Redirect('/baby-names', 302)
|
@Redirect('/baby-names', 302)
|
||||||
@@ -89,4 +116,62 @@ export class BabyNamesController {
|
|||||||
nameCount: this.babyNamesService.nameCountMap,
|
nameCount: this.babyNamesService.nameCountMap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('current-index.json')
|
||||||
|
async getCurrentIndex(@Query('key') key: string, @Res() res: Response) {
|
||||||
|
if (!key) {
|
||||||
|
res.status(400).json({ error: "Missing 'key' field" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentIndex = await this.babyNamesService.getCurrentNumber(key);
|
||||||
|
res.json({ currentIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('data/:index.json')
|
||||||
|
async getNameByIndex(@Req() request: Request, @Res() res: Response) {
|
||||||
|
const indexStr = request.params.index;
|
||||||
|
const index = parseInt(indexStr, 10);
|
||||||
|
if (
|
||||||
|
isNaN(index) ||
|
||||||
|
index < 0 ||
|
||||||
|
index >= this.babyNamesService.nameList.length
|
||||||
|
) {
|
||||||
|
res.status(400).json({ error: 'Invalid index' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const name = this.babyNamesService.nameList[index];
|
||||||
|
const synonyms = this.babyNamesService.getSynonyms(name);
|
||||||
|
res.json({
|
||||||
|
index,
|
||||||
|
name,
|
||||||
|
synonyms,
|
||||||
|
count: this.babyNamesService.nameCountMap[name] || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('cards/:key.json')
|
||||||
|
async getUserCards(@Req() request: Request, @Res() res: Response) {
|
||||||
|
const key = request.params.key;
|
||||||
|
if (!key) {
|
||||||
|
res.status(400).json({ error: "Missing 'key' field" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cards = await this.babyNamesService.getUserCardsOrDefault(key);
|
||||||
|
res.json(cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('cards/:key.json')
|
||||||
|
async updateUserCards(
|
||||||
|
@Req() request: Request,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Body() body: Cards,
|
||||||
|
) {
|
||||||
|
const key = request.params.key;
|
||||||
|
if (!key) {
|
||||||
|
res.status(400).json({ error: "Missing 'key' field" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cards = await this.babyNamesService.saveUserCards(key, body);
|
||||||
|
res.json(cards);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { uniq } from 'ramda';
|
||||||
import { KvService } from 'src/kv/kv.service';
|
import { KvService } from 'src/kv/kv.service';
|
||||||
import { MinioService } from 'src/minio/minio.service';
|
import { MinioService } from 'src/minio/minio.service';
|
||||||
|
|
||||||
@@ -10,6 +11,16 @@ export interface Name {
|
|||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Cards {
|
||||||
|
green: string[];
|
||||||
|
red: string[];
|
||||||
|
wild: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CardsWithHistory extends Cards {
|
||||||
|
removed?: Cards;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NameMap {
|
export interface NameMap {
|
||||||
[key: string]: Name[];
|
[key: string]: Name[];
|
||||||
}
|
}
|
||||||
@@ -68,7 +79,7 @@ export class BabyNamesService {
|
|||||||
const currentKey = await this.minioService
|
const currentKey = await this.minioService
|
||||||
.getBuffer(
|
.getBuffer(
|
||||||
this.minioService.defaultBucketName,
|
this.minioService.defaultBucketName,
|
||||||
`baby-names/${userKey}-current`,
|
`baby-names/${userKey}-current.json`,
|
||||||
)
|
)
|
||||||
.then((buffer) => buffer.toString())
|
.then((buffer) => buffer.toString())
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
@@ -77,22 +88,39 @@ export class BabyNamesService {
|
|||||||
}
|
}
|
||||||
const currentNumber = parseInt(currentKey || '0', 10);
|
const currentNumber = parseInt(currentKey || '0', 10);
|
||||||
|
|
||||||
return currentNumber + 1;
|
return currentNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async writeUserNumber(userKey: string, number: number): Promise<void> {
|
public async writeUserNumber(userKey: string, number: number): Promise<void> {
|
||||||
await this.minioService.uploadBuffer(
|
await this.minioService.uploadBuffer(
|
||||||
this.minioService.defaultBucketName,
|
this.minioService.defaultBucketName,
|
||||||
`baby-names/${userKey}-current`,
|
`baby-names/${userKey}-current.json`,
|
||||||
Buffer.from(number.toString()),
|
Buffer.from(number.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getUserCurrentName(userKey: string): Promise<{
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
index: number;
|
||||||
|
synonyms: string[];
|
||||||
|
}> {
|
||||||
|
const currentIndex = await this.getCurrentNumber(userKey);
|
||||||
|
const name = this.nameList[currentIndex];
|
||||||
|
const synonyms = this.getSynonyms(name);
|
||||||
|
return {
|
||||||
|
key: userKey,
|
||||||
|
name,
|
||||||
|
index: currentIndex,
|
||||||
|
synonyms: synonyms,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getUserScores(userKey: string): Promise<UserScoreMap> {
|
public async getUserScores(userKey: string): Promise<UserScoreMap> {
|
||||||
const scoresKey = await this.minioService
|
const scoresKey = await this.minioService
|
||||||
.getBuffer(
|
.getBuffer(
|
||||||
this.minioService.defaultBucketName,
|
this.minioService.defaultBucketName,
|
||||||
`baby-names/${userKey}-scores`,
|
`baby-names/${userKey}-scores.json`,
|
||||||
)
|
)
|
||||||
.then((buffer) => buffer.toString())
|
.then((buffer) => buffer.toString())
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
@@ -102,14 +130,44 @@ export class BabyNamesService {
|
|||||||
return JSON.parse(scoresKey);
|
return JSON.parse(scoresKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getUserCardsOrDefault(
|
||||||
|
userKey: string,
|
||||||
|
): Promise<CardsWithHistory> {
|
||||||
|
const cardsKey = await this.minioService
|
||||||
|
.getBuffer(
|
||||||
|
this.minioService.defaultBucketName,
|
||||||
|
`baby-names/${userKey}-cards.json`,
|
||||||
|
)
|
||||||
|
.then((buffer) => buffer.toString())
|
||||||
|
.catch(() => null);
|
||||||
|
if (cardsKey === null) {
|
||||||
|
return { green: [], red: [], wild: [] };
|
||||||
|
}
|
||||||
|
return JSON.parse(cardsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async saveUserCards(
|
||||||
|
userKey: string,
|
||||||
|
cards: Cards,
|
||||||
|
): Promise<CardsWithHistory> {
|
||||||
|
const existingCards = await this.getUserCardsOrDefault(userKey);
|
||||||
|
const updatedCards = resolveRemovedCards(existingCards, cards);
|
||||||
|
await this.minioService.uploadBuffer(
|
||||||
|
this.minioService.defaultBucketName,
|
||||||
|
`baby-names/${userKey}-cards.json`,
|
||||||
|
Buffer.from(JSON.stringify(updatedCards, null, 2)),
|
||||||
|
);
|
||||||
|
return updatedCards;
|
||||||
|
}
|
||||||
|
|
||||||
public async saveUserScores(
|
public async saveUserScores(
|
||||||
userKey: string,
|
userKey: string,
|
||||||
scores: UserScoreMap,
|
scores: UserScoreMap,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.minioService.uploadBuffer(
|
await this.minioService.uploadBuffer(
|
||||||
this.minioService.defaultBucketName,
|
this.minioService.defaultBucketName,
|
||||||
`baby-names/${userKey}-scores`,
|
`baby-names/${userKey}-scores.json`,
|
||||||
Buffer.from(JSON.stringify(scores)),
|
Buffer.from(JSON.stringify(scores, null, 2)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,3 +189,26 @@ export class BabyNamesService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveRemovedCards = (
|
||||||
|
existingCards: CardsWithHistory,
|
||||||
|
newCards: Cards,
|
||||||
|
): CardsWithHistory => {
|
||||||
|
return {
|
||||||
|
...newCards,
|
||||||
|
removed: {
|
||||||
|
green: uniq([
|
||||||
|
...(existingCards.removed?.green || []),
|
||||||
|
...existingCards.green.filter((name) => !newCards.green.includes(name)),
|
||||||
|
]),
|
||||||
|
red: uniq([
|
||||||
|
...(existingCards.removed?.red || []),
|
||||||
|
...existingCards.red.filter((name) => !newCards.red.includes(name)),
|
||||||
|
]),
|
||||||
|
wild: uniq([
|
||||||
|
...(existingCards.removed?.wild || []),
|
||||||
|
...existingCards.wild.filter((name) => !newCards.wild.includes(name)),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user