From 08b69851cdf04b16d63a99db468412ce8265fd96 Mon Sep 17 00:00:00 2001 From: Antoine Pelletier Date: Tue, 29 Sep 2026 08:57:04 +0200 Subject: [PATCH] wip --- .gitignore | 1 + Cargo.lock | 60 +- Cargo.toml | 9 +- README.md | 4 +- config.example.yml | 14 + db/migrations/20260928150000_course_page.sql | 70 ++ db/migrations/20260929090000_course_files.sql | 51 + db/schema.sql | 325 ++++- .../src/components/ConfirmDeleteDialog.vue | 37 + .../components/calendar/CoursesCalendar.vue | 253 ++++ .../src/components/course/CourseFilesTab.vue | 170 +++ .../src/components/course/CourseGradesTab.vue | 171 +++ .../components/course/CourseScheduleTab.vue | 65 + .../src/components/course/CourseTasksTab.vue | 269 ++++ .../src/components/course/GradeFormDialog.vue | 147 +++ .../src/components/course/MarkdownEditor.vue | 185 +++ frontend/src/components/course/TagBadge.vue | 18 + .../src/components/course/TaskFormDialog.vue | 219 ++++ .../components/courses/CourseFormDialog.vue | 5 +- .../components/schedule/SlotFormDialog.vue | 38 +- .../components/timetable/WeekTimetable.vue | 256 ++++ .../src/components/ui/checkbox/Checkbox.vue | 33 + frontend/src/components/ui/checkbox/index.ts | 1 + frontend/src/components/ui/tabs/Tabs.vue | 15 + .../src/components/ui/tabs/TabsContent.vue | 25 + frontend/src/components/ui/tabs/TabsList.vue | 25 + .../src/components/ui/tabs/TabsTrigger.vue | 29 + frontend/src/components/ui/tabs/index.ts | 4 + frontend/src/lib/api.d.ts | 1083 ++++++++++++++++- frontend/src/locales/en.yml | 133 +- frontend/src/locales/fr.yml | 133 +- frontend/src/router/index.ts | 11 + frontend/src/services/api/course-files.ts | 95 ++ frontend/src/services/api/courses.ts | 97 +- frontend/src/services/api/grades.ts | 62 + .../services/api/{slot-tags.ts => tags.ts} | 17 +- frontend/src/services/api/tasks.ts | 101 ++ frontend/src/utils/calendar.ts | 39 + frontend/src/utils/dates.ts | 49 + frontend/src/utils/markdown-editor.ts | 97 ++ frontend/src/utils/types.ts | 29 +- frontend/src/views/CalendarView.vue | 38 +- frontend/src/views/CourseView.vue | 133 +- frontend/src/views/LectureNoteView.vue | 264 ++++ frontend/src/views/ScheduleView.vue | 220 +--- src/api/course_files.rs | 168 +++ src/api/courses.rs | 98 +- src/api/docs.rs | 12 + src/api/grades.rs | 99 ++ src/api/helpers.rs | 21 +- src/api/mod.rs | 12 +- src/api/models.rs | 115 +- src/api/slot_tags.rs | 28 - src/api/tags.rs | 40 + src/api/tasks.rs | 126 ++ src/core/controller/course_files.rs | 229 ++++ src/core/controller/courses.rs | 132 +- src/core/controller/grades.rs | 142 +++ src/core/controller/mod.rs | 59 +- src/core/controller/slot_tags.rs | 9 - src/core/controller/tags.rs | 28 + src/core/controller/tasks.rs | 287 +++++ src/core/models/course.rs | 5 +- src/core/models/course_file.rs | 49 + src/core/models/grade.rs | 45 + src/core/models/mod.rs | 5 +- src/core/models/slot_tag.rs | 15 - src/core/models/tag.rs | 38 + src/core/models/task.rs | 60 + src/core/models/time_slot.rs | 8 +- .../repositories/course_files_repository.rs | 37 + src/core/repositories/courses_repository.rs | 25 +- src/core/repositories/grades_repository.rs | 26 + src/core/repositories/mod.rs | 22 +- src/core/repositories/pdf_renderer.rs | 18 + src/core/repositories/slot_tags_repository.rs | 8 - src/core/repositories/storage_repository.rs | 18 + src/core/repositories/tags_repository.rs | 14 + src/core/repositories/tasks_repository.rs | 35 + src/main.rs | 30 +- src/services/database/course_files.rs | 158 +++ src/services/database/courses.rs | 123 +- src/services/database/grades.rs | 110 ++ src/services/database/mod.rs | 5 +- src/services/database/slot_tags.rs | 44 - src/services/database/tags.rs | 72 ++ src/services/database/tasks.rs | 202 +++ src/services/mod.rs | 5 +- src/services/pandoc.rs | 73 ++ src/services/storage.rs | 78 ++ src/utils/config.rs | 16 + 91 files changed, 7372 insertions(+), 677 deletions(-) create mode 100644 db/migrations/20260928150000_course_page.sql create mode 100644 db/migrations/20260929090000_course_files.sql create mode 100644 frontend/src/components/ConfirmDeleteDialog.vue create mode 100644 frontend/src/components/calendar/CoursesCalendar.vue create mode 100644 frontend/src/components/course/CourseFilesTab.vue create mode 100644 frontend/src/components/course/CourseGradesTab.vue create mode 100644 frontend/src/components/course/CourseScheduleTab.vue create mode 100644 frontend/src/components/course/CourseTasksTab.vue create mode 100644 frontend/src/components/course/GradeFormDialog.vue create mode 100644 frontend/src/components/course/MarkdownEditor.vue create mode 100644 frontend/src/components/course/TagBadge.vue create mode 100644 frontend/src/components/course/TaskFormDialog.vue create mode 100644 frontend/src/components/timetable/WeekTimetable.vue create mode 100644 frontend/src/components/ui/checkbox/Checkbox.vue create mode 100644 frontend/src/components/ui/checkbox/index.ts create mode 100644 frontend/src/components/ui/tabs/Tabs.vue create mode 100644 frontend/src/components/ui/tabs/TabsContent.vue create mode 100644 frontend/src/components/ui/tabs/TabsList.vue create mode 100644 frontend/src/components/ui/tabs/TabsTrigger.vue create mode 100644 frontend/src/components/ui/tabs/index.ts create mode 100644 frontend/src/services/api/course-files.ts create mode 100644 frontend/src/services/api/grades.ts rename frontend/src/services/api/{slot-tags.ts => tags.ts} (62%) create mode 100644 frontend/src/services/api/tasks.ts create mode 100644 frontend/src/utils/calendar.ts create mode 100644 frontend/src/utils/dates.ts create mode 100644 frontend/src/utils/markdown-editor.ts create mode 100644 frontend/src/views/LectureNoteView.vue create mode 100644 src/api/course_files.rs create mode 100644 src/api/grades.rs delete mode 100644 src/api/slot_tags.rs create mode 100644 src/api/tags.rs create mode 100644 src/api/tasks.rs create mode 100644 src/core/controller/course_files.rs create mode 100644 src/core/controller/grades.rs delete mode 100644 src/core/controller/slot_tags.rs create mode 100644 src/core/controller/tags.rs create mode 100644 src/core/controller/tasks.rs create mode 100644 src/core/models/course_file.rs create mode 100644 src/core/models/grade.rs delete mode 100644 src/core/models/slot_tag.rs create mode 100644 src/core/models/tag.rs create mode 100644 src/core/models/task.rs create mode 100644 src/core/repositories/course_files_repository.rs create mode 100644 src/core/repositories/grades_repository.rs create mode 100644 src/core/repositories/pdf_renderer.rs delete mode 100644 src/core/repositories/slot_tags_repository.rs create mode 100644 src/core/repositories/storage_repository.rs create mode 100644 src/core/repositories/tags_repository.rs create mode 100644 src/core/repositories/tasks_repository.rs create mode 100644 src/services/database/course_files.rs create mode 100644 src/services/database/grades.rs delete mode 100644 src/services/database/slot_tags.rs create mode 100644 src/services/database/tags.rs create mode 100644 src/services/database/tasks.rs create mode 100644 src/services/pandoc.rs create mode 100644 src/services/storage.rs diff --git a/.gitignore b/.gitignore index 2fd919e..f66746c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target config.yml config.yaml +/storage diff --git a/Cargo.lock b/Cargo.lock index 2072f52..064b86b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,7 +281,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] @@ -512,6 +512,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -676,6 +682,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1067,6 +1084,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1383,6 +1406,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.7" @@ -1410,7 +1439,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] @@ -1512,6 +1541,19 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1997,6 +2039,7 @@ dependencies = [ "serde", "serde_json", "sqlx", + "tempfile", "thiserror", "tokio", "tower-http", @@ -2049,6 +2092,19 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.19" diff --git a/Cargo.toml b/Cargo.toml index ab858b5..89732ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,14 @@ sqlx = { version = "0.8.6", features = [ "runtime-tokio", ] } thiserror = "2.0.18" -tokio = { version = "1.52.3", features = ["rt-multi-thread", "signal"] } +tempfile = "3" +tokio = { version = "1.52.3", features = [ + "fs", + "process", + "rt-multi-thread", + "signal", + "time", +] } tower-http = { version = "0.6.10", features = ["fs"] } tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } diff --git a/README.md b/README.md index 1294902..1b01aa6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ psql -h localhost -U postgres -c 'CREATE DATABASE studium' dbmate up psql "$(grep DATABASE_URL .env | cut -d= -f2-)" -f db/seed.sql # optional demo data -# 2. Configure the app +# 2. Configure the app (the lecture notes also need pandoc and pdflatex) cp config.example.yml config.yml # 3. Run the backend (port 3000) @@ -53,6 +53,8 @@ key, keep it up to date. Every value can also come from the environment │ │ └── repositories/ traits describing what the core needs from the storage │ ├── services/ implementations of the repositories (postgres/sqlx) │ └── utils/ configuration +├── LaTeX_pdf/ template of the lecture notes pdf (pandoc + pdflatex) +├── storage/ files of the courses, stands in for the NAS (gitignored) ├── db/ │ ├── migrations/ dbmate migrations │ ├── schema.sql dump regenerated by dbmate, do not edit by hand diff --git a/config.example.yml b/config.example.yml index e7c5c40..b41c677 100644 --- a/config.example.yml +++ b/config.example.yml @@ -13,3 +13,17 @@ postgres: # Directory containing the built frontend (frontend/dist after `npm run build`) frontend_dir: frontend/dist + +# Where the files of the courses are stored (lecture notes and their pdf...). +# A local directory standing in for the NAS for now. +storage_dir: storage + +# Rendering of the lecture notes to pdf (needs pandoc and pdflatex) +notes: + # Directory of template.tex, box-filter.lua and assets/ + template_dir: LaTeX_pdf + # Relative to template_dir + logo: assets/epfl.png + # Shown in the header of the pdf, both optional + student_name: Jane Doe + student_id: "123456" diff --git a/db/migrations/20260928150000_course_page.sql b/db/migrations/20260928150000_course_page.sql new file mode 100644 index 0000000..c0806ef --- /dev/null +++ b/db/migrations/20260928150000_course_page.sql @@ -0,0 +1,70 @@ +-- migrate:up + +-- Tags are shared by the time slots and the tasks +ALTER TABLE slot_tags RENAME TO tags; +ALTER TABLE tags RENAME CONSTRAINT slot_tags_pkey TO tags_pkey; +ALTER SEQUENCE slot_tags_id_seq RENAME TO tags_id_seq; + +INSERT INTO tags ("name", color) VALUES + ('{"fr": "Devoir", "en": "Homework"}', '#9333ea'), + ('{"fr": "Examen", "en": "Exam"}', '#dc2626'), + ('{"fr": "Projet", "en": "Project"}', '#0891b2'); + +-- Grades of a course, on any scale: `score` out of `max_score`. The weights are relative +-- to each other, the average of the course is weighted by them. +CREATE TABLE grades ( + id SERIAL PRIMARY KEY, + course_id INTEGER NOT NULL REFERENCES courses (id) ON DELETE CASCADE, + "name" TEXT NOT NULL, + score DOUBLE PRECISION NOT NULL CHECK (score >= 0), + max_score DOUBLE PRECISION NOT NULL CHECK (max_score > 0), + weight DOUBLE PRECISION NOT NULL CHECK (weight > 0), + CHECK (score <= max_score) +); + +CREATE INDEX grades_course_id_idx ON grades (course_id); + +-- Homework, exams, or anything to do for a course. A task may have subtasks (one level). +CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + course_id INTEGER NOT NULL REFERENCES courses (id) ON DELETE CASCADE, + parent_id INTEGER REFERENCES tasks (id) ON DELETE CASCADE, + "name" TEXT NOT NULL, + description TEXT, + links TEXT[] NOT NULL DEFAULT '{}', + deadline TIMESTAMPTZ, + done BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX tasks_course_id_idx ON tasks (course_id); + +CREATE TABLE task_tags ( + task_id INTEGER NOT NULL REFERENCES tasks (id) ON DELETE CASCADE, + tag_id INTEGER NOT NULL REFERENCES tags (id) ON DELETE CASCADE, + PRIMARY KEY (task_id, tag_id) +); + +-- Lecture notes written in markdown. The content (.md) and the rendered pdf are files +-- in the storage, not in the database. +CREATE TABLE lecture_notes ( + id SERIAL PRIMARY KEY, + course_id INTEGER NOT NULL REFERENCES courses (id) ON DELETE CASCADE, + title TEXT NOT NULL, + -- False when the last rendering to pdf failed + has_pdf BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX lecture_notes_course_id_idx ON lecture_notes (course_id); + +-- migrate:down +DROP TABLE IF EXISTS lecture_notes; +DROP TABLE IF EXISTS task_tags; +DROP TABLE IF EXISTS tasks; +DROP TABLE IF EXISTS grades; + +DELETE FROM tags WHERE id NOT IN (SELECT DISTINCT tag_id FROM time_slots) AND id > 3; +ALTER SEQUENCE tags_id_seq RENAME TO slot_tags_id_seq; +ALTER TABLE tags RENAME CONSTRAINT tags_pkey TO slot_tags_pkey; +ALTER TABLE tags RENAME TO slot_tags; diff --git a/db/migrations/20260929090000_course_files.sql b/db/migrations/20260929090000_course_files.sql new file mode 100644 index 0000000..cf71fe8 --- /dev/null +++ b/db/migrations/20260929090000_course_files.sql @@ -0,0 +1,51 @@ +-- migrate:up + +-- What each tag can label: time slots, tasks and/or course files +ALTER TABLE tags ADD COLUMN scopes TEXT[] NOT NULL DEFAULT '{}' + CHECK (scopes <@ ARRAY['slot', 'task', 'file']); +-- Stable name, for the tags the backend needs to find (e.g. `notes` for the lecture notes) +ALTER TABLE tags ADD COLUMN slug TEXT UNIQUE; +UPDATE tags SET scopes = '{slot,task}', slug = lower("name"->>'en') + WHERE "name"->>'en' IN ('Lecture', 'Exercises', 'Lab'); +UPDATE tags SET scopes = '{task}', slug = lower("name"->>'en') + WHERE "name"->>'en' IN ('Homework', 'Exam', 'Project'); +INSERT INTO tags ("name", color, scopes, slug) VALUES + ('{"fr": "Slides", "en": "Slides"}', '#0d9488', '{file}', 'slides'), + ('{"fr": "Notes", "en": "Notes"}', '#ca8a04', '{file}', 'notes'); + +-- The lecture notes become the first kind of course files. Uploaded files (slides...) +-- will be the second one. Ids are kept: they are part of the paths in the storage. +ALTER TABLE lecture_notes RENAME TO course_files; +ALTER TABLE course_files RENAME CONSTRAINT lecture_notes_pkey TO course_files_pkey; +ALTER TABLE course_files RENAME CONSTRAINT lecture_notes_course_id_fkey TO course_files_course_id_fkey; +ALTER SEQUENCE lecture_notes_id_seq RENAME TO course_files_id_seq; +ALTER INDEX lecture_notes_course_id_idx RENAME TO course_files_course_id_idx; +ALTER TABLE course_files RENAME COLUMN title TO "name"; +-- `note`: markdown written in the app and rendered to pdf, `upload`: any file +ALTER TABLE course_files ADD COLUMN kind TEXT NOT NULL DEFAULT 'note' + CHECK (kind IN ('note', 'upload')); +ALTER TABLE course_files ALTER COLUMN kind DROP DEFAULT; + +CREATE TABLE file_tags ( + file_id INTEGER NOT NULL REFERENCES course_files (id) ON DELETE CASCADE, + tag_id INTEGER NOT NULL REFERENCES tags (id) ON DELETE CASCADE, + PRIMARY KEY (file_id, tag_id) +); + +INSERT INTO file_tags (file_id, tag_id) +SELECT f.id, t.id FROM course_files f, tags t WHERE t.slug = 'notes'; + +-- migrate:down +DROP TABLE IF EXISTS file_tags; +DELETE FROM course_files WHERE kind <> 'note'; +ALTER TABLE course_files DROP COLUMN kind; +ALTER TABLE course_files RENAME COLUMN "name" TO title; +ALTER INDEX course_files_course_id_idx RENAME TO lecture_notes_course_id_idx; +ALTER SEQUENCE course_files_id_seq RENAME TO lecture_notes_id_seq; +ALTER TABLE course_files RENAME CONSTRAINT course_files_course_id_fkey TO lecture_notes_course_id_fkey; +ALTER TABLE course_files RENAME CONSTRAINT course_files_pkey TO lecture_notes_pkey; +ALTER TABLE course_files RENAME TO lecture_notes; + +DELETE FROM tags WHERE scopes = '{file}'; +ALTER TABLE tags DROP COLUMN slug; +ALTER TABLE tags DROP COLUMN scopes; diff --git a/db/schema.sql b/db/schema.sql index 5cbeb62..19ce074 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -19,6 +19,42 @@ SET default_tablespace = ''; SET default_table_access_method = heap; +-- +-- Name: course_files; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.course_files ( + id integer CONSTRAINT lecture_notes_id_not_null NOT NULL, + course_id integer CONSTRAINT lecture_notes_course_id_not_null NOT NULL, + name text CONSTRAINT lecture_notes_title_not_null NOT NULL, + has_pdf boolean DEFAULT false CONSTRAINT lecture_notes_has_pdf_not_null NOT NULL, + created_at timestamp with time zone DEFAULT now() CONSTRAINT lecture_notes_created_at_not_null NOT NULL, + updated_at timestamp with time zone DEFAULT now() CONSTRAINT lecture_notes_updated_at_not_null NOT NULL, + kind text NOT NULL, + CONSTRAINT course_files_kind_check CHECK ((kind = ANY (ARRAY['note'::text, 'upload'::text]))) +); + + +-- +-- Name: course_files_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.course_files_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: course_files_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.course_files_id_seq OWNED BY public.course_files.id; + + -- -- Name: courses; Type: TABLE; Schema: public; Owner: - -- @@ -53,30 +89,38 @@ ALTER SEQUENCE public.courses_id_seq OWNED BY public.courses.id; -- --- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- Name: file_tags; Type: TABLE; Schema: public; Owner: - -- -CREATE TABLE public.schema_migrations ( - version character varying NOT NULL +CREATE TABLE public.file_tags ( + file_id integer NOT NULL, + tag_id integer NOT NULL ); -- --- Name: slot_tags; Type: TABLE; Schema: public; Owner: - +-- Name: grades; Type: TABLE; Schema: public; Owner: - -- -CREATE TABLE public.slot_tags ( +CREATE TABLE public.grades ( id integer NOT NULL, - name jsonb NOT NULL, - color text NOT NULL + course_id integer NOT NULL, + name text NOT NULL, + score double precision NOT NULL, + max_score double precision NOT NULL, + weight double precision NOT NULL, + CONSTRAINT grades_check CHECK ((score <= max_score)), + CONSTRAINT grades_max_score_check CHECK ((max_score > (0)::double precision)), + CONSTRAINT grades_score_check CHECK ((score >= (0)::double precision)), + CONSTRAINT grades_weight_check CHECK ((weight > (0)::double precision)) ); -- --- Name: slot_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- Name: grades_id_seq; Type: SEQUENCE; Schema: public; Owner: - -- -CREATE SEQUENCE public.slot_tags_id_seq +CREATE SEQUENCE public.grades_id_seq AS integer START WITH 1 INCREMENT BY 1 @@ -86,10 +130,99 @@ CREATE SEQUENCE public.slot_tags_id_seq -- --- Name: slot_tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- Name: grades_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - -- -ALTER SEQUENCE public.slot_tags_id_seq OWNED BY public.slot_tags.id; +ALTER SEQUENCE public.grades_id_seq OWNED BY public.grades.id; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version character varying NOT NULL +); + + +-- +-- Name: tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tags ( + id integer CONSTRAINT slot_tags_id_not_null NOT NULL, + name jsonb CONSTRAINT slot_tags_name_not_null NOT NULL, + color text CONSTRAINT slot_tags_color_not_null NOT NULL, + scopes text[] DEFAULT '{}'::text[] NOT NULL, + slug text, + CONSTRAINT tags_scopes_check CHECK ((scopes <@ ARRAY['slot'::text, 'task'::text, 'file'::text])) +); + + +-- +-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.tags_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.tags_id_seq OWNED BY public.tags.id; + + +-- +-- Name: task_tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_tags ( + task_id integer NOT NULL, + tag_id integer NOT NULL +); + + +-- +-- Name: tasks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tasks ( + id integer NOT NULL, + course_id integer NOT NULL, + parent_id integer, + name text NOT NULL, + description text, + links text[] DEFAULT '{}'::text[] NOT NULL, + deadline timestamp with time zone, + done boolean DEFAULT false NOT NULL +); + + +-- +-- Name: tasks_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.tasks_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tasks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.tasks_id_seq OWNED BY public.tasks.id; -- @@ -128,6 +261,13 @@ CREATE SEQUENCE public.time_slots_id_seq ALTER SEQUENCE public.time_slots_id_seq OWNED BY public.time_slots.id; +-- +-- Name: course_files id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.course_files ALTER COLUMN id SET DEFAULT nextval('public.course_files_id_seq'::regclass); + + -- -- Name: courses id; Type: DEFAULT; Schema: public; Owner: - -- @@ -136,10 +276,24 @@ ALTER TABLE ONLY public.courses ALTER COLUMN id SET DEFAULT nextval('public.cour -- --- Name: slot_tags id; Type: DEFAULT; Schema: public; Owner: - +-- Name: grades id; Type: DEFAULT; Schema: public; Owner: - -- -ALTER TABLE ONLY public.slot_tags ALTER COLUMN id SET DEFAULT nextval('public.slot_tags_id_seq'::regclass); +ALTER TABLE ONLY public.grades ALTER COLUMN id SET DEFAULT nextval('public.grades_id_seq'::regclass); + + +-- +-- Name: tags id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tags ALTER COLUMN id SET DEFAULT nextval('public.tags_id_seq'::regclass); + + +-- +-- Name: tasks id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tasks ALTER COLUMN id SET DEFAULT nextval('public.tasks_id_seq'::regclass); -- @@ -149,6 +303,14 @@ ALTER TABLE ONLY public.slot_tags ALTER COLUMN id SET DEFAULT nextval('public.sl ALTER TABLE ONLY public.time_slots ALTER COLUMN id SET DEFAULT nextval('public.time_slots_id_seq'::regclass); +-- +-- Name: course_files course_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.course_files + ADD CONSTRAINT course_files_pkey PRIMARY KEY (id); + + -- -- Name: courses courses_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -157,6 +319,22 @@ ALTER TABLE ONLY public.courses ADD CONSTRAINT courses_pkey PRIMARY KEY (id); +-- +-- Name: file_tags file_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_tags + ADD CONSTRAINT file_tags_pkey PRIMARY KEY (file_id, tag_id); + + +-- +-- Name: grades grades_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.grades + ADD CONSTRAINT grades_pkey PRIMARY KEY (id); + + -- -- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -166,11 +344,35 @@ ALTER TABLE ONLY public.schema_migrations -- --- Name: slot_tags slot_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- -ALTER TABLE ONLY public.slot_tags - ADD CONSTRAINT slot_tags_pkey PRIMARY KEY (id); +ALTER TABLE ONLY public.tags + ADD CONSTRAINT tags_pkey PRIMARY KEY (id); + + +-- +-- Name: tags tags_slug_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tags + ADD CONSTRAINT tags_slug_key UNIQUE (slug); + + +-- +-- Name: task_tags task_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tags + ADD CONSTRAINT task_tags_pkey PRIMARY KEY (task_id, tag_id); + + +-- +-- Name: tasks tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tasks + ADD CONSTRAINT tasks_pkey PRIMARY KEY (id); -- @@ -181,6 +383,27 @@ ALTER TABLE ONLY public.time_slots ADD CONSTRAINT time_slots_pkey PRIMARY KEY (id); +-- +-- Name: course_files_course_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX course_files_course_id_idx ON public.course_files USING btree (course_id); + + +-- +-- Name: grades_course_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX grades_course_id_idx ON public.grades USING btree (course_id); + + +-- +-- Name: tasks_course_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX tasks_course_id_idx ON public.tasks USING btree (course_id); + + -- -- Name: time_slots_course_id_idx; Type: INDEX; Schema: public; Owner: - -- @@ -188,6 +411,70 @@ ALTER TABLE ONLY public.time_slots CREATE INDEX time_slots_course_id_idx ON public.time_slots USING btree (course_id); +-- +-- Name: course_files course_files_course_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.course_files + ADD CONSTRAINT course_files_course_id_fkey FOREIGN KEY (course_id) REFERENCES public.courses(id) ON DELETE CASCADE; + + +-- +-- Name: file_tags file_tags_file_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_tags + ADD CONSTRAINT file_tags_file_id_fkey FOREIGN KEY (file_id) REFERENCES public.course_files(id) ON DELETE CASCADE; + + +-- +-- Name: file_tags file_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_tags + ADD CONSTRAINT file_tags_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE; + + +-- +-- Name: grades grades_course_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.grades + ADD CONSTRAINT grades_course_id_fkey FOREIGN KEY (course_id) REFERENCES public.courses(id) ON DELETE CASCADE; + + +-- +-- Name: task_tags task_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tags + ADD CONSTRAINT task_tags_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE; + + +-- +-- Name: task_tags task_tags_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_tags + ADD CONSTRAINT task_tags_task_id_fkey FOREIGN KEY (task_id) REFERENCES public.tasks(id) ON DELETE CASCADE; + + +-- +-- Name: tasks tasks_course_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tasks + ADD CONSTRAINT tasks_course_id_fkey FOREIGN KEY (course_id) REFERENCES public.courses(id) ON DELETE CASCADE; + + +-- +-- Name: tasks tasks_parent_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tasks + ADD CONSTRAINT tasks_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES public.tasks(id) ON DELETE CASCADE; + + -- -- Name: time_slots time_slots_course_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -201,7 +488,7 @@ ALTER TABLE ONLY public.time_slots -- ALTER TABLE ONLY public.time_slots - ADD CONSTRAINT time_slots_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.slot_tags(id); + ADD CONSTRAINT time_slots_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id); -- @@ -216,4 +503,6 @@ ALTER TABLE ONLY public.time_slots -- INSERT INTO public.schema_migrations (version) VALUES - ('20260928120000'); + ('20260928120000'), + ('20260928150000'), + ('20260929090000'); diff --git a/frontend/src/components/ConfirmDeleteDialog.vue b/frontend/src/components/ConfirmDeleteDialog.vue new file mode 100644 index 0000000..8cc14c0 --- /dev/null +++ b/frontend/src/components/ConfirmDeleteDialog.vue @@ -0,0 +1,37 @@ + + + diff --git a/frontend/src/components/calendar/CoursesCalendar.vue b/frontend/src/components/calendar/CoursesCalendar.vue new file mode 100644 index 0000000..fb0e557 --- /dev/null +++ b/frontend/src/components/calendar/CoursesCalendar.vue @@ -0,0 +1,253 @@ + + + diff --git a/frontend/src/components/course/CourseFilesTab.vue b/frontend/src/components/course/CourseFilesTab.vue new file mode 100644 index 0000000..a57363d --- /dev/null +++ b/frontend/src/components/course/CourseFilesTab.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/components/course/CourseGradesTab.vue b/frontend/src/components/course/CourseGradesTab.vue new file mode 100644 index 0000000..05c2b91 --- /dev/null +++ b/frontend/src/components/course/CourseGradesTab.vue @@ -0,0 +1,171 @@ + + + diff --git a/frontend/src/components/course/CourseScheduleTab.vue b/frontend/src/components/course/CourseScheduleTab.vue new file mode 100644 index 0000000..06446a0 --- /dev/null +++ b/frontend/src/components/course/CourseScheduleTab.vue @@ -0,0 +1,65 @@ + + + diff --git a/frontend/src/components/course/CourseTasksTab.vue b/frontend/src/components/course/CourseTasksTab.vue new file mode 100644 index 0000000..b9ff6c3 --- /dev/null +++ b/frontend/src/components/course/CourseTasksTab.vue @@ -0,0 +1,269 @@ + + + diff --git a/frontend/src/components/course/GradeFormDialog.vue b/frontend/src/components/course/GradeFormDialog.vue new file mode 100644 index 0000000..bc522d3 --- /dev/null +++ b/frontend/src/components/course/GradeFormDialog.vue @@ -0,0 +1,147 @@ + + + diff --git a/frontend/src/components/course/MarkdownEditor.vue b/frontend/src/components/course/MarkdownEditor.vue new file mode 100644 index 0000000..00d2daf --- /dev/null +++ b/frontend/src/components/course/MarkdownEditor.vue @@ -0,0 +1,185 @@ + + +