Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
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;
}
23 changes: 9 additions & 14 deletions server/src/common/formatters/student.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@ 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,
parent: student.parents?.[0]?.parent
? {
id: student.parents[0].parent.id,
name: student.parents[0].parent.name,
}
: null,
class: student.enrollments?.[0]?.class
? {
id: student.enrollments[0].class.id,
name: student.enrollments[0].class.name,
}
: null,
phone: student.phone,
address: student.address,
gender: student.gender,
dateOfBirth: student.dateOfBirth ? student.dateOfBirth.toISOString() : null,
parentId: student.parents?.[0]?.parent?.id ?? null,
classId: student.enrollments?.[0]?.class?.id ?? null,

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how reference modules (academic-years, classes) handle enrollment scoping.
rg -n 'academicYearId|enrollments' server/src/modules/academic-years/ server/src/modules/classes/ --type ts -C3

Repository: thureinhtet99/student-os

Length of output: 22399


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the formatter and the student query paths that populate `student.enrollments`.
for f in \
  server/src/common/formatters/student.formatter.ts \
  server/src/modules/students/students.service.ts \
  server/src/modules/students/students.controller.ts \
  server/src/modules/students/dto/*.ts \
  server/src/modules/enrollments/*.ts
do
  [ -f "$f" ] && echo "### $f" && sed -n '1,260p' "$f" && echo
done

Repository: thureinhtet99/student-os

Length of output: 23373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether student queries ever scope enrollments by academic year or impose a stable order.
rg -n 'academicYearId|enrollments:\s*{|orderBy:.*enrollments|formatStudent\(' server/src/modules/students server/src/common -g '*.ts' -C3

# Inspect the Prisma model for enrollment ordering fields that might make `[0]` deterministic.
rg -n 'model Enrollment|createdAt|updatedAt|academicYearId|studentId|classId' prisma -g '*.prisma' -C3

Repository: thureinhtet99/student-os

Length of output: 345


classId should be scoped to the active academic year

student.enrollments?.[0]?.class?.id reads the first enrollment from an unfiltered relation, so a student with multiple enrollments can return the wrong class. Scope enrollments to the intended academic year, or make this fallback explicit if the ambiguity is intentional.

🤖 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 17 - 18,
Update the student formatter’s classId mapping to select the enrollment
associated with the active academic year before reading class.id, rather than
blindly using student.enrollments[0]. Reuse the existing academic-year context
or filtering convention, and preserve null when no matching enrollment exists.

setPasswordToken: student.user.setPasswordToken,
setPasswordTokenExpires: student.user.setPasswordTokenExpires ?? null,
resetPasswordToken: student.user.resetPasswordToken,
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;
}
4 changes: 2 additions & 2 deletions server/src/common/types/student.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
export type StudentWithRelations = Omit<Student, 'password'> & {
user: User;
enrollments: (Enrollment & {
class: Class;
class: Pick<Class, 'id'>;
})[];
parents: (ParentStudent & {
parent: Parent;
parent: Pick<Parent, 'id'>;
})[];
};
Loading
Loading