teacher-tools/api/source/api_modules/classroom_compliance/api_student.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.
}