Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb8aef8
feat(be): add study group features with total study time and study in…
Choi-Jung-Hyeon Feb 11, 2026
729f483
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Feb 11, 2026
59dde76
feat(be): add totalStudyTime to UserGroup and create StudyInfo model …
Choi-Jung-Hyeon Feb 11, 2026
182aa29
Merge branch 't2561-studygroup-management' of https://github.com/skku…
Choi-Jung-Hyeon Feb 11, 2026
7e36ca4
feat(be): add capacity and invitationCode fields to CreateGroupInput;…
Choi-Jung-Hyeon Feb 11, 2026
98b9947
feat(be): implement createGroup method in GroupService and update Cre…
Choi-Jung-Hyeon Feb 13, 2026
6575cfe
feat(be): add createGroup mutation and update group input/output models
Choi-Jung-Hyeon Feb 13, 2026
8fd0816
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Feb 13, 2026
d87951d
feat(be): add totalStudyTime to UserGroup and create StudyInfo model …
Choi-Jung-Hyeon Feb 13, 2026
fc9cd3f
Merge branch 't2561-studygroup-management' of https://github.com/skku…
Choi-Jung-Hyeon Feb 13, 2026
f290a18
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Feb 13, 2026
9048d81
feat(be): add totalStudyTime property to UserGroup in mock data and t…
Choi-Jung-Hyeon Feb 13, 2026
cb01f0a
feat(be): add totalStudyTime to user groups in test data
Choi-Jung-Hyeon Feb 13, 2026
99d4cd4
feat(be): group tags, comments, join requests, timers, problem drafts
Choi-Jung-Hyeon Feb 19, 2026
cf2d596
feat(be): add migration for study group management and update schema
Choi-Jung-Hyeon Feb 19, 2026
789c31b
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Feb 19, 2026
cc4064e
feat(be): add invitationCode to user data and groupId to submissions
Choi-Jung-Hyeon Feb 19, 2026
f220e34
feat(be): add invitationCode field to user data retrieval in group se…
Choi-Jung-Hyeon Feb 19, 2026
79f5994
refactor(be): remove debug logging and unused invitationCode field in…
Choi-Jung-Hyeon Feb 19, 2026
ff13a07
feat(be): implement study group management with CreateStudyDto and Up…
Choi-Jung-Hyeon Feb 19, 2026
4873ba3
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Feb 19, 2026
f7a6b73
feat(be): add study group management with StudyModule, StudyControlle…
Choi-Jung-Hyeon Feb 20, 2026
1950433
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Mar 8, 2026
cec6135
fix(be): standardize spacing in User model relations
Choi-Jung-Hyeon Mar 8, 2026
d2c1f52
fix(be): remove unused fields from User model for optimization
Choi-Jung-Hyeon Mar 8, 2026
48537be
Merge branch 'main' into t2561-studygroup-management
Choi-Jung-Hyeon Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
},
"[caddyfile]": {
"editor.defaultFormatter": "matthewpi.caddyfile-support"
}
},
"prisma.pinToPrisma6": true
}
11 changes: 10 additions & 1 deletion apps/backend/apps/admin/src/group/group.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
UpdateCourseNoticeInput
} from './model/course-notice.input'
import { UpdateCourseQnAInput } from './model/course-qna.input'
import { CourseInput } from './model/group.input'
import { CourseInput, CreateGroupInput } from './model/group.input'
import { DuplicateCourse, FindGroup } from './model/group.output'

@Resolver(() => Group)
Expand Down Expand Up @@ -68,6 +68,15 @@ export class GroupResolver {
return await this.groupService.duplicateCourse(groupId, req.user.id)
}

@Mutation(() => Group)
@UseGroupLeaderGuard()
async createGroup(
@Args('input') input: CreateGroupInput,
@Context('req') req: AuthenticatedRequest
) {
return await this.groupService.createGroup(input, req.user.id)
}

@Query(() => [FindGroup])
@UseDisableAdminGuard()
async getCoursesUserLead(@Context('req') req: AuthenticatedRequest) {
Expand Down
28 changes: 25 additions & 3 deletions apps/backend/apps/admin/src/group/group.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const courseInfo = {
email: 'johndoe@example.com',
website: 'https://example.com',
office: 'Room 301',
phoneNum: '010-3333-2222'
phoneNum: '010-3333-2222',
invitationCode: null
}

const group = {
Expand All @@ -66,14 +67,16 @@ const group = {
groupId: 1,
isGroupLeader: true,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
},
{
userId: faker.number.int(),
groupId: 1,
isGroupLeader: false,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
}
],
groupName: 'Programming Basics',
Expand Down Expand Up @@ -635,4 +638,23 @@ describe('WhitelistService', () => {
).to.be.true
})
})

describe('Study Tracking (totalStudyTime)', () => {
it('Default totalStudyTime of User in new study group must be 0.', async () => {
const mockUserGroup = {
userId,
groupId,
isGroupLeader: false,
totalStudyTime: 0
}

db.userGroup.create.resolves(mockUserGroup)

const result = await db.userGroup.create({
data: { userId, groupId }
})

expect(result.totalStudyTime).to.equal(0)
})
})
})
42 changes: 42 additions & 0 deletions apps/backend/apps/admin/src/group/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
} from './model/course-notice.input'
import type { UpdateCourseQnAInput } from './model/course-qna.input'
import type { CourseInput } from './model/group.input'
import { CreateGroupInput } from './model/group.input'

@Injectable()
export class GroupService {
Expand Down Expand Up @@ -101,6 +102,47 @@ export class GroupService {
}
}

async createGroup(input: CreateGroupInput, userId: number) {
const { groupName, description, isPrivate, capacity, invitationCode } =
input

const existingGroup = await this.prisma.group.findFirst({
where: { groupName }
})
if (existingGroup) {
throw new ConflictFoundException('Group name already exists')
}

const group = await this.prisma.group.create({
data: {
groupName,
description: description ?? '',
config: {
type: 'Study',
isPrivate,
showOnList: true,
allowJoinFromSearch: true,
allowJoinWithURL: true,
requireApprovalBeforeJoin: false
},
userGroup: {
create: {
userId,
isGroupLeader: true
}
},
studyInfo: {
create: {
capacity: capacity ?? 10,
invitationCode: isPrivate ? invitationCode : null
}
}
}
})

return group
}

/**
* 강좌(Course) 목록을 조회합니다.
*
Expand Down
23 changes: 19 additions & 4 deletions apps/backend/apps/admin/src/group/model/group.input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Field, Int } from '@nestjs/graphql'
import { InputType } from '@nestjs/graphql'
import { GroupCreateInput, GroupUpdateInput } from '@generated'
import { GroupUpdateInput } from '@generated'

@InputType()
class Config {
Expand All @@ -18,9 +18,24 @@ class Config {
}

@InputType()
export class CreateGroupInput extends GroupCreateInput {
@Field(() => Config, { nullable: false })
declare config: Config
export class CreateGroupInput {
@Field(() => String)
groupName: string

@Field(() => String, { nullable: true })
description?: string

@Field(() => Boolean, { defaultValue: false })
isPrivate: boolean

@Field(() => Int, { nullable: true, description: 'Max people (default: 10)' })
capacity?: number

@Field(() => String, {
nullable: true,
description: 'Private invitation code'
})
invitationCode?: string
}

@InputType()
Expand Down
7 changes: 3 additions & 4 deletions apps/backend/apps/admin/src/group/model/group.output.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Field, Int } from '@nestjs/graphql'
import { ObjectType } from '@nestjs/graphql'
import { Field, Int, ObjectType } from '@nestjs/graphql'
import { Group } from '@generated'

@ObjectType()
Expand All @@ -17,10 +16,10 @@ export class DuplicateCourse {
duplicatedCourse!: Group

@Field(() => [Int], { nullable: false })
originAssignments!: number
originAssignments!: number[]

@Field(() => [Int], { nullable: false })
copiedAssignments!: number
copiedAssignments!: number[]
}

@ObjectType()
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/apps/admin/src/user/user.resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ describe('GroupMemberResolver', () => {
groupId,
isGroupLeader: false,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
}

// Mock the service method
Expand Down
9 changes: 6 additions & 3 deletions apps/backend/apps/admin/src/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,26 @@ const userGroup1: UserGroup = {
groupId: 2,
isGroupLeader: true,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
}

const userGroup2: UserGroup = {
userId: 4,
groupId: 2,
isGroupLeader: true,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
}

const userGroup3: UserGroup = {
userId: 5,
groupId: 2,
isGroupLeader: false,
createTime: faker.date.past(),
updateTime: faker.date.past()
updateTime: faker.date.past(),
totalStudyTime: 0
}

const updateFindResult = [
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/apps/client/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { GroupModule } from './group/group.module'
import { NoticeModule } from './notice/notice.module'
import { NotificationModule } from './notification/notification.module'
import { ProblemModule } from './problem/problem.module'
import { StudyModule } from './study/study.module'
import { SubmissionModule } from './submission/submission.module'
import { UserModule } from './user/user.module'
import { WorkbookModule } from './workbook/workbook.module'
Expand All @@ -43,6 +44,7 @@ import { WorkbookModule } from './workbook/workbook.module'
AuthModule,
ContestModule,
GroupModule,
StudyModule,
NoticeModule,
ProblemModule,
SubmissionModule,
Expand Down
13 changes: 9 additions & 4 deletions apps/backend/apps/client/src/group/group.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ describe('GroupService', () => {
email: 'example01@skku.edu',
website: 'https://seclab.com',
office: null,
phoneNum: null
phoneNum: null,
invitationCode: null
},
isGroupLeader: true,
isJoined: true
Expand Down Expand Up @@ -151,7 +152,8 @@ describe('GroupService', () => {
email: 'example01@skku.edu',
website: 'https://seclab.com',
office: null,
phoneNum: null
phoneNum: null,
invitationCode: null
},
isGroupLeader: true,
isJoined: true
Expand Down Expand Up @@ -221,7 +223,8 @@ describe('GroupService', () => {
email: 'example01@skku.edu',
website: 'https://seclab.com',
office: null,
phoneNum: null
phoneNum: null,
invitationCode: null
},
memberNum: 11,
isGroupLeader: true
Expand Down Expand Up @@ -256,7 +259,8 @@ describe('GroupService', () => {
const userGroupData: UserGroupData = {
userId,
groupId,
isGroupLeader: false
isGroupLeader: false,
totalStudyTime: 0
}

expect(res)
Expand Down Expand Up @@ -317,6 +321,7 @@ describe('GroupService', () => {
userId,
groupId,
isGroupLeader: false,
totalStudyTime: 0,
createTime: undefined,
updateTime: undefined
})
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/apps/client/src/group/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export class GroupService {
email: true,
phoneNum: true,
office: true,
website: true
website: true,
invitationCode: true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface UserGroupData {
userId: number
groupId: number
isGroupLeader: boolean
totalStudyTime?: number
}
12 changes: 8 additions & 4 deletions apps/backend/apps/client/src/group/mock/group.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,28 +114,32 @@ export const userGroups: UserGroup[] = [
userId: 1,
createTime: new Date('2023-02-22T00:00:00.000Z'),
updateTime: new Date('2023-02-22T10:00:00.000Z'),
isGroupLeader: true
isGroupLeader: true,
totalStudyTime: 0
},
{
groupId: 1,
userId: 2,
createTime: new Date('2023-02-22T00:00:00.000Z'),
updateTime: new Date('2023-02-22T10:00:00.000Z'),
isGroupLeader: false
isGroupLeader: false,
totalStudyTime: 0
},
{
groupId: 2,
userId: 1,
createTime: new Date('2023-02-22T00:00:00.000Z'),
updateTime: new Date('2023-02-22T10:00:00.000Z'),
isGroupLeader: true
isGroupLeader: true,
totalStudyTime: 0
},
{
groupId: 2,
userId: 2,
createTime: new Date('2023-02-22T00:00:00.000Z'),
updateTime: new Date('2023-02-22T10:00:00.000Z'),
isGroupLeader: false
isGroupLeader: false,
totalStudyTime: 0
}
]

Expand Down
Loading
Loading