249 lines
8.7 KiB
D
249 lines
8.7 KiB
D
module api_modules.classroom_compliance.api_student;
|
|
|
|
import handy_httpd;
|
|
import ddbc;
|
|
import std.json;
|
|
|
|
import api_modules.auth : User, getUserOrThrow;
|
|
import api_modules.classroom_compliance.model;
|
|
import api_modules.classroom_compliance.util;
|
|
import db;
|
|
import data_utils;
|
|
|
|
void createStudent(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto cls = getClassOrThrow(ctx, conn, user);
|
|
struct StudentPayload {
|
|
string name;
|
|
ushort deskNumber;
|
|
bool removed;
|
|
}
|
|
auto payload = readJsonPayload!(StudentPayload)(ctx);
|
|
bool studentExists = recordExists(
|
|
conn,
|
|
"SELECT id FROM classroom_compliance_student WHERE name = ? AND class_id = ?",
|
|
payload.name, cls.id
|
|
);
|
|
if (studentExists) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("A student with that name already exists in this class.");
|
|
return;
|
|
}
|
|
bool deskAlreadyOccupied = payload.deskNumber != 0 && recordExists(
|
|
conn,
|
|
"SELECT id FROM classroom_compliance_student WHERE class_id = ? AND desk_number = ?",
|
|
cls.id, payload.deskNumber
|
|
);
|
|
if (deskAlreadyOccupied) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("There is already a student assigned to that desk number.");
|
|
return;
|
|
}
|
|
ulong studentId = insertOne(
|
|
conn,
|
|
"INSERT INTO classroom_compliance_student
|
|
(name, class_id, desk_number, removed)
|
|
VALUES (?, ?, ?, ?) RETURNING id",
|
|
payload.name, cls.id, payload.deskNumber, payload.removed
|
|
);
|
|
auto student = findOne(
|
|
conn,
|
|
"SELECT * FROM classroom_compliance_student WHERE id = ?",
|
|
&ClassroomComplianceStudent.parse,
|
|
studentId
|
|
).orElseThrow();
|
|
writeJsonBody(ctx, student);
|
|
}
|
|
|
|
void getStudents(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto cls = getClassOrThrow(ctx, conn, user);
|
|
auto students = findAll(
|
|
conn,
|
|
"SELECT * FROM classroom_compliance_student WHERE class_id = ? ORDER BY name ASC",
|
|
&ClassroomComplianceStudent.parse,
|
|
cls.id
|
|
);
|
|
writeJsonBody(ctx, students);
|
|
}
|
|
|
|
void getStudent(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
writeJsonBody(ctx, student);
|
|
}
|
|
|
|
void updateStudent(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
struct StudentUpdatePayload {
|
|
string name;
|
|
ushort deskNumber;
|
|
bool removed;
|
|
}
|
|
auto payload = readJsonPayload!(StudentUpdatePayload)(ctx);
|
|
// If there is nothing to update, quit.
|
|
if (
|
|
payload.name == student.name
|
|
&& payload.deskNumber == student.deskNumber
|
|
&& payload.removed == student.removed
|
|
) return;
|
|
// Check that the new name doesn't already exist.
|
|
bool newNameExists = payload.name != student.name && recordExists(
|
|
conn,
|
|
"SELECT id FROM classroom_compliance_student WHERE name = ? AND class_id = ?",
|
|
payload.name, student.classId
|
|
);
|
|
if (newNameExists) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("A student with that name already exists in this class.");
|
|
return;
|
|
}
|
|
// Check that if a new desk number is assigned, that it's not already assigned to anyone else.
|
|
bool newDeskOccupied = payload.deskNumber != 0 && payload.deskNumber != student.deskNumber && recordExists(
|
|
conn,
|
|
"SELECT id FROM classroom_compliance_student WHERE class_id = ? AND desk_number = ?",
|
|
student.classId, payload.deskNumber
|
|
);
|
|
if (newDeskOccupied) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("That desk is already assigned to another student.");
|
|
return;
|
|
}
|
|
update(
|
|
conn,
|
|
"UPDATE classroom_compliance_student SET name = ?, desk_number = ?, removed = ? WHERE id = ?",
|
|
payload.name,
|
|
payload.deskNumber,
|
|
payload.removed,
|
|
student.id
|
|
);
|
|
auto updatedStudent = findOne(
|
|
conn,
|
|
"SELECT * FROM classroom_compliance_student WHERE id = ?",
|
|
&ClassroomComplianceStudent.parse,
|
|
student.id
|
|
).orElseThrow();
|
|
writeJsonBody(ctx, updatedStudent);
|
|
}
|
|
|
|
void deleteStudent(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
update(
|
|
conn,
|
|
"DELETE FROM classroom_compliance_student WHERE id = ? AND class_id = ?",
|
|
student.id, student.classId
|
|
);
|
|
}
|
|
|
|
void getStudentEntries(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
auto entries = findAll(
|
|
conn,
|
|
"SELECT * FROM classroom_compliance_entry WHERE student_id = ? ORDER BY date DESC",
|
|
&ClassroomComplianceEntry.parse,
|
|
student.id
|
|
);
|
|
JSONValue response = JSONValue.emptyArray;
|
|
foreach (entry; entries) response.array ~= entry.toJsonObj();
|
|
ctx.response.writeBodyString(response.toJSON(), "application/json");
|
|
}
|
|
|
|
void getStudentOverview(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
scope(exit) conn.close();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
|
|
const ulong entryCount = count(
|
|
conn,
|
|
"SELECT COUNT(id) FROM classroom_compliance_entry WHERE student_id = ?",
|
|
student.id
|
|
);
|
|
if (entryCount == 0) {
|
|
ctx.response.status = HttpStatus.NOT_FOUND;
|
|
ctx.response.writeBodyString("No entries for this student.");
|
|
return;
|
|
}
|
|
const ulong absenceCount = count(
|
|
conn,
|
|
"SELECT COUNT(id) FROM classroom_compliance_entry WHERE student_id = ? AND absent = true",
|
|
student.id
|
|
);
|
|
const ulong phoneNoncomplianceCount = count(
|
|
conn,
|
|
"SELECT COUNT(id) FROM classroom_compliance_entry WHERE phone_compliant = FALSE AND student_id = ?",
|
|
student.id
|
|
);
|
|
const behaviorCountQuery = "
|
|
SELECT COUNT(id)
|
|
FROM classroom_compliance_entry
|
|
WHERE student_id = ? AND behavior_rating = ?
|
|
";
|
|
|
|
const ulong behaviorGoodCount = count(conn, behaviorCountQuery, student.id, 3);
|
|
const ulong behaviorMediocreCount = count(conn, behaviorCountQuery, student.id, 2);
|
|
const ulong behaviorPoorCount = count(conn, behaviorCountQuery, student.id, 1);
|
|
|
|
// Calculate derived statistics.
|
|
const ulong attendanceCount = entryCount - absenceCount;
|
|
double attendanceRate = attendanceCount / cast(double) entryCount;
|
|
double phoneComplianceRate = (attendanceCount - phoneNoncomplianceCount) / cast(double) attendanceCount;
|
|
double behaviorScore = (
|
|
behaviorGoodCount * 1.0 +
|
|
behaviorMediocreCount * 0.5
|
|
) / attendanceCount;
|
|
|
|
JSONValue response = JSONValue.emptyObject;
|
|
response.object["attendanceRate"] = JSONValue(attendanceRate);
|
|
response.object["phoneComplianceRate"] = JSONValue(phoneComplianceRate);
|
|
response.object["behaviorScore"] = JSONValue(behaviorScore);
|
|
response.object["entryCount"] = JSONValue(entryCount);
|
|
ctx.response.writeBodyString(response.toJSON(), "application/json");
|
|
}
|
|
|
|
void moveStudentToOtherClass(ref HttpRequestContext ctx) {
|
|
Connection conn = getDb();
|
|
User user = getUserOrThrow(ctx, conn);
|
|
auto student = getStudentOrThrow(ctx, conn, user);
|
|
struct Payload {
|
|
ulong classId;
|
|
}
|
|
Payload payload = readJsonPayload!(Payload)(ctx);
|
|
if (payload.classId == student.classId) {
|
|
return; // Quit if the student is already in the desired class.
|
|
}
|
|
// Check that the desired class exists, and belongs to the user.
|
|
bool newClassIdValid = recordExists(
|
|
conn,
|
|
"SELECT id FROM classroom_compliance_class WHERE user_id = ? and id = ?",
|
|
user.id, payload.classId
|
|
);
|
|
if (!newClassIdValid) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("Invalid class was selected.");
|
|
return;
|
|
}
|
|
// All good, so update the student's class to the desired one, and reset their desk.
|
|
update(
|
|
conn,
|
|
"UPDATE classroom_compliance_student SET class_id = ?, desk_number = 0 WHERE id = ?",
|
|
payload.classId, student.id
|
|
);
|
|
// We just return 200 OK, no response body.
|
|
}
|