feat: initial english learning backend
This commit is contained in:
commit
ad897dc1e1
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.js.map
|
||||
.env
|
||||
5
nest-cli.json
Normal file
5
nest-cli.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
}
|
||||
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "english-back",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"build": "nest build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/typeorm": "^10.0.0",
|
||||
"@nestjs/schedule": "^4.0.0",
|
||||
"typeorm": "^0.3.0",
|
||||
"mysql2": "^3.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
}
|
||||
36
src/app.module.ts
Normal file
36
src/app.module.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { WordsModule } from './words/words.module';
|
||||
import { QuizModule } from './quiz/quiz.module';
|
||||
import { QuestModule } from './quest/quest.module';
|
||||
import { PhrasesModule } from './phrases/phrases.module';
|
||||
import { GrammarModule } from './grammar/grammar.module';
|
||||
import { Word } from './words/entities/word.entity';
|
||||
import { Phrase } from './phrases/entities/phrase.entity';
|
||||
import { GrammarLesson } from './grammar/entities/grammar.entity';
|
||||
import { DailyQuest } from './quest/entities/quest.entity';
|
||||
import { QuizResult } from './quiz/entities/quiz-result.entity';
|
||||
import { Streak } from './quest/entities/streak.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'mysql',
|
||||
host: 'localhost',
|
||||
port: 3306,
|
||||
username: 'kakao',
|
||||
password: '486251daKWON@',
|
||||
database: 'english_study',
|
||||
entities: [Word, Phrase, GrammarLesson, DailyQuest, QuizResult, Streak],
|
||||
synchronize: true,
|
||||
}),
|
||||
ScheduleModule.forRoot(),
|
||||
WordsModule,
|
||||
QuizModule,
|
||||
QuestModule,
|
||||
PhrasesModule,
|
||||
GrammarModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
22
src/grammar/entities/grammar.entity.ts
Normal file
22
src/grammar/entities/grammar.entity.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('grammar_lessons')
|
||||
export class GrammarLesson {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
explanation: string;
|
||||
|
||||
@Column({ type: 'json' })
|
||||
examples: { en: string; ko: string }[];
|
||||
|
||||
@Column({ default: 'beginner' })
|
||||
level: string;
|
||||
|
||||
@Column({ default: 1 })
|
||||
order_num: number;
|
||||
}
|
||||
9
src/grammar/grammar.controller.ts
Normal file
9
src/grammar/grammar.controller.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Controller, Get, Param } from '@nestjs/common';
|
||||
import { GrammarService } from './grammar.service';
|
||||
|
||||
@Controller('grammar')
|
||||
export class GrammarController {
|
||||
constructor(private readonly service: GrammarService) {}
|
||||
@Get() findAll() { return this.service.findAll(); }
|
||||
@Get(':id') findOne(@Param('id') id: string) { return this.service.findOne(+id); }
|
||||
}
|
||||
12
src/grammar/grammar.module.ts
Normal file
12
src/grammar/grammar.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { GrammarLesson } from './entities/grammar.entity';
|
||||
import { GrammarController } from './grammar.controller';
|
||||
import { GrammarService } from './grammar.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([GrammarLesson])],
|
||||
controllers: [GrammarController],
|
||||
providers: [GrammarService],
|
||||
})
|
||||
export class GrammarModule {}
|
||||
11
src/grammar/grammar.service.ts
Normal file
11
src/grammar/grammar.service.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { GrammarLesson } from './entities/grammar.entity';
|
||||
|
||||
@Injectable()
|
||||
export class GrammarService {
|
||||
constructor(@InjectRepository(GrammarLesson) private repo: Repository<GrammarLesson>) {}
|
||||
findAll() { return this.repo.find({ order: { order_num: 'ASC' } }); }
|
||||
findOne(id: number) { return this.repo.findOne({ where: { id } }); }
|
||||
}
|
||||
11
src/main.ts
Normal file
11
src/main.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors({ origin: '*' });
|
||||
app.setGlobalPrefix('api');
|
||||
await app.listen(3011);
|
||||
console.log('English Learning API running on port 3011');
|
||||
}
|
||||
bootstrap();
|
||||
19
src/phrases/entities/phrase.entity.ts
Normal file
19
src/phrases/entities/phrase.entity.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('phrases')
|
||||
export class Phrase {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
english: string;
|
||||
|
||||
@Column()
|
||||
korean: string;
|
||||
|
||||
@Column({ default: 'greeting' })
|
||||
category: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
situation: string;
|
||||
}
|
||||
10
src/phrases/phrases.controller.ts
Normal file
10
src/phrases/phrases.controller.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { PhrasesService } from './phrases.service';
|
||||
|
||||
@Controller('phrases')
|
||||
export class PhrasesController {
|
||||
constructor(private readonly service: PhrasesService) {}
|
||||
@Get() findAll(@Query('category') category?: string) { return this.service.findAll(category); }
|
||||
@Get('categories') getCategories() { return this.service.getCategories(); }
|
||||
@Get('daily') getDaily() { return this.service.getDailyPhrases(); }
|
||||
}
|
||||
13
src/phrases/phrases.module.ts
Normal file
13
src/phrases/phrases.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Phrase } from './entities/phrase.entity';
|
||||
import { PhrasesController } from './phrases.controller';
|
||||
import { PhrasesService } from './phrases.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Phrase])],
|
||||
controllers: [PhrasesController],
|
||||
providers: [PhrasesService],
|
||||
exports: [PhrasesService],
|
||||
})
|
||||
export class PhrasesModule {}
|
||||
20
src/phrases/phrases.service.ts
Normal file
20
src/phrases/phrases.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Phrase } from './entities/phrase.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PhrasesService {
|
||||
constructor(@InjectRepository(Phrase) private repo: Repository<Phrase>) {}
|
||||
findAll(category?: string) {
|
||||
if (category) return this.repo.find({ where: { category } });
|
||||
return this.repo.find();
|
||||
}
|
||||
getCategories() {
|
||||
return this.repo.createQueryBuilder('p').select('DISTINCT p.category', 'category').getRawMany();
|
||||
}
|
||||
async getDailyPhrases(): Promise<Phrase[]> {
|
||||
const all = await this.repo.find();
|
||||
return all.sort(() => Math.random() - 0.5).slice(0, 3);
|
||||
}
|
||||
}
|
||||
25
src/quest/entities/quest.entity.ts
Normal file
25
src/quest/entities/quest.entity.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('daily_quests')
|
||||
export class DailyQuest {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
session_id: string;
|
||||
|
||||
@Column({ type: 'date' })
|
||||
quest_date: string;
|
||||
|
||||
@Column({ default: false })
|
||||
word_done: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
phrase_done: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
quiz_done: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
quiz_score: number;
|
||||
}
|
||||
22
src/quest/entities/streak.entity.ts
Normal file
22
src/quest/entities/streak.entity.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('streaks')
|
||||
export class Streak {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
session_id: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
current_streak: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
max_streak: number;
|
||||
|
||||
@Column({ type: 'date', nullable: true })
|
||||
last_date: string;
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
history: string[];
|
||||
}
|
||||
27
src/quest/quest.controller.ts
Normal file
27
src/quest/quest.controller.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common';
|
||||
import { QuestService } from './quest.service';
|
||||
|
||||
@Controller('quest')
|
||||
export class QuestController {
|
||||
constructor(private readonly service: QuestService) {}
|
||||
|
||||
@Get('today')
|
||||
getToday(@Query('session') session: string) {
|
||||
return this.service.getOrCreateQuest(session || 'default');
|
||||
}
|
||||
|
||||
@Post('complete')
|
||||
complete(@Body() body: { session_id: string; type: 'word' | 'phrase' | 'quiz'; score?: number }) {
|
||||
return this.service.completeQuest(body.session_id, body.type, body.score);
|
||||
}
|
||||
|
||||
@Get('streak')
|
||||
getStreak(@Query('session') session: string) {
|
||||
return this.service.getStreak(session || 'default');
|
||||
}
|
||||
|
||||
@Get('history')
|
||||
getHistory(@Query('session') session: string) {
|
||||
return this.service.getHistory(session || 'default');
|
||||
}
|
||||
}
|
||||
14
src/quest/quest.module.ts
Normal file
14
src/quest/quest.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DailyQuest } from './entities/quest.entity';
|
||||
import { Streak } from './entities/streak.entity';
|
||||
import { QuestController } from './quest.controller';
|
||||
import { QuestService } from './quest.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([DailyQuest, Streak])],
|
||||
controllers: [QuestController],
|
||||
providers: [QuestService],
|
||||
exports: [QuestService],
|
||||
})
|
||||
export class QuestModule {}
|
||||
72
src/quest/quest.service.ts
Normal file
72
src/quest/quest.service.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { DailyQuest } from './entities/quest.entity';
|
||||
import { Streak } from './entities/streak.entity';
|
||||
|
||||
@Injectable()
|
||||
export class QuestService {
|
||||
constructor(
|
||||
@InjectRepository(DailyQuest) private questRepo: Repository<DailyQuest>,
|
||||
@InjectRepository(Streak) private streakRepo: Repository<Streak>,
|
||||
) {}
|
||||
|
||||
async getOrCreateQuest(session_id: string): Promise<DailyQuest> {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
let quest = await this.questRepo.findOne({ where: { session_id, quest_date: today } });
|
||||
if (!quest) {
|
||||
quest = this.questRepo.create({ session_id, quest_date: today });
|
||||
await this.questRepo.save(quest);
|
||||
}
|
||||
return quest;
|
||||
}
|
||||
|
||||
async completeQuest(session_id: string, type: 'word' | 'phrase' | 'quiz', score?: number): Promise<DailyQuest> {
|
||||
const quest = await this.getOrCreateQuest(session_id);
|
||||
if (type === 'word') quest.word_done = true;
|
||||
if (type === 'phrase') quest.phrase_done = true;
|
||||
if (type === 'quiz') { quest.quiz_done = true; if (score !== undefined) quest.quiz_score = score; }
|
||||
await this.questRepo.save(quest);
|
||||
|
||||
if (quest.word_done && quest.phrase_done && quest.quiz_done) {
|
||||
await this.updateStreak(session_id);
|
||||
}
|
||||
return quest;
|
||||
}
|
||||
|
||||
async getStreak(session_id: string): Promise<Streak> {
|
||||
let streak = await this.streakRepo.findOne({ where: { session_id } });
|
||||
if (!streak) {
|
||||
streak = this.streakRepo.create({ session_id, history: [] });
|
||||
await this.streakRepo.save(streak);
|
||||
}
|
||||
return streak;
|
||||
}
|
||||
|
||||
async updateStreak(session_id: string): Promise<Streak> {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
let streak = await this.getStreak(session_id);
|
||||
if (streak.last_date === today) return streak;
|
||||
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const yest = yesterday.toISOString().split('T')[0];
|
||||
|
||||
if (streak.last_date === yest) {
|
||||
streak.current_streak += 1;
|
||||
} else {
|
||||
streak.current_streak = 1;
|
||||
}
|
||||
streak.max_streak = Math.max(streak.max_streak, streak.current_streak);
|
||||
streak.last_date = today;
|
||||
if (!streak.history) streak.history = [];
|
||||
if (!streak.history.includes(today)) streak.history.push(today);
|
||||
return this.streakRepo.save(streak);
|
||||
}
|
||||
|
||||
async getHistory(session_id: string) {
|
||||
const quests = await this.questRepo.find({ where: { session_id } });
|
||||
const streak = await this.getStreak(session_id);
|
||||
return { quests, streak };
|
||||
}
|
||||
}
|
||||
19
src/quiz/entities/quiz-result.entity.ts
Normal file
19
src/quiz/entities/quiz-result.entity.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('quiz_results')
|
||||
export class QuizResult {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
session_id: string;
|
||||
|
||||
@Column()
|
||||
score: number;
|
||||
|
||||
@Column()
|
||||
total: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
}
|
||||
11
src/quiz/quiz.controller.ts
Normal file
11
src/quiz/quiz.controller.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Controller, Get, Post, Body, Query } from '@nestjs/common';
|
||||
import { QuizService } from './quiz.service';
|
||||
|
||||
@Controller('quiz')
|
||||
export class QuizController {
|
||||
constructor(private readonly service: QuizService) {}
|
||||
@Get('generate') generate(@Query('count') count?: string) { return this.service.generateQuiz(count ? +count : 5); }
|
||||
@Post('result') saveResult(@Body() body: { session_id: string; score: number; total: number }) {
|
||||
return this.service.saveResult(body.session_id, body.score, body.total);
|
||||
}
|
||||
}
|
||||
14
src/quiz/quiz.module.ts
Normal file
14
src/quiz/quiz.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { QuizResult } from './entities/quiz-result.entity';
|
||||
import { QuizController } from './quiz.controller';
|
||||
import { QuizService } from './quiz.service';
|
||||
import { WordsModule } from '../words/words.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([QuizResult]), WordsModule],
|
||||
controllers: [QuizController],
|
||||
providers: [QuizService],
|
||||
exports: [QuizService],
|
||||
})
|
||||
export class QuizModule {}
|
||||
35
src/quiz/quiz.service.ts
Normal file
35
src/quiz/quiz.service.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { QuizResult } from './entities/quiz-result.entity';
|
||||
import { WordsService } from '../words/words.service';
|
||||
|
||||
@Injectable()
|
||||
export class QuizService {
|
||||
constructor(
|
||||
@InjectRepository(QuizResult) private repo: Repository<QuizResult>,
|
||||
private wordsService: WordsService,
|
||||
) {}
|
||||
|
||||
async generateQuiz(count = 5) {
|
||||
const words = await this.wordsService.getQuizWords(count + 12);
|
||||
const selected = words.slice(0, count);
|
||||
const pool = words.slice(count);
|
||||
|
||||
return selected.map((word) => {
|
||||
const wrong = pool.sort(() => Math.random() - 0.5).slice(0, 3);
|
||||
const choices = [word.korean, ...wrong.map((w) => w.korean)].sort(() => Math.random() - 0.5);
|
||||
return {
|
||||
id: word.id,
|
||||
english: word.english,
|
||||
pronunciation: word.pronunciation,
|
||||
answer: word.korean,
|
||||
choices,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
saveResult(session_id: string, score: number, total: number) {
|
||||
return this.repo.save({ session_id, score, total });
|
||||
}
|
||||
}
|
||||
34
src/words/entities/word.entity.ts
Normal file
34
src/words/entities/word.entity.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('words')
|
||||
export class Word {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
english: string;
|
||||
|
||||
@Column()
|
||||
korean: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
pronunciation: string;
|
||||
|
||||
@Column({ default: 'general' })
|
||||
category: string;
|
||||
|
||||
@Column({ default: 'beginner' })
|
||||
difficulty: string;
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
example_en: string;
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
example_ko: string;
|
||||
|
||||
@Column({ default: false })
|
||||
is_daily: boolean;
|
||||
|
||||
@Column({ type: 'date', nullable: true })
|
||||
daily_date: string;
|
||||
}
|
||||
27
src/words/words.controller.ts
Normal file
27
src/words/words.controller.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { WordsService } from './words.service';
|
||||
|
||||
@Controller('words')
|
||||
export class WordsController {
|
||||
constructor(private readonly service: WordsService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Query('category') category?: string) {
|
||||
return this.service.findAll(category);
|
||||
}
|
||||
|
||||
@Get('daily')
|
||||
getDaily() {
|
||||
return this.service.getDailyWords();
|
||||
}
|
||||
|
||||
@Get('quiz')
|
||||
getQuizWords(@Query('count') count?: string) {
|
||||
return this.service.getQuizWords(count ? +count : 10);
|
||||
}
|
||||
|
||||
@Get('categories')
|
||||
getCategories() {
|
||||
return this.service.getCategories();
|
||||
}
|
||||
}
|
||||
13
src/words/words.module.ts
Normal file
13
src/words/words.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Word } from './entities/word.entity';
|
||||
import { WordsController } from './words.controller';
|
||||
import { WordsService } from './words.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Word])],
|
||||
controllers: [WordsController],
|
||||
providers: [WordsService],
|
||||
exports: [WordsService],
|
||||
})
|
||||
export class WordsModule {}
|
||||
44
src/words/words.service.ts
Normal file
44
src/words/words.service.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Word } from './entities/word.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WordsService {
|
||||
constructor(@InjectRepository(Word) private repo: Repository<Word>) {}
|
||||
|
||||
findAll(category?: string) {
|
||||
if (category) return this.repo.find({ where: { category } });
|
||||
return this.repo.find();
|
||||
}
|
||||
|
||||
findByCategory(category: string) {
|
||||
return this.repo.find({ where: { category } });
|
||||
}
|
||||
|
||||
async getDailyWords(): Promise<Word[]> {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const existing = await this.repo.find({ where: { daily_date: today, is_daily: true } });
|
||||
if (existing.length >= 5) return existing;
|
||||
|
||||
const all = await this.repo.find();
|
||||
const shuffled = all.sort(() => Math.random() - 0.5).slice(0, 5);
|
||||
for (const w of shuffled) {
|
||||
w.is_daily = true;
|
||||
w.daily_date = today;
|
||||
}
|
||||
return this.repo.save(shuffled);
|
||||
}
|
||||
|
||||
async getQuizWords(count = 10): Promise<Word[]> {
|
||||
const all = await this.repo.find();
|
||||
return all.sort(() => Math.random() - 0.5).slice(0, count);
|
||||
}
|
||||
|
||||
getCategories() {
|
||||
return this.repo
|
||||
.createQueryBuilder('w')
|
||||
.select('DISTINCT w.category', 'category')
|
||||
.getRawMany();
|
||||
}
|
||||
}
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user