Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions mobile/services/student-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,35 @@ export interface Student {
// Add other fields as needed
}

export type StudentGender = 'MALE' | 'FEMALE' | 'OTHER';
export type StudentRole = 'STUDENT';

export interface CreateStudentPayload {
name: string;
email: string;
password: string;
gender: StudentGender;
academicYearId: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and map their structure.
git ls-files 'mobile/services/student-service.ts' 'server/services/*student*' 'server/controllers/*student*' 'server/**/students*' 'mobile/**/api*' | sed -n '1,200p'

echo
echo '--- student-service.ts outline ---'
ast-grep outline mobile/services/student-service.ts --view expanded

echo
echo '--- nearby lines in mobile/services/student-service.ts ---'
sed -n '1,220p' mobile/services/student-service.ts

echo
echo '--- search for createStudent / academicYearId usage ---'
rg -n "createStudent|academicYearId|academicYearContext|getActiveId|RequestInit|body:" mobile server -S

Repository: thureinhtet99/student-os

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- mobile/services/api.ts ---'
sed -n '1,220p' mobile/services/api.ts

echo
echo '--- server/src/modules/students/dto/create-student.dto.ts ---'
sed -n '1,220p' server/src/modules/students/dto/create-student.dto.ts

echo
echo '--- server/src/modules/students/students.service.ts (create path) ---'
sed -n '1,180p' server/src/modules/students/students.service.ts

echo
echo '--- server/src/modules/students/students.service.spec.ts (academicYearId cases) ---'
sed -n '70,180p' server/src/modules/students/students.service.spec.ts

Repository: thureinhtet99/student-os

Length of output: 10287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- students controller ---'
sed -n '1,220p' server/src/modules/students/students.controller.ts

echo
echo '--- server bootstrap / validation pipe search ---'
rg -n "ValidationPipe|app.useGlobalPipes|skipMissingProperties|whitelist|forbidNonWhitelisted" server/src -S

echo
echo '--- CreateStudentDto references / generated docs hints ---'
rg -n "CreateStudentDto|academicYearId" server/src/modules/students -S

Repository: thureinhtet99/student-os

Length of output: 5622


Serialize the student-create payload before sending it.
createStudent passes a plain object as body, but fetch expects serialized JSON here. Stringify the payload before calling apiRequest (or handle serialization centrally in apiRequest).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/services/student-service.ts` at line 24, Update createStudent to
serialize its student-create payload as JSON before passing it to apiRequest, or
reuse an existing centralized serialization path in apiRequest; ensure the
request body is no longer sent as a plain object.

role?: StudentRole;
phone?: string;
address?: string;
dateOfBirth?: string; // ISO date string
image?: string;
parentId?: string;
classId?: string;
}

export async function getStudents(page = 1, limit = 10): Promise<PaginatedResponse<Student>> {
return apiRequest<PaginatedResponse<Student>>(`/students?page=${page}&limit=${limit}`);
}

export async function getStudent(id: string): Promise<Student> {
return apiRequest<Student>(`/students/${id}`);
}

export async function createStudent(payload: CreateStudentPayload): Promise<Student> {
return apiRequest<Student>('/students', {
method: 'POST',
body: payload,
});
Comment on lines +42 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize the POST body before calling fetch.

apiRequest forwards body directly to fetch, but this passes a plain object instead of a JSON BodyInit. The request can fail TypeScript validation or arrive at the server as invalid JSON.

   return apiRequest<Student>('/students', {
     method: 'POST',
-    body: payload,
+    body: JSON.stringify(payload),
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function createStudent(payload: CreateStudentPayload): Promise<Student> {
return apiRequest<Student>('/students', {
method: 'POST',
body: payload,
});
export async function createStudent(payload: CreateStudentPayload): Promise<Student> {
return apiRequest<Student>('/students', {
method: 'POST',
body: JSON.stringify(payload),
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/services/student-service.ts` around lines 42 - 46, Update
createStudent to serialize payload into a JSON string before passing it as the
body to apiRequest, ensuring the POST request supplies a valid fetch BodyInit
while preserving the existing endpoint and method.

}
287 changes: 150 additions & 137 deletions server/AGENT.md

Large diffs are not rendered by default.

88 changes: 0 additions & 88 deletions server/MODULE_ALIGNMENT.md

This file was deleted.

10 changes: 0 additions & 10 deletions server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,6 @@ model AcademicYear {
results Result[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// classes Class[]
// attendances Attendance[]
// exams Exam[]

@@map("academic_years")
}
Expand All @@ -177,12 +174,6 @@ model Class {
teachingAllocations TeachingAllocation[]
announcements Announcement[]
deletedAt DateTime?
// academicYearId String
// academicYear AcademicYear @relation(fields: [academicYearId], references: [id])
// gradeId String
// grade Grade @relation(fields: [gradeId], references: [id])
// @@unique([gradeId, name])
// @@unique([name, academicYearId])

@@unique([name])
@@map("classes")
Expand Down Expand Up @@ -228,7 +219,6 @@ model TeachingAllocation {
academicYear AcademicYear @relation(fields: [academicYearId], references: [id])
timetableEntries Timetable[]
exams Exam[]
// assignments Assignment[]

@@unique([teacherId, subjectId, classId, academicYearId])
@@map("teaching_assignments")
Expand Down
2 changes: 0 additions & 2 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import { ClassesModule } from './modules/classes/classes.module';
import { EnrollmentsModule } from './modules/enrollments/enrollments.module.js';
import { ExamsModule } from './modules/exams/exams.module';
import { ParentsModule } from './modules/parents/parents.module';
import { ResultsModule } from './modules/results/results.module';
import { StudentsModule } from './modules/students/students.module';
import { SubjectsModule } from './modules/subjects/subjects.module';
Expand Down Expand Up @@ -59,7 +58,6 @@
StudentsModule,
AcademicYearsModule,
ClassesModule,
ParentsModule,
TeachersModule,
AdminsModule,
SubjectsModule,
Expand All @@ -76,7 +74,7 @@
urlencoded: { enabled: true, limit: '2mb', extended: true },
rawBody: true,
},
middleware: (req, res, next) => rateLimiter(req, res, next),

Check warning on line 77 in server/src/app.module.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `Response<any, Record<string, any>, number>`

Check warning on line 77 in server/src/app.module.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>`
}),
LocalAuthModule,
],
Expand Down
66 changes: 55 additions & 11 deletions server/src/common/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,94 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsDateString,
IsEmail,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsStrongPassword,
MaxLength,
} from 'class-validator';
import { UserGender, UserRole } from '../../../prisma/generated/prisma/client';
import { UserGender } from '../../../prisma/generated/prisma/client';

export class CreateUserDto {
@ApiProperty({
type: String,
example: 'john doe',
})
@IsString()
@IsNotEmpty()
@MaxLength(200)
@MaxLength(400)
name!: string;

@ApiProperty({
type: String,
example: 'johndoe@example.com',
})
@IsNotEmpty()
@IsEmail()
email!: string;

@ApiProperty({
type: String,
example: 'password123',
})
@IsString()
@IsStrongPassword()
@IsNotEmpty()
password!: string;
Comment on lines +32 to 39

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File context ==\n'
git ls-files server/src/common/dto/create-user.dto.ts
wc -l server/src/common/dto/create-user.dto.ts
cat -n server/src/common/dto/create-user.dto.ts | sed -n '1,120p'

printf '\n== class-validator version refs ==\n'
rg -n '"class-validator"|from '\''class-validator'\''|from \"class-validator\"' -S package.json server package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true

printf '\n== StrongPassword usages ==\n'
rg -n "`@IsStrongPassword`\(" server -S || true

printf '\n== Nearby DTO patterns ==\n'
rg -n "`@ApiProperty`\\(|example:" server/src/common/dto -S || true

Repository: thureinhtet99/student-os

Length of output: 5154


Use a strong-password-compliant example
password123 doesn't satisfy @IsStrongPassword(), so copying the Swagger sample will trigger a 400. Change it to something like Password123!.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/common/dto/create-user.dto.ts` around lines 32 - 39, Update the
Swagger example in the password property of create-user.dto.ts to a value that
satisfies `@IsStrongPassword`(), such as a password containing uppercase and
lowercase letters, numbers, and a symbol; leave the validation decorators
unchanged.


@ApiPropertyOptional({
type: String,
example: '123456789',
nullable: true,
})
@IsString()
@IsNotEmpty()
@IsOptional()
@MaxLength(15)
phone!: string | null;
phone?: string | null;

@ApiPropertyOptional({
type: String,
example: '123 Main St, Anytown',
nullable: true,
})
@IsString()
@IsOptional()
@MaxLength(500)
address!: string | null;
@MaxLength(200)
address?: string | null;

@ApiProperty({
type: String,
example: 'MALE',
})
@IsNotEmpty()
@IsEnum(UserGender)
gender!: UserGender;

@ApiPropertyOptional({
type: String,
example: '2005-08-24T00:00:00.000Z',
nullable: true,
})
@IsDateString()
@IsOptional()
dateOfBirth!: string | null;
dateOfBirth?: string | null;

@ApiPropertyOptional({
type: String,
example: 'http://example.com/image.png',
nullable: true,
})
@IsString()
@IsOptional()
image!: string | null;
image?: string | null;

@IsEnum(UserRole)
@IsOptional()
role!: UserRole;
// @ApiProperty({
// type: String,
// example: 'STUDENT',
// })
// @IsNotEmpty()
// @IsEnum(UserRole)
// role!: UserRole;
}
20 changes: 0 additions & 20 deletions server/src/common/formatters/parent.formatter.ts

This file was deleted.

16 changes: 11 additions & 5 deletions server/src/common/formatters/student.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ export function formatStudent(
student: StudentWithRelations,
): StudentResponseDto {
return {
id: student.user.id,
id: student.id,
userId: student.user.id,
name: student.user.name,
email: student.user.email,
studentId: student.studentNumber,
studentNumber: student.studentNumber,
image: student.user.image,
phone: student.phone,
address: student.address,
gender: student.gender,
dateOfBirth: student.dateOfBirth ? student.dateOfBirth.toISOString() : null,
parent_student_relationship: student.parents?.[0]?.relationship ?? null,
parent: student.parents?.[0]?.parent
? {
id: student.parents[0].parent.id,
name: student.parents[0].parent.name,
phone: student.parents[0].parent.phone,
address: student.parents[0].parent.address,
}
: null,
class: student.enrollments?.[0]?.class
? {
id: student.enrollments[0].class.id,
name: student.enrollments[0].class.name,
name: student.enrollments?.[0].class.name,
}
: null,
Comment on lines 26 to 30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

class still taken from unfiltered enrollments[0].

Unresolved from a previous review (previously about .id, now .name — same underlying defect): a student's enrollments relation isn't scoped to the active/target academic year, so a student with enrollments across multiple years returns an arbitrary class.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/common/formatters/student.formatter.ts` around lines 26 - 30,
Update the student formatter’s class mapping to select the enrollment matching
the active/target academic year before reading its class name, rather than
always using enrollments[0]. Reuse the same year-selection logic or value
already used elsewhere in the formatter, and preserve the null result when no
matching enrollment or class exists.

setPasswordToken: student.user.setPasswordToken,
Expand Down
2 changes: 1 addition & 1 deletion server/src/common/formatters/user.formatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UserGender } from '../../../prisma/generated/prisma/client';

export function formatGender(gender: string): UserGender {
export function formatGender(gender: UserGender): UserGender {
return gender.toUpperCase() as UserGender;
}
Loading
Loading