aboutsummaryrefslogtreecommitdiff
path: root/src/database-prod.ts
blob: 6712adbb3a7e83416cd8605938d11e93ab21602b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import { assert } from "./assert.ts";
import { getCourseId, getSesskey } from "./utils-ENV.js";

const DB_TEMPLATE_LIST_HEADER = `
<table class="table table-striped">
  <thead>
    <tr>
      <th>ChangeId</th>
      <th>Date</th>
      <th>Coach</th>
      <th>Json</th>
    </tr>
  </thead>
  <tbody>
`;
const DB_TEMPLATE_LIST_ENTRY = `
<tr class="db-row">
  <td class="db-cell" data-key="changeId">##id##</td>
  <td class="db-cell" data-key="date">##timeadded##</td>
  <td class="db-cell" data-key="coach">##user##</td>
  <td class="db-cell" data-key="json">[[Json]]</td>
</tr>
`;
const DB_TEMPLATE_LIST_FOOTER = `
 </tbody>
</table>
`;

let graduationDb = findGraduationDb();

const ID_QUERY_PARAM_REGEX = /[?&]id=(\d+)/;

async function findGraduationDb() {
	let graduationDbId = localStorage.getItem(`gruaduationDbId-${getCourseId()}`);
	if (graduationDbId !== null) {
		return new DB(Number(graduationDbId));
	}

	const courseUrl = `https://lernplattform.bycs.de/course/view.php?id=${getCourseId()}`;
	const courseRes = await fetch(courseUrl);
	const courseDoc = new DOMParser().parseFromString(await courseRes.text(), "text/html");

	for (const a of courseDoc.querySelectorAll("div.activityname a.aalink")) {
		if (a.textContent.trim() === "Graduation  Datenbank") {
			const anchor = a as HTMLAnchorElement;
			const match = anchor.href.match(ID_QUERY_PARAM_REGEX);
			if (match === null) continue;
			const dbId = match[1];
			localStorage.setItem("graduationDbId", dbId);
			return new DB(Number(dbId));
		}
	}
	assert(false, "Graduation DB not found");
}

interface DatabaseParams {
	[key: string]: string;
}

interface DatabaseEntry {
	[key: string]: string;
}

class DB {
	id: number;
	d: Promise<number> | null = null;

	constructor(id: number, d?: number) {
		this.id = id;
		if (typeof d !== "undefined") {
			this.d = new Promise((resolve) => {
				resolve(d);
			});
		}
	}

	async fetchEntries(params?: DatabaseParams) {
		return loadFromDatabaseActivity(this, params);
	}

	async saveEntry(json: string) {
		return addEntryToDatabaseActivity(this, json);
	}

	async deleteEntries(entryIds: [number]) {
		return deleteFromDatabaseActivity(await this.getD(), entryIds);
	}

	async getD() {
		if (this.d === null) this.d = fetchDFromDatabaseActivity(this);
		return this.d;
	}
}

const dRegex = /d=(\d+)/;
function getDFromDbDoc(db: DB, dbDoc: Document) {
	const urlWithD = dbDoc.querySelector<HTMLInputElement>('input[name="pageurl"]')!.value;
	const match = dRegex.exec(urlWithD);
	if (match === null) {
		console.error(`d of database ${db.id} not found`);
		return -1;
	}
	return Number(match[1]);
}

async function fetchDFromDatabaseActivity(db: DB): Promise<number> {
	let dbUrl = `${window.location.origin}/mod/data/view.php?id=${db.id}`;
	const dbRes = await fetch(encodeURI(dbUrl));
	const dbDoc = new DOMParser().parseFromString(await dbRes.text(), "text/html");

	return getDFromDbDoc(db, dbDoc);
}

async function loadFromDatabaseActivity(db: DB, params?: DatabaseParams) {
	let dbUrl = `${window.location.origin}/mod/data/view.php?id=${db.id}&mode=list&perpage=1000`;
	// TODO(muhq): Why does this not include entries?
	if (typeof params !== "undefined") {
		dbUrl += "&filter=1&advanced=1";
		for (const param in params) {
			dbUrl += `&${param}=${encodeURIComponent(params[param])}`;
		}
	}

	const dbRes = await fetch(encodeURI(dbUrl));
	const dbDoc = new DOMParser().parseFromString(await dbRes.text(), "text/html");

	if (db.d === null) {
		db.d = new Promise((resolve) => {
			resolve(getDFromDbDoc(db, dbDoc));
		});
	}

	let data: any[] = [];
	dbDoc.querySelectorAll(".db-row").forEach((row) => {
		let entry: DatabaseEntry = {};
		row.querySelectorAll<HTMLElement>(".db-cell").forEach((field) => {
			const key = field.dataset.key!;
			let value;
			// Moodle includes the full date in the title attribute of the included span
			if (key === "date") {
				value = field.querySelector("span")?.title;
			} else {
				value = field.innerText.trim();
			}
			entry[key] = value!;
		});
		data.push(entry);
	});
	return data;
}

async function getGraduationDb() {
	return (await graduationDb)!;
}

async function addEntryToDatabaseActivity(db: DB, json: string) {
	// TODO(muhq): do not fetch the form
	const url = `/mod/data/edit.php?id=${db.id}`;
	const editRes = await fetch(url);
	const editDoc = new DOMParser().parseFromString(await editRes.text(), "text/html");
	const form = editDoc.querySelector<HTMLFormElement>('div[role="main"] > form');

	const formData = new FormData(form!);

	for (const key of formData.keys()) {
		if (key.startsWith("field_")) {
			formData.set(key, json);
			// We found our json key
			break;
		}
	}
	formData.set("saveandview", "Speichern");

	return await fetch("/mod/data/edit.php", {
		method: "POST",
		body: formData,
	});
}

async function deleteFromDatabaseActivity(d: number, entryIds: [number]) {
	const promises = [];
	assert(d != -1, "DB has no valid d yet.");
	for (const entryId of entryIds) {
		const formData = new FormData();

		formData.set("d", String(d));
		formData.set("sesskey", getSesskey());
		formData.set("confirm", "1");
		formData.set("delete", String(entryId));

		promises.push(
			fetch("/mod/data/view.php", {
				method: "POST",
				body: formData,
			}),
		);
	}
	return Promise.all(promises);
}

export { DB, DatabaseEntry, DatabaseParams, getGraduationDb };