feat: 과목 제한 해제 — 수학 외 자유 과목 추가 가능

- backend: ALLOWED_SUBJECT_NAMES, @IsIn() 삭제, 모든 서비스에서 name:'수학' 필터 제거
- frontend: purgeNonMath 제거 (대시보드 안정화), 과목 생성 가드 삭제
- defaultSubjectColor에 영어/국어/과학/사회/한국사 색상 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
reloop
2026-04-19 16:08:48 +09:00
parent aa270be659
commit f5214d20b2
10 changed files with 14 additions and 44 deletions

View File

@@ -28,7 +28,6 @@ export class DashboardService {
scheduledAt: { lte: now },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
}),
@@ -39,7 +38,6 @@ export class DashboardService {
scheduledAt: { gt: now, lte: todayEnd },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
}),
@@ -47,7 +45,6 @@ export class DashboardService {
where: {
userId,
psProblemId: null,
subject: { name: '수학' },
},
orderBy: { studiedAt: 'desc' },
take: 5,
@@ -61,7 +58,6 @@ export class DashboardService {
where: {
userId,
psProblemId: null,
subject: { name: '수학' },
studiedAt: { gte: weekStart },
},
_count: { result: true },
@@ -70,7 +66,6 @@ export class DashboardService {
where: {
userId,
tagId: { not: null },
tag: { subject: { name: '수학' } },
},
include: {
tag: {

View File

@@ -16,7 +16,6 @@ import {
IsArray,
IsBoolean,
IsEnum,
IsIn,
IsInt,
IsOptional,
IsString,
@@ -34,7 +33,6 @@ import { AuthUser } from '../auth/jwt.strategy';
import { MeService } from './me.service';
const ALLOWED_AVATAR_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;
const ALLOWED_FOCUS_SUBJECTS = ['수학'] as const;
const AVATAR_UPLOAD_DIR = join(__dirname, '..', '..', 'uploads', 'avatars');
function ensureAvatarUploadDir() {
@@ -69,8 +67,8 @@ class OnboardingDto {
@IsOptional()
@IsArray()
@ArrayMaxSize(1)
@IsIn(ALLOWED_FOCUS_SUBJECTS, { each: true })
@ArrayMaxSize(5)
@IsString({ each: true })
focusSubjects?: string[];
@IsEnum(ReviewIntensity)
@@ -121,8 +119,8 @@ class ProfilePatchDto {
@IsOptional()
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(1)
@IsIn(ALLOWED_FOCUS_SUBJECTS, { each: true })
@ArrayMaxSize(5)
@IsString({ each: true })
focusSubjects?: string[];
@IsOptional()

View File

@@ -37,7 +37,6 @@ export class ReviewsService {
scheduledAt: { lte: soon },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
include: {
@@ -81,7 +80,6 @@ export class ReviewsService {
scheduledAt: { lt: utcTodayStartKST },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
select: { id: true },
@@ -302,7 +300,6 @@ export class ReviewsService {
status: { in: [ReviewStatus.done, ReviewStatus.skipped] },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
include: {
@@ -332,7 +329,6 @@ export class ReviewsService {
scheduledAt: { gte: start, lt: end },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
select: {
@@ -379,7 +375,6 @@ export class ReviewsService {
scheduledAt: { gte: start, lt: end },
studyLog: {
psProblemId: null,
subject: { name: '수학' },
},
},
include: {

View File

@@ -193,9 +193,8 @@ export class StatsService {
}
async bySubject(userId: number) {
// 프로토타입: 수학 과목만 노출 (soft filter — DB 데이터는 유지)
const subjects = await this.prisma.subject.findMany({
where: { userId, name: '수학' },
where: { userId },
include: {
tags: {
include: {

View File

@@ -508,7 +508,6 @@ export class StudyLogsService {
where: {
userId,
psProblemId: null,
subject: { name: '수학' },
...(opts.subjectId && { subjectId: opts.subjectId }),
...(opts.tagId && { tagId: opts.tagId }),
},
@@ -803,6 +802,11 @@ function clamp01(n: number): number {
function defaultSubjectColor(subjectName: string): string {
const colorMap: Record<string, string> = {
: "#3b82f6",
: "#10b981",
: "#f59e0b",
: "#8b5cf6",
: "#ec4899",
: "#ef4444",
};
return colorMap[subjectName] ?? "#6366f1";

View File

@@ -9,20 +9,16 @@ import {
Post,
UseGuards,
} from '@nestjs/common';
import { IsIn, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CurrentUser } from '../auth/current-user.decorator';
import { AuthUser } from '../auth/jwt.strategy';
import { SubjectsService } from './subjects.service';
// 프로토타입: 수학만 허용 — 나중에 sed 한 방으로 해제 가능
const ALLOWED_SUBJECT_NAMES = ['수학'] as const;
class CreateSubjectDto {
@IsString()
@MinLength(1)
@MaxLength(40)
@IsIn(ALLOWED_SUBJECT_NAMES)
name: string;
@IsOptional()
@@ -35,7 +31,6 @@ class UpdateSubjectDto {
@IsString()
@MinLength(1)
@MaxLength(40)
@IsIn(ALLOWED_SUBJECT_NAMES)
name?: string;
@IsOptional()

View File

@@ -6,18 +6,16 @@ export class SubjectsService {
constructor(private readonly prisma: PrismaService) {}
list(userId: number) {
// 프로토타입: 수학 과목만 노출 (soft filter — DB 데이터는 유지)
return this.prisma.subject.findMany({
where: { userId, name: '수학' },
where: { userId },
include: { tags: { orderBy: { name: 'asc' } } },
orderBy: { createdAt: 'asc' },
});
}
async getOne(userId: number, id: number) {
// 프로토타입: 수학 과목만 노출 (soft filter — DB 데이터는 유지)
const subject = await this.prisma.subject.findFirst({
where: { id, userId, name: '수학' },
where: { id, userId },
include: { tags: { orderBy: { name: 'asc' } } },
});
if (!subject) throw new NotFoundException();

View File

@@ -95,14 +95,6 @@ function DashboardBody() {
setCoachDismissed(window.localStorage.getItem(COACH_DISMISS_KEY) === '1');
}, []);
useEffect(() => {
const purged = localStorage.getItem('reloop-purged-non-math');
if (!purged) {
api.post('/study-logs/purge-non-math').then(() => {
localStorage.setItem('reloop-purged-non-math', 'true');
}).catch(() => {});
}
}, []);
useEffect(() => {
let cancelled = false;

View File

@@ -182,7 +182,7 @@ function OnboardingBody() {
nickname: trimmedNickname,
persona,
reviewIntensity: DEFAULT_REVIEW_INTENSITY,
focusSubjects: goalType === 'none' ? [] : ['수학'],
focusSubjects: goalType === 'math-suneung' ? ['수학'] : [],
focusUnits: goalType === 'math-suneung' ? focusUnits : [],
onboarded: true,
};

View File

@@ -180,12 +180,6 @@ function SubjectsBody() {
setSubjectModalError('과목 이름을 입력해 주세요.');
return;
}
// 프로토타입: 수학 단일 과목 잠금 — 추후 sed 한 방으로 해제
if (name !== '수학') {
setSubjectModalError('현재는 수학 과목만 추가할 수 있어요.');
return;
}
setSubjectModalSaving(true);
setSubjectModalError(null);