feat: private checklists for zjazd preparation

Per-user checklists with items that may link a course, reading, deadline,
material or own presentation deck. An item with a reading shares the
reading's read mark. Student API routes with token twins, MCP tools,
migrations, tests and README.
This commit is contained in:
Jakub Zych
2026-10-30 12:41:04 +01:00
parent 643f8cbf7c
commit d7cd5baedf
15 changed files with 1262 additions and 21 deletions

View File

@@ -0,0 +1,33 @@
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateChecklists returns the 20261030120000_create_jz_bm_checklists
// gormigrate entry: a user's own checklists, e.g. one per zjazd to plan the
// preparation. They go with the golem15.user user; a deleted zjazd leaves
// the checklist without one.
func CreateChecklists() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261030120000_create_jz_bm_checklists",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_checklists (
id BIGSERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
meeting_id BIGINT NULL REFERENCES jz_bm_meetings (id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
description TEXT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX jz_bm_checklists_user_id_index ON jz_bm_checklists (user_id);
CREATE INDEX jz_bm_checklists_meeting_id_index ON jz_bm_checklists (meeting_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_checklists").Error
},
}
}

View File

@@ -0,0 +1,46 @@
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateChecklistItems returns the
// 20261030120100_create_jz_bm_checklist_items gormigrate entry: the items
// of a checklist. An item may point at a reading, a deadline, a material or
// a course (the link is cleared when that row goes) and name a deck of
// golem15.presentations by slug (no foreign key across plugins). done_at is
// the done mark of an item without a reading; an item with one is done when
// its owner has read the reading.
func CreateChecklistItems() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261030120100_create_jz_bm_checklist_items",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_checklist_items (
id BIGSERIAL PRIMARY KEY,
checklist_id BIGINT NOT NULL REFERENCES jz_bm_checklists (id) ON DELETE CASCADE,
kind VARCHAR(20) NOT NULL DEFAULT 'zadanie'
CHECK (kind IN ('lektura', 'artykul', 'ksiazka', 'prezentacja', 'zadanie', 'inne')),
title VARCHAR(500) NOT NULL,
notes TEXT NULL,
url VARCHAR(2048) NULL,
course_id BIGINT NULL REFERENCES jz_bm_courses (id) ON DELETE SET NULL,
reading_id BIGINT NULL REFERENCES jz_bm_readings (id) ON DELETE SET NULL,
deadline_id BIGINT NULL REFERENCES jz_bm_deadlines (id) ON DELETE SET NULL,
material_id BIGINT NULL REFERENCES jz_bm_materials (id) ON DELETE SET NULL,
presentation_slug VARCHAR(255) NULL,
due_at TIMESTAMPTZ NULL,
estimate_minutes INTEGER NULL CHECK (estimate_minutes >= 0),
sort_order INTEGER NOT NULL DEFAULT 0,
done_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX jz_bm_checklist_items_checklist_id_index ON jz_bm_checklist_items (checklist_id, sort_order);
CREATE INDEX jz_bm_checklist_items_reading_id_index ON jz_bm_checklist_items (reading_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_checklist_items").Error
},
}
}