From 9b8a0ef0d666394823ecd19e02a7ccf5fc5dcee1 Mon Sep 17 00:00:00 2001 From: Saman Date: Thu, 10 Apr 2025 02:43:35 -0500 Subject: [PATCH 1/9] added routes and types for assignments --- src/api.ts | 116 ++++++++++++++++++++++++++++++ src/types/Assignment.ts | 5 ++ src/types/AssignmentId.ts | 3 + src/types/CreateAssignmentData.ts | 4 ++ src/types/CreateSubmissionData.ts | 3 + src/types/Submission.ts | 5 ++ src/types/SubmissionId.ts | 3 + src/types/UpdateAssignmentData.ts | 3 + 8 files changed, 142 insertions(+) create mode 100644 src/types/Assignment.ts create mode 100644 src/types/AssignmentId.ts create mode 100644 src/types/CreateAssignmentData.ts create mode 100644 src/types/CreateSubmissionData.ts create mode 100644 src/types/Submission.ts create mode 100644 src/types/SubmissionId.ts create mode 100644 src/types/UpdateAssignmentData.ts diff --git a/src/api.ts b/src/api.ts index 4682d3b..ac35eba 100644 --- a/src/api.ts +++ b/src/api.ts @@ -42,6 +42,13 @@ import { UpdateGroupData } from "./types/UpdateGroupData"; import { UpdateProjectData } from "./types/UpdateProjectData"; import { UpdateRoleData } from "./types/UpdateRoleData"; import { User } from "./types/User"; +import { AssignmentId } from "./types/AssignmentId"; +import { CreateAssignmentData } from "./types/CreateAssignmentData"; +import { Assignment } from "./types/Assignment"; +import { SubmissionId } from "./types/SubmissionId"; +import { CreateSubmissionData } from "./types/CreateSubmissionData"; +import { UpdateAssignmentData } from "./types/UpdateAssignmentData"; +import { Submission } from "./types/Submission"; export default class NetsBloxApi { private baseUrl: string; @@ -237,6 +244,115 @@ export default class NetsBloxApi { return await this.fetchJson(`/groups/id/${encodeURIComponent(id)}/members`); } + async createAssignment( + id: GroupId, + data: CreateAssignmentData, + ): Promise { + return await this.post( + `groups/id/${encodeURIComponent(id)}/assignments/`, + data, + ); + } + + async listGroupAssignments(id: GroupId): Promise { + return await this.fetchJson( + `groups/id/${encodeURIComponent(id)}/assignments/`, + ); + } + + async viewAssignment( + group_id: GroupId, + id: AssignmentId, + ): Promise { + return await this.fetchJson( + `groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, + ); + } + + async editAssignment( + group_id: GroupId, + id: AssignmentId, + data: UpdateAssignmentData, + ): Promise { + const opts = { + method: "patch", + body: JSON.stringify(data), + }; + return await this.fetchJson( + `groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, + opts, + ); + } + + async deleteAssignment( + group_id: GroupId, + id: AssignmentId, + ): Promise { + const opts = { method: "delete" }; + return await this.fetchJson( + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(id)}/`, + opts, + ); + } + + async createSubmission( + group_id: GroupId, + id: AssignmentId, + data: CreateSubmissionData, + ): Promise { + return await this.post( + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(id)}/submissions/`, + data, + ); + } + + async viewSubmission(group_id: GroupId, assignment_id: AssignmentId, id: SubmissionId): Promise { + return await this.fetchJson( + `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, + ); + } + + async viewAssignmentSubmissions( + group_id: GroupId, + assignment_id: AssignmentId, + ): Promise { + return await this.fetchJson( + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/`, + ); + } + + async viewUserSubmissions( + group_id: GroupId, + assignment_id: AssignmentId, + username: string, + ): Promise { + return await this.fetchJson( + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/user/${encodeURIComponent(username)}/`, + ); + } + + async viewSubmissionXml( + group_id: GroupId, + assignment_id: AssignmentId, + id: SubmissionId, + ): Promise { + return await this.fetchJson( + `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/xml/`, + ); + } + + async deleteSubmission( + group_id: GroupId, + assignment_id: AssignmentId, + id: SubmissionId, + ): Promise { + const opts = { method: "delete" }; + return await this.fetchJson( + `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, + opts, + ); + } + ////////////////////////////// Projects ////////////////////////////// async createProject(data: CreateProjectData): Promise { const opts = { diff --git a/src/types/Assignment.ts b/src/types/Assignment.ts new file mode 100644 index 0000000..05751aa --- /dev/null +++ b/src/types/Assignment.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AssignmentId } from "./AssignmentId"; +import type { GroupId } from "./GroupId"; + +export interface Assignment { id: AssignmentId, groupId: GroupId, name: string, originTime: any, dueDate: any, } \ No newline at end of file diff --git a/src/types/AssignmentId.ts b/src/types/AssignmentId.ts new file mode 100644 index 0000000..e517cce --- /dev/null +++ b/src/types/AssignmentId.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AssignmentId = string; \ No newline at end of file diff --git a/src/types/CreateAssignmentData.ts b/src/types/CreateAssignmentData.ts new file mode 100644 index 0000000..f84e69c --- /dev/null +++ b/src/types/CreateAssignmentData.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { GroupId } from "./GroupId"; + +export interface CreateAssignmentData { name: string, dueDate: any, } diff --git a/src/types/CreateSubmissionData.ts b/src/types/CreateSubmissionData.ts new file mode 100644 index 0000000..08451b8 --- /dev/null +++ b/src/types/CreateSubmissionData.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface CreateSubmissionData { owner: string, xml: string, } \ No newline at end of file diff --git a/src/types/Submission.ts b/src/types/Submission.ts new file mode 100644 index 0000000..558da7f --- /dev/null +++ b/src/types/Submission.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AssignmentId } from "./AssignmentId"; +import type { SubmissionId } from "./SubmissionId"; + +export interface Submission { id: SubmissionId, assignmentId: AssignmentId, owner: string, originTime: any, } \ No newline at end of file diff --git a/src/types/SubmissionId.ts b/src/types/SubmissionId.ts new file mode 100644 index 0000000..82d39aa --- /dev/null +++ b/src/types/SubmissionId.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SubmissionId = string; \ No newline at end of file diff --git a/src/types/UpdateAssignmentData.ts b/src/types/UpdateAssignmentData.ts new file mode 100644 index 0000000..0eab52d --- /dev/null +++ b/src/types/UpdateAssignmentData.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface UpdateAssignmentData { name: string | null, dueDate?: any, } \ No newline at end of file From 6356b59f6709e2350cf127919fe0272e24c17017 Mon Sep 17 00:00:00 2001 From: Saman Date: Mon, 28 Apr 2025 12:46:25 -0500 Subject: [PATCH 2/9] add groupId field to cloud api --- src/client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client.ts b/src/client.ts index 14eced3..472469f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -49,6 +49,7 @@ export default class Cloud { username: string; projectId: string | null; roleId: string | null; + groupId: string | null; newProjectRequest: Promise | undefined; localize: (text: string) => string; token: string | null; @@ -59,6 +60,7 @@ export default class Cloud { this.username = username; this.projectId = null; this.roleId = null; + this.groupId = null; this.url = url; this.token = null; // only needed in NodeJs this.localize = localize; @@ -67,6 +69,7 @@ export default class Cloud { clear() { this.username = null; + this.groupId = null; this.token = null; } @@ -96,6 +99,7 @@ export default class Cloud { const response = await this.post("/users/login", body); const user = await response.json(); this.username = user.username; + this.groupId = user.groupId; if (isNodeJs) { const cookie = response.headers.get("set-cookie"); if (!cookie) throw new CloudError("No cookie received"); From 37873c0ce7c99d72af9408a9f70ea489f9b1b649 Mon Sep 17 00:00:00 2001 From: Saman Date: Mon, 28 Apr 2025 18:03:17 -0500 Subject: [PATCH 3/9] update groupId on construction --- src/client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 472469f..8cafbdb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -65,7 +65,9 @@ export default class Cloud { this.token = null; // only needed in NodeJs this.localize = localize; this.api = new NetsBloxApi(this.url); - } + + this.viewUser(username).then((res) => this.groupId = res.groupId).catch(() => null) // Load groupId asyncronously +} clear() { this.username = null; From 30425d0c23846572726be695182e5a96059de07b Mon Sep 17 00:00:00 2001 From: Saman Date: Mon, 28 Apr 2025 18:29:49 -0500 Subject: [PATCH 4/9] hotfix --- src/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 8cafbdb..8e7c96f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -65,8 +65,7 @@ export default class Cloud { this.token = null; // only needed in NodeJs this.localize = localize; this.api = new NetsBloxApi(this.url); - - this.viewUser(username).then((res) => this.groupId = res.groupId).catch(() => null) // Load groupId asyncronously + this.viewUser(username).then((res) => {this.groupId = res.groupId}) // Load groupId asyncronously } clear() { From 8cbd9141f477f83221c2fd8800b03ba49cd61b17 Mon Sep 17 00:00:00 2001 From: Saman Date: Tue, 29 Apr 2025 14:43:55 -0500 Subject: [PATCH 5/9] add save submission method to client / hotfix api fetch --- src/api.ts | 14 +++++++------- src/client.ts | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/api.ts b/src/api.ts index ac35eba..47da4a6 100644 --- a/src/api.ts +++ b/src/api.ts @@ -249,14 +249,14 @@ export default class NetsBloxApi { data: CreateAssignmentData, ): Promise { return await this.post( - `groups/id/${encodeURIComponent(id)}/assignments/`, + `/groups/id/${encodeURIComponent(id)}/assignments/`, data, ); } async listGroupAssignments(id: GroupId): Promise { return await this.fetchJson( - `groups/id/${encodeURIComponent(id)}/assignments/`, + `/groups/id/${encodeURIComponent(id)}/assignments/`, ); } @@ -265,7 +265,7 @@ export default class NetsBloxApi { id: AssignmentId, ): Promise { return await this.fetchJson( - `groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, ); } @@ -279,7 +279,7 @@ export default class NetsBloxApi { body: JSON.stringify(data), }; return await this.fetchJson( - `groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${id}/`, opts, ); } @@ -308,7 +308,7 @@ export default class NetsBloxApi { async viewSubmission(group_id: GroupId, assignment_id: AssignmentId, id: SubmissionId): Promise { return await this.fetchJson( - `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, ); } @@ -337,7 +337,7 @@ export default class NetsBloxApi { id: SubmissionId, ): Promise { return await this.fetchJson( - `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/xml/`, + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/xml/`, ); } @@ -348,7 +348,7 @@ export default class NetsBloxApi { ): Promise { const opts = { method: "delete" }; return await this.fetchJson( - `groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/`, opts, ); } diff --git a/src/client.ts b/src/client.ts index 8e7c96f..2ba2e17 100644 --- a/src/client.ts +++ b/src/client.ts @@ -682,6 +682,15 @@ export default class Cloud { await this.post(`/libraries/user/${this.username}/${name}/unpublish`); } + async saveSubmission(assignmentId, xml) { + const body = { owner: this.username, xml: xml }; + const response = await this.post( + `/groups/id/${encodeURIComponent(this.groupId)}/assignments/id/${encodeURIComponent(assignmentId)}/submissions/`, + body, + ); + return await response.json(); + } + // Cloud: user messages (to be overridden) message(string) { From 0472f83aecab35d1590fb5fbcb5f73f07c4179bf Mon Sep 17 00:00:00 2001 From: Saman Date: Wed, 30 Apr 2025 06:55:25 -0500 Subject: [PATCH 6/9] add listGroupAssignments to client | hotfix submissionxml --- src/api.ts | 2 +- src/client.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api.ts b/src/api.ts index 47da4a6..0176120 100644 --- a/src/api.ts +++ b/src/api.ts @@ -336,7 +336,7 @@ export default class NetsBloxApi { assignment_id: AssignmentId, id: SubmissionId, ): Promise { - return await this.fetchJson( + return await this.fetchText( `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/xml/`, ); } diff --git a/src/client.ts b/src/client.ts index 2ba2e17..971c335 100644 --- a/src/client.ts +++ b/src/client.ts @@ -682,6 +682,11 @@ export default class Cloud { await this.post(`/libraries/user/${this.username}/${name}/unpublish`); } + async listGroupAssignments() { + const response = await this.fetch( `/groups/id/${encodeURIComponent(this.groupId)}/assignments/`, ); + return await response.json(); + } + async saveSubmission(assignmentId, xml) { const body = { owner: this.username, xml: xml }; const response = await this.post( From b867e6ba07a579c53e0992acadf82c09407f0726 Mon Sep 17 00:00:00 2001 From: Saman Date: Wed, 30 Apr 2025 08:26:27 -0500 Subject: [PATCH 7/9] add viewSubmissionXml to client --- src/client.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client.ts b/src/client.ts index 971c335..f977357 100644 --- a/src/client.ts +++ b/src/client.ts @@ -696,6 +696,13 @@ export default class Cloud { return await response.json(); } + async viewSubmissionXml( group_id, assignment_id, id) { + const response = await this.fetch( + `/groups/id/${encodeURIComponent(group_id)}/assignments/id/${encodeURIComponent(assignment_id)}/submissions/id/${encodeURIComponent(id)}/xml/`, + ); + return await response.text(); + } + // Cloud: user messages (to be overridden) message(string) { From e876c48ebc787df43b68eab7822f22bf7229a0fd Mon Sep 17 00:00:00 2001 From: Saman Date: Wed, 14 May 2025 13:49:40 -0500 Subject: [PATCH 8/9] hotfix check for empty username --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index f977357..df2d3c7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -65,7 +65,7 @@ export default class Cloud { this.token = null; // only needed in NodeJs this.localize = localize; this.api = new NetsBloxApi(this.url); - this.viewUser(username).then((res) => {this.groupId = res.groupId}) // Load groupId asyncronously + if (username) this.viewUser(username).then((res) => {this.groupId = res.groupId}) // Load groupId asynchronously } clear() { From f094e7988f7b6da429de801666bc84dece01f106 Mon Sep 17 00:00:00 2001 From: Saman Date: Thu, 15 May 2025 01:57:43 -0500 Subject: [PATCH 9/9] refactor: fix racey cloud client initialization by moving fetch upstream --- src/client.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index df2d3c7..7fcbb2b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -55,17 +55,16 @@ export default class Cloud { token: string | null; api: NetsBloxApi; - constructor(url, clientId, username, localize = defaultLocalizer) { + constructor(url, clientId, username, groupId, localize = defaultLocalizer) { this.clientId = clientId; this.username = username; this.projectId = null; this.roleId = null; - this.groupId = null; + this.groupId = groupId; this.url = url; this.token = null; // only needed in NodeJs this.localize = localize; this.api = new NetsBloxApi(this.url); - if (username) this.viewUser(username).then((res) => {this.groupId = res.groupId}) // Load groupId asynchronously } clear() {