Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ jobs:
- name: Join-class ref tests
run: npm run test:join-class

- name: Path / task-ref guard tests
run: npm run test:paths

- name: Build
run: npm run build
15 changes: 15 additions & 0 deletions scripts/test-paths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
bareClassIdFromEnrollment,
flatClassPath,
flatTaskPath,
isValidTaskRouteRef,
} from "../src/common/paths.ts";

let failed = 0;
Expand Down Expand Up @@ -130,6 +131,20 @@ assertEq(
"writeTaskIds legacy local path"
);

console.log("\n--- isValidTaskRouteRef (EditTask/NotesTask guard) ---\n");

assert(isValidTaskRouteRef("abc123~task9"), "accepts canonical classId~taskId");
assert(isValidTaskRouteRef("abc123/task9"), "accepts classId/taskId slash form");
assert(isValidTaskRouteRef("t~abc123~task9", org), "accepts legacy local~classId~taskId");
assert(isValidTaskRouteRef("t@mvla.net/abc123/task9", org), "accepts legacy email/classId/taskId");
assert(!isValidTaskRouteRef(""), "rejects empty");
assert(!isValidTaskRouteRef(null), "rejects null");
assert(!isValidTaskRouteRef(" "), "rejects whitespace");
assert(!isValidTaskRouteRef("abc123"), "rejects class-only (one segment)");
assert(!isValidTaskRouteRef("t@mvla.net/abc123"), "rejects email/classId (class, not task)");
assert(!isValidTaskRouteRef("a~b~c~d"), "rejects four-segment refs");
assert(!isValidTaskRouteRef("classId~"), "rejects trailing empty task id");

console.log("\n--- done ---\n");
if (failed) {
console.error(`${failed} assertion(s) failed`);
Expand Down
16 changes: 16 additions & 0 deletions src/common/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ export function parseTaskId(
return null;
}

/**
* True when `idOrRef` is a loadable task route ref:
* `classId~taskId` | `classId/taskId` | `local~classId~taskId` | `email/classId/taskId`.
* Rejects empty, class-only, email/classId, and extra segments.
* Used by EditTask / NotesTask so the router canonical 2-segment flat refs pass.
*/
export function isValidTaskRouteRef(
idOrRef: string | undefined | null,
orgDomain: string = "mvla.net"
): boolean {
if (!idOrRef || typeof idOrRef !== "string") return false;
const trimmed = idOrRef.trim();
if (!trimmed) return false;
return parseTaskId(trimmed, orgDomain) != null;
}

/**
* Ambiguous 2-segment refs: local~classId (class) vs classId~taskId (task).
* Returns both interpretations; callers dual-read class-with-email first, then task.
Expand Down
13 changes: 9 additions & 4 deletions src/views/Portal/EditTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
*/

import { compatDateObj } from "@/common";
import { isValidTaskRouteRef } from "@/common/paths";
import { ErrorToast, WarningToast } from "@svonk/util";
import smoothReflow from "vue-smooth-reflow";
import OverlayWrapper from "@/components/Modal/OverlayWrapper.vue";
Expand Down Expand Up @@ -292,15 +293,19 @@ export default {
});
},
async get_task() {
// get task ref from route params
const ref = this.$route.params.ref.split("~").join("/");
if (!ref) {
// get task ref from route params (canonical flat form is classId~taskId)
const raw = this.$route.params.ref;
if (!raw) {
new WarningToast("No task specified", 1500);
this.$emit("close");
} else if (ref.split("/").length < 3) {
return;
}
if (!isValidTaskRouteRef(raw)) {
new WarningToast("Invalid task specified", 1500);
this.$emit("close");
return;
}
const ref = String(raw).split("~").join("/");
// get task from store
this.$store
.task_from_ref(ref)
Expand Down
13 changes: 9 additions & 4 deletions src/views/Portal/NotesTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
* @emits {Function} close - An event emitted when the task is created or the modal is closed.
*/

import { isValidTaskRouteRef } from "@/common/paths";
import { ErrorToast, WarningToast, SuccessToast } from "@svonk/util";
import smoothReflow from "vue-smooth-reflow";

Expand Down Expand Up @@ -162,15 +163,19 @@ export default {
});
},
async get_task() {
// get task ref from route params
const ref = this.$route.params.ref.split("~").join("/");
if (!ref) {
// get task ref from route params (canonical flat form is classId~taskId)
const raw = this.$route.params.ref;
if (!raw) {
new WarningToast("No task specified", 1500);
this.$emit("close");
} else if (ref.split("/").length < 3) {
return;
}
if (!isValidTaskRouteRef(raw)) {
new WarningToast("Invalid task specified", 1500);
this.$emit("close");
return;
}
const ref = String(raw).split("~").join("/");
// get task from store
this.$store
.task_from_ref(ref)
Expand Down
Loading