Skip to content

feat(be): implement study group main api#3456

Draft
RyuRaseul wants to merge 33 commits into
mainfrom
t2594-add-studyGroup-main-with-socket
Draft

feat(be): implement study group main api#3456
RyuRaseul wants to merge 33 commits into
mainfrom
t2594-add-studyGroup-main-with-socket

Conversation

@RyuRaseul

Copy link
Copy Markdown
Contributor

Description

Additional context


Before submitting the PR, please make sure you do the following

@RyuRaseul RyuRaseul changed the title feat(be): add RedisModule to AppModule for Redis integration feat(be): implemeng study group main api Feb 25, 2026
@RyuRaseul RyuRaseul changed the title feat(be): implemeng study group main api feat(be): implement study group main api Feb 25, 2026
@RyuRaseul RyuRaseul self-assigned this Feb 25, 2026
@RyuRaseul

Copy link
Copy Markdown
Contributor Author

@nhjbest22
이거 현재까지 진행상황이랑 더 해야할 거 있으면 적어주세요 제가 함 보겠음

@nhjbest22

Copy link
Copy Markdown
Contributor

@RyuRaseul
일단 StudyGroup 간단한 API 까지는 짜둔 상태인데, 단일 StudyGroup 조회했을 경우에는 정보를 어디까지 제공해야 할지 애매해서 단일 StudyGroup 조회 API는 안 짜뒀어요.

@nhjbest22

Copy link
Copy Markdown
Contributor

@RyuRaseul
그리고 저희 서버 상황으로는 일반적인 Socket.IO로는 broadcasting이 안될거 같아서 RedisIoAdapter를 사용해야 하더라구여.
그래서, 일단 redis 세팅 및 RedisIoAdapter 설정까지는 다 마친 상태구요.
WebSocketGateway를 Study API 내부에 구현해서 해당 GateWay 통해서 타이머 및 그룹 내부 채팅 통신을 지원해야 할 거 같아요.

@nhjbest22

Copy link
Copy Markdown
Contributor

추가적으로 주기적으로 Heartbeat 처럼 FE에서 BE 서버로 일정 시간마다 StudyGroup 참가하고 있는 사용자의 누적 참여 시간을 보내줘야 할 거 같아요.
BE 서버에서 누적 시간을 받으면 redis 내부에 특정 사용자가 특정 그룹에서 현재 얼마나 참여했는지를 atomic 하게 저장하고, 저장된 값들을 BullMQ 사용해서 Cron 형식으로 DB에 반영해줘야 할 거 같아요

@nhjbest22

Copy link
Copy Markdown
Contributor

추가적으로 StudyGroup 전체 GET 요청 시 지금은 모든 StudyGroup을 DB에서 조회해서 가져오고 있는데, pagination을 통한 조회나 종료된 StudyGroup은 가져오지 않는 방법으로 수정이 필요할 거 같아요

@RyuRaseul

Copy link
Copy Markdown
Contributor Author

오늘 만나서 얘기 ㄱㄱ

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements backend support for “Study” groups in the client API, including persistence (Prisma models/migration), REST endpoints for managing/joining study groups, and initial websocket/Redis adapter plumbing for realtime features.

Changes:

  • Add GroupTag and StudyInfo Prisma models (and migration) to support tagging and study-specific settings (capacity/invitation).
  • Introduce /study REST API (create/list/update/delete/join) with DTO validation and service logic.
  • Add Redis module + Socket.IO Redis adapter wiring and extend JWT auth guard to support websocket contexts.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks new dependencies for Socket.IO + Redis adapter/ioredis.
apps/backend/package.json Adds websocket/redis-related runtime deps (@nestjs/websockets, @nestjs/platform-socket.io, socket.io, ioredis, @socket.io/redis-adapter).
apps/backend/tsconfig.json Adds @libs/redis path alias.
apps/backend/prisma/schema.prisma Adds GroupTag and StudyInfo models and relations.
apps/backend/prisma/migrations/20260223130454_add_grouptag_and_studyinfo_model/migration.sql Creates group_tag and study_info tables and FKs.
apps/backend/libs/redis/tsconfig.lib.json New lib tsconfig for redis library build output.
apps/backend/libs/redis/src/redis.module.ts Provides a global ioredis client via DI with ping-based connection check.
apps/backend/libs/redis/src/redis-io.adapter.ts Implements Socket.IO Redis adapter integration for Nest websocket adapter.
apps/backend/libs/redis/src/index.ts Exports redis module and adapter.
apps/backend/libs/auth/src/jwt/jwt-auth.guard.ts Extends JWT guard to extract auth/groupId correctly for websocket contexts.
apps/backend/apps/client/src/study/study.service.ts Core study-group CRUD/join logic + tag derivation from shared problems.
apps/backend/apps/client/src/study/study.controller.ts Exposes /study HTTP routes for study-group operations.
apps/backend/apps/client/src/study/study.module.ts Declares Study module (controller/service).
apps/backend/apps/client/src/study/study.gateway.ts Adds a websocket gateway for the study namespace (connection logging).
apps/backend/apps/client/src/study/dto/study.dto.ts Defines create/update DTOs and validation rules.
apps/backend/apps/client/src/main.ts Wires RedisIoAdapter into app bootstrap for websocket scaling.
apps/backend/apps/client/src/app.module.ts Registers StudyModule and RedisModule in the client app.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +27
const tags = await this.prisma.problemTag.findMany({
where: {
problemId: {
in: problemIds
}
},
select: {
tagId: true
},
distinct: ['tagId']
})
Comment on lines +46 to +48
sharedProblems: {
connect: problemIds?.map((problmeId) => ({ id: problmeId }))
},
Comment on lines +206 to +222
async deleteStudyGroup(groupId: number) {
const studyGroup = await this.prisma.group.findUnique({
where: {
id: groupId
},
select: { id: true }
})

if (!studyGroup) {
throw new EntityNotExistException('StudyGroup')
}

await this.prisma.group.delete({
where: {
id: groupId
}
})
Comment on lines +268 to +285
if (
studyGroup.studyInfo &&
studyGroup.studyInfo.capacity <= studyGroup._count.userGroup
)
throw new ConflictFoundException('Study group capacity exceeded')

if (
studyGroup.studyInfo?.invitationCode &&
studyGroup.studyInfo.invitationCode !== invitation
)
throw new ConflictFoundException('Invalid invitation code')

return await this.prisma.userGroup.create({
data: {
userId,
groupId
}
})
Comment on lines +4 to +8
import { StudyService } from './study.service'

@Module({
controllers: [StudyController],
providers: [StudyService],
Comment on lines +14 to +62
async createStudyGroup(userId: number, dto: CreateStudyDto) {
const { groupName, description, capacity, problemIds, invitationCode } = dto

const tags = await this.prisma.problemTag.findMany({
where: {
problemId: {
in: problemIds
}
},
select: {
tagId: true
},
distinct: ['tagId']
})

return await this.prisma.group.create({
data: {
groupName,
groupType: GroupType.Study,
description,
config: {
showOnList: true,
allowJoinFromSearch: true,
allowJoinWithURL: false,
requireApprovalBeforeJoin: false
},
userGroup: {
create: {
userId,
isGroupLeader: true
}
},
sharedProblems: {
connect: problemIds?.map((problmeId) => ({ id: problmeId }))
},
groupTag: {
createMany: {
data: tags.map((tag) => ({ tagId: tag.tagId }))
}
},
studyInfo: {
create: {
capacity,
invitationCode
}
}
}
})
}
Comment on lines +73 to +82
userGroup: {
select: {
user: {
select: {
username: true
}
},
isGroupLeader: true
}
},
UseGroupLeaderGuard
} from '@libs/auth'
import { GroupIDPipe } from '@libs/pipe'
import { CreateStudyDto, type UpdateStudyDto } from './dto/study.dto'
Comment on lines +187 to +189
update: {
capacity,
invitationCode
Comment on lines +268 to +272
if (
studyGroup.studyInfo &&
studyGroup.studyInfo.capacity <= studyGroup._count.userGroup
)
throw new ConflictFoundException('Study group capacity exceeded')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants