diff --git a/api/schema.sql b/api/schema/auth.sql similarity index 51% rename from api/schema.sql rename to api/schema/auth.sql index a704a95..cdc3662 100644 --- a/api/schema.sql +++ b/api/schema/auth.sql @@ -6,11 +6,3 @@ CREATE TABLE user ( is_locked INTEGER NOT NULL, is_admin INTEGER NOT NULL ); - -INSERT INTO user (username, password_hash, created_at, is_locked, is_admin) VALUES ( - 'test', - '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08', - 1734380300, - 0, - 1 -); diff --git a/api/schema/classroom_compliance.sql b/api/schema/classroom_compliance.sql new file mode 100644 index 0000000..63820a0 --- /dev/null +++ b/api/schema/classroom_compliance.sql @@ -0,0 +1,47 @@ +CREATE TABLE classroom_compliance_class ( + id INTEGER PRIMARY KEY, + number INTEGER NOT NULL, + school_year TEXT NOT NULL, + user_id INTEGER NOT NULL REFERENCES user(id) + ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE classroom_compliance_student ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + class_id INTEGER NOT NULL REFERENCES classroom_compliance_class(id) + ON UPDATE CASCADE ON DELETE CASCADE +); + +CREATE TABLE classroom_compliance_desk_assignment ( + id INTEGER PRIMARY KEY, + class_id INTEGER NOT NULL REFERENCES classroom_compliance_class(id) + ON UPDATE CASCADE ON DELETE CASCADE, + desk_number INTEGER NOT NULL, + student_id INTEGER REFERENCES classroom_compliance_student(id) + ON UPDATE CASCADE ON DELETE SET NULL +); + +CREATE TABLE classroom_compliance_entry ( + id INTEGER PRIMARY KEY, + class_id INTEGER NOT NULL REFERENCES classroom_compliance_class(id) + ON UPDATE CASCADE ON DELETE CASCADE, + class_description TEXT NOT NULL, + student_id INTEGER NOT NULL REFERENCES classroom_compliance_student(id), + date TEXT NOT NULL, + created_at INTEGER NOT NULL, + absent INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE classroom_compliance_entry_phone ( + entry_id INTEGER PRIMARY KEY REFERENCES classroom_compliance_entry(id) + ON UPDATE CASCADE ON DELETE CASCADE, + compliant INTEGER NOT NULL DEFAULT 1 +); + +CREATE TABLE classroom_compliance_entry_behavior ( + entry_id INTEGER PRIMARY KEY REFERENCES classroom_compliance_entry(id) + ON UPDATE CASCADE ON DELETE CASCADE, + rating INTEGER NOT NULL, + comment TEXT +); diff --git a/api/schema/sample_data.sql b/api/schema/sample_data.sql new file mode 100644 index 0000000..7552cb3 --- /dev/null +++ b/api/schema/sample_data.sql @@ -0,0 +1,17 @@ +-- TEST DATA + +-- username: test, password: test +INSERT INTO user (username, password_hash, created_at, is_locked, is_admin) VALUES ( + 'test', + '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08', + 1734380300, + 0, + 1 +); +INSERT INTO user (username, password_hash, created_at, is_locked, is_admin) VALUES ( + 'test2', + '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08', + 1734394062, + 0, + 0 +); diff --git a/api/source/api_modules/auth.d b/api/source/api_modules/auth.d index 64f93db..623a70b 100644 --- a/api/source/api_modules/auth.d +++ b/api/source/api_modules/auth.d @@ -11,17 +11,13 @@ import data_utils; struct User { const ulong id; const string username; - @Column("password_hash") const string passwordHash; - @Column("created_at") const ulong createdAt; - @Column("is_locked") const bool isLocked; - @Column("is_admin") const bool isAdmin; } -struct UserResponse { +private struct UserResponse { ulong id; string username; ulong createdAt; diff --git a/api/source/api_modules/classroom_compliance.d b/api/source/api_modules/classroom_compliance.d new file mode 100644 index 0000000..6d2cfd3 --- /dev/null +++ b/api/source/api_modules/classroom_compliance.d @@ -0,0 +1,373 @@ +module api_modules.classroom_compliance; + +import handy_httpd; +import handy_httpd.handlers.path_handler; +import std.typecons : Nullable; +import d2sqlite3; + +import db; +import data_utils; +import api_modules.auth : User, getUserOrThrow; + +struct ClassroomComplianceClass { + const ulong id; + const ushort number; + const string schoolYear; + const ulong userId; +} + +struct ClassroomComplianceStudent { + const ulong id; + const string name; + const ulong classId; +} + +struct ClassroomComplianceDeskAssignment { + const ulong id; + const ulong classId; + const ushort deskNumber; + const ulong studentId; +} + +struct ClassroomComplianceEntry { + const ulong id; + const ulong classId; + const string classDescription; + const ulong studentId; + const string date; + const ulong createdAt; + const bool absent; +} + +struct ClassroomComplianceEntryPhone { + const ulong entryId; + const bool compliant; +} + +struct ClassroomComplianceEntryBehavior { + const ulong entryId; + const ubyte rating; + const string comment; +} + +void registerApiEndpoints(PathHandler handler) { + const ROOT_PATH = "/api/classroom-compliance"; + + handler.addMapping(Method.POST, ROOT_PATH ~ "/classes", &createClass); + handler.addMapping(Method.GET, ROOT_PATH ~ "/classes", &getClasses); + const CLASS_PATH = ROOT_PATH ~ "/classes/:classId:ulong"; + handler.addMapping(Method.DELETE, CLASS_PATH, &deleteClass); + + handler.addMapping(Method.POST, CLASS_PATH ~ "/students", &createStudent); + handler.addMapping(Method.GET, CLASS_PATH ~ "/students", &getStudents); + const STUDENT_PATH = CLASS_PATH ~ "/students/:studentId:ulong"; + handler.addMapping(Method.PUT, STUDENT_PATH, &updateStudent); + handler.addMapping(Method.DELETE, STUDENT_PATH, &deleteStudent); + + handler.addMapping(Method.POST, CLASS_PATH ~ "/desk-assignments", &setDeskAssignments); + handler.addMapping(Method.GET, CLASS_PATH ~ "/desk-assignments", &getDeskAssignments); + handler.addMapping(Method.DELETE, CLASS_PATH ~ "/desk-assignments", &removeAllDeskAssignments); + + handler.addMapping(Method.POST, CLASS_PATH ~ "/entries", &createEntry); +} + +void createClass(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + struct ClassPayload { + ushort number; + string schoolYear; + } + auto payload = readJsonPayload!(ClassPayload)(ctx); + auto db = getDb(); + const bool classNumberExists = canFind( + db, + "SELECT id FROM classroom_compliance_class WHERE number = ? AND school_year = ? AND user_id = ?", + payload.number, + payload.schoolYear, + user.id + ); + if (classNumberExists) { + ctx.response.status = HttpStatus.BAD_REQUEST; + ctx.response.writeBodyString("There is already a class with this number, for this school year."); + return; + } + auto stmt = db.prepare("INSERT INTO classroom_compliance_class (number, school_year, user_id) VALUES (?, ?, ?)"); + stmt.bindAll(payload.number, payload.schoolYear, user.id); + stmt.execute(); + ulong classId = db.lastInsertRowid(); + auto newClass = findOne!(ClassroomComplianceClass)( + db, + "SELECT * FROM classroom_compliance_class WHERE id = ? AND user_id = ?", + classId, + user.id + ).orElseThrow(); + writeJsonBody(ctx, newClass); +} + +void getClasses(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto db = getDb(); + auto classes = findAll!(ClassroomComplianceClass)( + db, + "SELECT * FROM classroom_compliance_class WHERE user_id = ? ORDER BY school_year DESC, number ASC", + user.id + ); + writeJsonBody(ctx, classes); +} + +void deleteClass(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + auto db = getDb(); + db.execute("DELETE FROM classroom_compliance_class WHERE id = ? AND user_id = ?", cls.id, user.id); +} + +ClassroomComplianceClass getClassOrThrow(ref HttpRequestContext ctx, in User user) { + ulong classId = ctx.request.getPathParamAs!ulong("classId"); + auto db = getDb(); + return findOne!(ClassroomComplianceClass)( + db, + "SELECT * FROM classroom_compliance_class WHERE user_id = ? AND id = ?", + user.id, + classId + ).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND)); +} + +void createStudent(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + struct StudentPayload { + string name; + } + auto payload = readJsonPayload!(StudentPayload)(ctx); + auto db = getDb(); + bool studentExists = canFind( + db, + "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("Student with that name already exists."); + return; + } + db.execute("INSERT INTO classroom_compliance_student (name, class_id) VALUES (?, ?)", payload.name, cls.id); + ulong studentId = db.lastInsertRowid(); + auto student = findOne!(ClassroomComplianceStudent)( + db, + "SELECT * FROM classroom_compliance_student WHERE id = ?", + studentId + ).orElseThrow(); + writeJsonBody(ctx, student); +} + +void getStudents(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + auto db = getDb(); + auto students = findAll!(ClassroomComplianceStudent)( + db, + "SELECT * FROM classroom_compliance_student WHERE class_id = ? ORDER BY name ASC", + cls.id + ); + writeJsonBody(ctx, students); +} + +void updateStudent(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto student = getStudentOrThrow(ctx, user); + struct StudentUpdatePayload { + string name; + } + auto payload = readJsonPayload!(StudentUpdatePayload)(ctx); + // If there is nothing to update, quit. + if (payload.name == student.name) return; + // Check that the new name doesn't already exist. + auto db = getDb(); + bool newNameExists = canFind( + db, + "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("Student with that name already exists."); + return; + } + db.execute( + "UPDATE classroom_compliance_student SET name = ? WHERE id = ?", + payload.name, + student.id + ); + auto updatedStudent = findOne!(ClassroomComplianceStudent)( + db, + "SELECT * FROM classroom_compliance_student WHERE id = ?", + student.id + ).orElseThrow(); + writeJsonBody(ctx, updatedStudent); +} + +void deleteStudent(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto student = getStudentOrThrow(ctx, user); + auto db = getDb(); + db.execute( + "DELETE FROM classroom_compliance_student WHERE id = ? AND class_id = ?", + student.id, + student.classId + ); +} + +ClassroomComplianceStudent getStudentOrThrow(ref HttpRequestContext ctx, in User user) { + ulong classId = ctx.request.getPathParamAs!ulong("classId"); + ulong studentId = ctx.request.getPathParamAs!ulong("studentId"); + auto db = getDb(); + string query = " + SELECT s.* + FROM classroom_compliance_student s + LEFT JOIN classroom_compliance_class c ON s.class_id = c.id + WHERE s.id = ? AND s.class_id = ? AND c.user_id = ? + "; + return findOne!(ClassroomComplianceStudent)( + db, + query, + studentId, + classId, + user.id + ).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND)); +} + +private struct DeskAssignmentPayloadEntry { + ushort deskNumber; + Nullable!ulong studentId; +} +private struct DeskAssignmentPayload { + DeskAssignmentPayloadEntry[] entries; +} + +void setDeskAssignments(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + auto payload = readJsonPayload!(DeskAssignmentPayload)(ctx); + auto db = getDb(); + auto validationError = validateDeskAssignments(db, payload, cls.id); + if (validationError) { + import slf4d; + warnF!"Desk assignment validation failed: %s"(validationError.value); + ctx.response.status = HttpStatus.BAD_REQUEST; + ctx.response.writeBodyString(validationError.value); + return; + } + + db.begin(); + try { + db.execute( + "DELETE FROM classroom_compliance_desk_assignment WHERE class_id = ?", + cls.id + ); + auto stmt = db.prepare( + "INSERT INTO classroom_compliance_desk_assignment (class_id, desk_number, student_id) VALUES (?, ?, ?)" + ); + foreach (entry; payload.entries) { + stmt.bindAll(cls.id, entry.deskNumber, entry.studentId); + stmt.execute(); + stmt.clearBindings(); + stmt.reset(); + } + db.commit(); + // Return the new list of desk assignments to the user. + auto newAssignments = findAll!(ClassroomComplianceDeskAssignment)( + db, + "SELECT * FROM classroom_compliance_desk_assignment WHERE class_id = ?", + cls.id + ); + writeJsonBody(ctx, newAssignments); + } catch (Exception e) { + db.rollback(); + } +} + +Optional!string validateDeskAssignments(Database db, in DeskAssignmentPayload payload, ulong classId) { + import std.algorithm : canFind, map; + import std.array; + // Check that desks are numbered 1 .. N. + for (int n = 1; n <= payload.entries.length; n++) { + bool deskPresent = false; + foreach (entry; payload.entries) { + if (entry.deskNumber == n) { + deskPresent = true; + break; + } + } + if (!deskPresent) return Optional!string.of("Desks should be numbered from 1 to N."); + } + auto allStudents = findAll!(ClassroomComplianceStudent)( + db, + "SELECT * FROM classroom_compliance_student WHERE class_id = ?", + classId + ); + auto studentIds = allStudents.map!(s => s.id).array; + // Check that if a desk is assigned to a student, that it's one from this class. + foreach (entry; payload.entries) { + if (!entry.studentId.isNull && !canFind(studentIds, entry.studentId.get)) { + return Optional!string.of("Desks cannot be assigned to students that don't belong to this class."); + } + } + + // Check that each student in the class is assigned a desk. + ushort[] takenDesks; + foreach (student; allStudents) { + ushort[] assignedDesks; + foreach (entry; payload.entries) { + if (!entry.studentId.isNull && entry.studentId.get() == student.id) { + assignedDesks ~= entry.deskNumber; + } + } + if (assignedDesks.length != 1) { + if (assignedDesks.length > 1) { + return Optional!string.of("A student is assigned to more than one desk."); + } else { + return Optional!string.of("Not all students are assigned to a desk."); + } + } + if (canFind(takenDesks, assignedDesks[0])) { + return Optional!string.of("Cannot assign more than one student to the same desk."); + } + takenDesks ~= assignedDesks[0]; + } + return Optional!string.empty(); +} + +void getDeskAssignments(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + auto db = getDb(); + auto deskAssignments = findAll!(ClassroomComplianceDeskAssignment)( + db, + " + SELECT d.* FROM classroom_compliance_desk_assignment d + WHERE class_id = ? + ORDER BY desk_number ASC + ", + cls.id + ); + writeJsonBody(ctx, deskAssignments); +} + +void removeAllDeskAssignments(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + auto db = getDb(); + db.execute( + "DELETE FROM classroom_compliance_desk_assignment WHERE class_id = ?", + cls.id + ); +} + +void createEntry(ref HttpRequestContext ctx) { + User user = getUserOrThrow(ctx); + auto cls = getClassOrThrow(ctx, user); + +} diff --git a/api/source/app.d b/api/source/app.d index b45be0e..3b66c27 100644 --- a/api/source/app.d +++ b/api/source/app.d @@ -5,8 +5,13 @@ import d2sqlite3; import db; import api_modules.auth; +static import api_modules.classroom_compliance; void main() { + // Initialize the database on startup. + auto db = getDb(); + db.close(); + ServerConfig config; config.enableWebSockets = false; config.port = 8080; @@ -21,6 +26,7 @@ void main() { PathHandler handler = new PathHandler(); handler.addMapping(Method.OPTIONS, "/api/**", &optionsEndpoint); handler.addMapping(Method.POST, "/api/auth/login", &loginEndpoint); + api_modules.classroom_compliance.registerApiEndpoints(handler); HttpServer server = new HttpServer(handler, config); server.start(); diff --git a/api/source/data_utils.d b/api/source/data_utils.d index a9c5665..15e151c 100644 --- a/api/source/data_utils.d +++ b/api/source/data_utils.d @@ -19,6 +19,8 @@ T readJsonPayload(T)(ref HttpRequestContext ctx) { string requestBody = ctx.request.readBodyAsString(); return deserialize!T(requestBody); } catch (SerdeException e) { + import slf4d; + warnF!"Failed to read JSON payload: %s"(e.msg); throw new HttpStatusException(HttpStatus.BAD_REQUEST); } } @@ -31,10 +33,25 @@ T readJsonPayload(T)(ref HttpRequestContext ctx) { * data = The data to write. */ void writeJsonBody(T)(ref HttpRequestContext ctx, in T data) { + import std.traits : isArray; try { + static if (isArray!T) { + if (data.length == 0) { + ctx.response.writeBodyString("[]", "application/json"); + return; + } + } string jsonStr = serializeToJson(data); ctx.response.writeBodyString(jsonStr, "application/json"); } catch (SerdeException e) { throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR); } } + +ulong getUnixTimestampMillis() { + import std.datetime; + SysTime now = Clock.currTime(); + SysTime unixEpoch = SysTime(DateTime(1970, 1, 1), UTC()); + Duration diff = now - unixEpoch; + return diff.total!"msecs"; +} diff --git a/api/source/db.d b/api/source/db.d index ae6ce6f..166f36a 100644 --- a/api/source/db.d +++ b/api/source/db.d @@ -22,14 +22,61 @@ Database getDb() { } Database db = Database("teacher-tools.db", flags); db.execute("PRAGMA foreign_keys=ON"); - const string schema = import("schema.sql"); if (shouldInitDb) { - db.run(schema); + const string authSchema = import("schema/auth.sql"); + const string classroomComplianceSchema = import("schema/classroom_compliance.sql"); + db.run(authSchema); + db.run(classroomComplianceSchema); + + const string sampleData = import("schema/sample_data.sql"); + db.run(sampleData); + info("Initialized database schema."); } return db; } +T[] findAll(T, Args...)(Database db, string query, Args args) { + Statement stmt = db.prepare(query); + stmt.bindAll(args); + ResultRange result = stmt.execute(); + return result.map!(row => parseRow!T(row)).array; +} + +Optional!T findOne(T, Args...)(Database db, string query, Args args) { + Statement stmt = db.prepare(query); + stmt.bindAll(args); + ResultRange result = stmt.execute(); + if (result.empty) return Optional!T.empty; + return Optional!T.of(parseRow!T(result.front)); +} + +bool canFind(Args...)(Database db, string query, Args args) { + Statement stmt = db.prepare(query); + stmt.bindAll(args); + return !stmt.execute().empty; +} + +private string toSnakeCase(string camelCase) { + import std.uni; + if (camelCase.length == 0) return camelCase; + auto app = appender!string; + app ~= toLower(camelCase[0]); + for (int i = 1; i < camelCase.length; i++) { + if (isUpper(camelCase[i])) { + app ~= '_'; + app ~= toLower(camelCase[i]); + } else { + app ~= camelCase[i]; + } + } + return app[]; +} + +unittest { + assert(toSnakeCase("testValue") == "test_value"); +} + private string[] getColumnNames(T)() { import std.string : toLower; alias members = __traits(allMembers, T); @@ -38,7 +85,7 @@ private string[] getColumnNames(T)() { static if (__traits(getAttributes, __traits(getMember, T, members[i])).length > 0) { columnNames[i] = toLower(__traits(getAttributes, __traits(getMember, T, members[i]))[0].name); } else { - columnNames[i] = toLower(members[i]); + columnNames[i] = toLower(toSnakeCase(members[i])); } } return columnNames.dup; @@ -61,18 +108,3 @@ T parseRow(T)(Row row) { mixin("T t = T(" ~ getArgsStr!T ~ ");"); return t; } - -T[] findAll(T, Args...)(Database db, string query, Args args) { - Statement stmt = db.prepare(query); - stmt.bindAll(args); - ResultRange result = stmt.execute(); - return result.map!(row => parseRow!T(row)).array; -} - -Optional!T findOne(T, Args...)(Database db, string query, Args args) { - Statement stmt = db.prepare(query); - stmt.bindAll(args); - ResultRange result = stmt.execute(); - if (result.empty) return Optional!T.empty; - return Optional!T.of(parseRow!T(result.front)); -} diff --git a/bruno-api/Auth/Login.bru b/bruno-api/Auth/Login.bru new file mode 100644 index 0000000..1579813 --- /dev/null +++ b/bruno-api/Auth/Login.bru @@ -0,0 +1,11 @@ +meta { + name: Login + type: http + seq: 1 +} + +post { + url: {{base_url}}/auth/login + body: none + auth: inherit +} diff --git a/bruno-api/Classroom Compliance/Create Class.bru b/bruno-api/Classroom Compliance/Create Class.bru new file mode 100644 index 0000000..a4a82a7 --- /dev/null +++ b/bruno-api/Classroom Compliance/Create Class.bru @@ -0,0 +1,18 @@ +meta { + name: Create Class + type: http + seq: 1 +} + +post { + url: {{base_url}}/classroom-compliance/classes + body: json + auth: inherit +} + +body:json { + { + "number": 2, + "schoolYear": "2024-2025" + } +} diff --git a/bruno-api/Classroom Compliance/Create Compliance Entry.bru b/bruno-api/Classroom Compliance/Create Compliance Entry.bru new file mode 100644 index 0000000..6e1a6f0 --- /dev/null +++ b/bruno-api/Classroom Compliance/Create Compliance Entry.bru @@ -0,0 +1,30 @@ +meta { + name: Create Compliance Entry + type: http + seq: 10 +} + +post { + url: {{base_url}}/classroom-compliance/classes/:classId/entries + body: json + auth: inherit +} + +params:path { + classId: {{class_id}} +} + +body:json { + { + "student_id": 1, + "date": "2024-12-16", + "absent": false, + "phone": { + "compliant": true + }, + "behavior": { + "rating": 3, + "comment": "Good job!" + } + } +} diff --git a/bruno-api/Classroom Compliance/Create Student.bru b/bruno-api/Classroom Compliance/Create Student.bru new file mode 100644 index 0000000..f099bc0 --- /dev/null +++ b/bruno-api/Classroom Compliance/Create Student.bru @@ -0,0 +1,21 @@ +meta { + name: Create Student + type: http + seq: 4 +} + +post { + url: {{base_url}}/classroom-compliance/classes/:classId/students + body: json + auth: inherit +} + +params:path { + classId: {{class_id}} +} + +body:json { + { + "name": "John F. Kennedy" + } +} diff --git a/bruno-api/Classroom Compliance/Delete Class.bru b/bruno-api/Classroom Compliance/Delete Class.bru new file mode 100644 index 0000000..87fbfa7 --- /dev/null +++ b/bruno-api/Classroom Compliance/Delete Class.bru @@ -0,0 +1,15 @@ +meta { + name: Delete Class + type: http + seq: 3 +} + +delete { + url: {{base_url}}/classroom-compliance/classes/:classId + body: none + auth: inherit +} + +params:path { + classId: 1 +} diff --git a/bruno-api/Classroom Compliance/Delete Student.bru b/bruno-api/Classroom Compliance/Delete Student.bru new file mode 100644 index 0000000..9a2ac33 --- /dev/null +++ b/bruno-api/Classroom Compliance/Delete Student.bru @@ -0,0 +1,16 @@ +meta { + name: Delete Student + type: http + seq: 7 +} + +delete { + url: {{base_url}}/classroom-compliance/classes/:classId/students/:studentId + body: none + auth: inherit +} + +params:path { + classId: {{class_id}} + studentId: 3 +} diff --git a/bruno-api/Classroom Compliance/Get Classes.bru b/bruno-api/Classroom Compliance/Get Classes.bru new file mode 100644 index 0000000..7e1808c --- /dev/null +++ b/bruno-api/Classroom Compliance/Get Classes.bru @@ -0,0 +1,11 @@ +meta { + name: Get Classes + type: http + seq: 2 +} + +get { + url: {{base_url}}/classroom-compliance/classes + body: none + auth: inherit +} diff --git a/bruno-api/Classroom Compliance/Get Desk Assignments.bru b/bruno-api/Classroom Compliance/Get Desk Assignments.bru new file mode 100644 index 0000000..85d4a7e --- /dev/null +++ b/bruno-api/Classroom Compliance/Get Desk Assignments.bru @@ -0,0 +1,15 @@ +meta { + name: Get Desk Assignments + type: http + seq: 8 +} + +get { + url: {{base_url}}/classroom-compliance/classes/:classId/desk-assignments + body: none + auth: inherit +} + +params:path { + classId: {{class_id}} +} diff --git a/bruno-api/Classroom Compliance/Get Students.bru b/bruno-api/Classroom Compliance/Get Students.bru new file mode 100644 index 0000000..6b8abc5 --- /dev/null +++ b/bruno-api/Classroom Compliance/Get Students.bru @@ -0,0 +1,15 @@ +meta { + name: Get Students + type: http + seq: 5 +} + +get { + url: {{base_url}}/classroom-compliance/classes/:classId/students + body: none + auth: inherit +} + +params:path { + classId: {{class_id}} +} diff --git a/bruno-api/Classroom Compliance/Set Desk Assignments.bru b/bruno-api/Classroom Compliance/Set Desk Assignments.bru new file mode 100644 index 0000000..0571d1c --- /dev/null +++ b/bruno-api/Classroom Compliance/Set Desk Assignments.bru @@ -0,0 +1,30 @@ +meta { + name: Set Desk Assignments + type: http + seq: 9 +} + +post { + url: {{base_url}}/classroom-compliance/classes/:classId/desk-assignments + body: json + auth: inherit +} + +params:path { + classId: {{class_id}} +} + +body:json { + { + "entries": [ + { + "deskNumber": 1, + "studentId": 1 + }, + { + "deskNumber": 2, + "studentId": 2 + } + ] + } +} diff --git a/bruno-api/Classroom Compliance/Update Student.bru b/bruno-api/Classroom Compliance/Update Student.bru new file mode 100644 index 0000000..1f702be --- /dev/null +++ b/bruno-api/Classroom Compliance/Update Student.bru @@ -0,0 +1,22 @@ +meta { + name: Update Student + type: http + seq: 6 +} + +put { + url: {{base_url}}/classroom-compliance/classes/:classId/students/:studentId + body: json + auth: inherit +} + +params:path { + classId: {{class_id}} + studentId: 1 +} + +body:json { + { + "name": "John W. Booth" + } +} diff --git a/bruno-api/Classroom Compliance/folder.bru b/bruno-api/Classroom Compliance/folder.bru new file mode 100644 index 0000000..4dca25f --- /dev/null +++ b/bruno-api/Classroom Compliance/folder.bru @@ -0,0 +1,7 @@ +meta { + name: Classroom Compliance +} + +vars:pre-request { + class_id: 1 +} diff --git a/bruno-api/bruno.json b/bruno-api/bruno.json new file mode 100644 index 0000000..65134d8 --- /dev/null +++ b/bruno-api/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "Teacher-Tools", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/bruno-api/collection.bru b/bruno-api/collection.bru new file mode 100644 index 0000000..68c9f0e --- /dev/null +++ b/bruno-api/collection.bru @@ -0,0 +1,12 @@ +auth { + mode: basic +} + +auth:basic { + username: test + password: test +} + +vars:pre-request { + base_url: http://localhost:8080/api +}