From 2f7c19f28e6fcc9ff38ccd1cd1b420ccb1c465b7 Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 1 Jul 2025 23:06:08 +0200 Subject: [PATCH] init --- .gitignore | 3 ++ data/.gitkeep | 1 + deno.jsonc | 9 ++++ deno.lock | 80 +++++++++++++++++++++++++++++++ main.ts | 50 +++++++++++++++++++ mlread.service | 14 ++++++ public/deno.jsonc | 9 ++++ public/favicon.ico | Bin 0 -> 13054 bytes public/index.html | 56 ++++++++++++++++++++++ public/index.js | 108 ++++++++++++++++++++++++++++++++++++++++++ public/style.css | 69 +++++++++++++++++++++++++++ public/timeslots.json | 21 ++++++++ 12 files changed, 420 insertions(+) create mode 100644 .gitignore create mode 100644 data/.gitkeep create mode 100644 deno.jsonc create mode 100644 deno.lock create mode 100644 main.ts create mode 100644 mlread.service create mode 100644 public/deno.jsonc create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 public/index.js create mode 100644 public/style.css create mode 100644 public/timeslots.json 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 0000000000000000000000000000000000000000..3dfef976d1338c462a277b1238948c6ab98d06eb GIT binary patch literal 13054 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 + + + + + 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"] } +]