commit 2f7c19f28e6fcc9ff38ccd1cd1b420ccb1c465b7 Author: sfja Date: Tue Jul 1 23:06:08 2025 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4332853 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +data/* +mlread + diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/.gitkeep @@ -0,0 +1 @@ + diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..d2906cb --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,9 @@ +{ + "fmt": { + "indentWidth": 4 + }, + "tasks": { + "run": "deno run --allow-net --allow-env=PORT --allow-read=./public,./data --allow-write=./public,./data main.ts", + "compile": "deno compile --allow-net --allow-env=PORT --allow-read=./public,./data --allow-write=./public,./data --output mlread main.ts" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..8d5b4f5 --- /dev/null +++ b/deno.lock @@ -0,0 +1,80 @@ +{ + "version": "4", + "specifiers": { + "jsr:@oak/commons@1": "1.0.0", + "jsr:@oak/oak@*": "17.1.3", + "jsr:@std/assert@1": "1.0.8", + "jsr:@std/bytes@1": "1.0.4", + "jsr:@std/bytes@^1.0.2": "1.0.4", + "jsr:@std/crypto@1": "1.0.3", + "jsr:@std/encoding@1": "1.0.5", + "jsr:@std/encoding@^1.0.5": "1.0.5", + "jsr:@std/http@1": "1.0.10", + "jsr:@std/io@0.224": "0.224.9", + "jsr:@std/media-types@1": "1.1.0", + "jsr:@std/path@1": "1.0.8", + "npm:path-to-regexp@6.2.1": "6.2.1" + }, + "jsr": { + "@oak/commons@1.0.0": { + "integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac", + "dependencies": [ + "jsr:@std/assert", + "jsr:@std/bytes@1", + "jsr:@std/crypto", + "jsr:@std/encoding@1", + "jsr:@std/http", + "jsr:@std/media-types" + ] + }, + "@oak/oak@17.1.3": { + "integrity": "d89296c22db91681dd3a2a1e1fd14e258d0d5a9654de55637aee5b661c159f33", + "dependencies": [ + "jsr:@oak/commons", + "jsr:@std/assert", + "jsr:@std/bytes@1", + "jsr:@std/crypto", + "jsr:@std/http", + "jsr:@std/io", + "jsr:@std/media-types", + "jsr:@std/path", + "npm:path-to-regexp" + ] + }, + "@std/assert@1.0.8": { + "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b" + }, + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" + }, + "@std/crypto@1.0.3": { + "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + }, + "@std/http@1.0.10": { + "integrity": "4e32d11493ab04e3ef09f104f0cb9beb4228b1d4b47c5469573c2c294c0d3692", + "dependencies": [ + "jsr:@std/encoding@^1.0.5" + ] + }, + "@std/io@0.224.9": { + "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + } + }, + "npm": { + "path-to-regexp@6.2.1": { + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + } + } +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..6ca41d1 --- /dev/null +++ b/main.ts @@ -0,0 +1,50 @@ +import * as oak from "jsr:@oak/oak"; + +const port = Number(Deno.env.get("PORT") ?? 8000); + +const router = new oak.Router(); + +router.post("/api/log", async (ctx) => { + const data = await ctx.request.body.json(); + + const enc = new TextEncoder(); + + const postLog = await Deno.open("./data/log.txt", { + append: true, + create: true, + }); + postLog.write( + enc.encode(`${new Date().toISOString()}\t${JSON.stringify(data)}\n`), + ); + + ctx.response.body = { ok: true }; + ctx.respond = true; +}); + +router.get("/api/log", async (ctx) => { + const postLog = await Deno.readTextFile("./data/log.txt"); + const entries = postLog + .split("\n") + .filter((l) => l) + .map((entry) => entry.split("\t")) + .map(([timestamp, data]) => (console.log(timestamp, data), { + timestamp: new Date(timestamp), + data: JSON.parse(data), + })); + ctx.response.body = entries; + ctx.respond = true; +}); + +console.log(`listening at http://localhost:${port}/`); + +new oak.Application() + .use(router.routes()) + .use(router.allowedMethods()) + .use(async (ctx) => { + const { request: { url: { pathname } } } = ctx; + await oak.send(ctx, pathname, { + root: "./public", + index: "index.html", + }); + }) + .listen({ port }); diff --git a/mlread.service b/mlread.service new file mode 100644 index 0000000..621f157 --- /dev/null +++ b/mlread.service @@ -0,0 +1,14 @@ +[Unit] +Description=mlread webservice + +[Service] +Type=simple +User=simone +ExecStart=/home/simone/mlread/mlread +WorkingDirectory=/home/simone/mlread/ +Restart=on-failure +Environment="PORT=8300" + +[Install] +WantedBy=default.target + diff --git a/public/deno.jsonc b/public/deno.jsonc new file mode 100644 index 0000000..b81e777 --- /dev/null +++ b/public/deno.jsonc @@ -0,0 +1,9 @@ +{ + "fmt": { + "indentWidth": 4 + }, + "compilerOptions": { + "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"] + }, + +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3dfef97 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..18f9ac3 --- /dev/null +++ b/public/index.html @@ -0,0 +1,56 @@ + + + + + + philopsher's ml reading + + + + +
+

philopsher's ml reading group

+

The plan is to read and discuss texts from the ML test materials in the #test-materials channels in FinBol's. (Link)

+

The Three Sources and Three Component Parts of Marxism by Vladimir Lenin

+

To be scheduled...

+

This text is available both as text at MIA and as audiobook by Socialism For All.

+

The work can be read in 20 minutes. The plan is to start the event 20 minutes before the announced time, to give people time to read the work, if they haven't already. Then at the announced time, the presentation/discussion will start. I will go through what I think are the main points, and we will use this presentation as foundation for discussion.

+

I may put some notes here afterwards...

+

Scheduler

+
+

+ Select as many options as possible. I will try and find the + best timeslot. +

+

+ Timestamps are UTC, which is currently 00:00. Compensate according to your own timezone. +

+
+
+ + +
loading...
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ + + diff --git a/public/index.js b/public/index.js new file mode 100644 index 0000000..8e3511e --- /dev/null +++ b/public/index.js @@ -0,0 +1,108 @@ +const timeUtcSpan = document.querySelector("#time-utc"); +const scheduleTable = document.querySelector("#schedule-table"); +const scheduleForm = document.querySelector("#schedule-form"); + +timeUtcSpan.innerText = new Date() + .toUTCString() + .slice(17, 22); +// .toLocaleTimeString(["en-UK"], { hour: "2-digit", minute: "2-digit" }); + +const weekDayNames = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +]; + +const monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +async function makeScheduleTable() { + const timeslotData = await fetch("timeslots.json") + .then((res) => res.json()); + + const timeslots = timeslotData + .map(({ date, times }) => ({ + dateString: date, + date: new Date(date), + times, + })) + .toSorted((a, b) => a.date.getTime() - b.date.getTime()); + + let tableHtml = ` + ${ + timeslots.map((ts) => + `${weekDayNames[ts.date.getUTCDay()]} ${ts.date.getUTCDay()}. ${ + monthNames[ts.date.getUTCMonth()] + }` + ) + .join("") + } + `; + + tableHtml += ""; + for (const timeslot of timeslots) { + tableHtml += ""; + for (const time of timeslot.times) { + const name = `${timeslot.dateString}-${time}`; + tableHtml += ` +
+ + +
+ `; + } + tableHtml += ""; + } + tableHtml += ""; + + scheduleTable.innerHTML = tableHtml; +} + +scheduleForm.onsubmit = async (ev) => { + ev.preventDefault(); + const formdata = new FormData(ev.target); + const data = formdata.entries().reduce( + (acc, [key, val]) => ({ ...acc, [key]: val }), + {}, + ); + if (!data.username) { + alert("please specify username"); + return; + } + if (!data.tos) { + alert("please check information agreement"); + return; + } + console.log(data); + + const response = await fetch("/api/log", { + method: "post", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res.json()); + + if (response.ok) { + alert("submitted!"); + } else { + alert("error occured!"); + console.log(response); + } +}; + +makeScheduleTable(); diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..0d8f512 --- /dev/null +++ b/public/style.css @@ -0,0 +1,69 @@ +*, *::before, *::after { + box-sizing: border-box; +} + +:root { + color-scheme: light dark; +} + +body { + margin: 0 auto; + padding: 2rem; + max-width: 1000px; + line-height: 1.6em; +} + +li { + margin-top: 1ex; +} + +table { + border-collapse: collapse; +} + +table td, table th { + border: 1px solid; + padding: 4px; +} + +#scheduler { + border: 1px solid; + padding: 10px; +} + +#scheduler form { + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 10px; +} + +#scheduler form #tail { + width: 50%; + display: flex; + flex-direction: column; + justify-content: start; + align-items: start; + gap: 10px; +} + +#scheduler #submit-div { + width: 100%; + text-align: center; +} + +#scheduler form input[type="submit"] { + width: 30%; + padding: 5px; +} + +#schedule-table-scroller { + width: 100%; + overflow: scroll; + padding-bottom: 20px; +} + +#schedule-table { + width: max-content; +} diff --git a/public/timeslots.json b/public/timeslots.json new file mode 100644 index 0000000..da38a7f --- /dev/null +++ b/public/timeslots.json @@ -0,0 +1,21 @@ +[ + { "date": "7-1-2025", "times": ["18:00", "21:00"] }, + { "date": "7-2-2025", "times": ["18:00", "21:00"] }, + { "date": "7-3-2025", "times": ["18:00", "21:00"] }, + { "date": "7-4-2025", "times": ["18:00", "21:00"] }, + { "date": "7-5-2025", "times": [] }, + { "date": "7-6-2025", "times": ["13:00", "15:00", "18:00", "21:00"] }, + { "date": "7-7-2025", "times": ["18:00", "21:00"] }, + { "date": "7-8-2025", "times": ["18:00", "21:00"] }, + { "date": "7-9-2025", "times": ["18:00", "21:00"] }, + { "date": "7-10-2025", "times": ["18:00", "21:00"] }, + { "date": "7-11-2025", "times": [] }, + { + "date": "7-12-2025", + "times": ["13:00", "15:00", "18:00", "21:00", "23:00"] + }, + { "date": "7-13-2025", "times": ["13:00", "15:00", "18:00", "21:00"] }, + { "date": "7-14-2025", "times": ["18:00", "21:00"] }, + { "date": "7-15-2025", "times": ["18:00", "21:00"] }, + { "date": "7-16-2025", "times": ["18:00", "21:00"] } +]