From 68c24dcec5cd66c55331a1a1c5257336a745cd74 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sun, 13 Sep 2026 18:55:12 +0300 Subject: [PATCH] Loading db schema --- dbschema.sql | 80 ++++++++++++++++++++++++++++++++++++++++++++++ src/dates.ts | 65 ++++++++++++++++++++++++++++++++++++++ src/schema.sql | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 dbschema.sql create mode 100644 src/dates.ts create mode 100644 src/schema.sql diff --git a/dbschema.sql b/dbschema.sql new file mode 100644 index 0000000..93b0913 --- /dev/null +++ b/dbschema.sql @@ -0,0 +1,80 @@ +create table custom_fields +( + id integer + primary key, + name text not null, + identifier text not null +); + +create table decks +( + id INTEGER + primary key autoincrement, + name TEXT not null, + forward TEXT not null, + backward TEXT not null, + filter TEXT not null +); + +create table settings +( + id text + primary key, + value text +); + +create table word_groups +( + id INTEGER + primary key autoincrement, + name TEXT not null +); + +create table words +( + id INTEGER + primary key autoincrement, + key TEXT not null, + value TEXT not null, + tags TEXT not null, + more TEXT, + group_id integer default 1 not null + constraint words_word_group_id_fk + references word_groups + on update cascade on delete cascade +); + +create table history +( + id INTEGER + primary key autoincrement, + deck_id INTEGER not null + references decks + on delete cascade, + word_id INTEGER not null + references words + on delete cascade, + timestamp INTEGER not null, + answer INTEGER not null, + before INTEGER not null, + after INTEGER not null, + fsrs INTEGER not null +); + +create table states +( + id INTEGER + primary key autoincrement, + word_id INTEGER not null + references words + on delete cascade, + deck_id INTEGER + references decks + on delete cascade, + score INTEGER default 1 not null, + last_opened integer not null, + fsrs_opened integer not null, + fsrs_memory integer not null, + fsrs_open_day integer not null +); + diff --git a/src/dates.ts b/src/dates.ts new file mode 100644 index 0000000..68696c7 --- /dev/null +++ b/src/dates.ts @@ -0,0 +1,65 @@ +// Числовые даты в БД JapLearn. Никаких TEXT/DATE-типов: всё — числа. +// +// Два формата: +// 1. «Дата-только» — число дней от 1970-01-01 (UTC). +// Поля: states.fsrs_opened, states.fsrs_open_day. +// Помощники: dateToEpochDay / epochDayToIso / isoToEpochDay. +// 2. Полный таймстамп — миллисекунды от 1970-01-01 (эпоха Unix, UTC). +// Поля: history.timestamp, states.last_opened. +// Помощники: dateToEpochMs / epochMsToIso / isoToEpochMs. +// (Предположение о единице измерения — см. ниже.) + +/** Миллисекунд в одних сутках (UTC). */ +export const DAY_MS = 86_400_000; + +// --------------------------------------------------------------------------- +// Формат «дата-только»: число дней от 1970-01-01. +// --------------------------------------------------------------------------- + +/** Количество дней от 1970-01-01 (UTC) до указанной даты. */ +export function dateToEpochDay(date: Date): number { + return Math.floor(date.getTime() / DAY_MS); +} + +/** Дата (полночь UTC) по номеру дня от 1970-01-01. */ +export function epochDayToDate(day: number): Date { + return new Date(day * DAY_MS); +} + +/** ISO-формат 'YYYY-MM-DD' по номеру дня от 1970-01-01. */ +export function epochDayToIso(day: number): string { + return epochDayToDate(day).toISOString().slice(0, 10); +} + +/** Номер дня от 1970-01-01 по ISO-дате 'YYYY-MM-DD'. */ +export function isoToEpochDay(iso: string): number { + return Math.floor(Date.parse(`${iso}T00:00:00Z`) / DAY_MS); +} + +// --------------------------------------------------------------------------- +// Полный таймстамп: миллисекунды от 1970-01-01. +// +// ВАЖНО: единица измерения не зафиксирована в схеме. Здесь приняты +// миллисекунды (нативный формат JS Date). Если десктопная версия хранит +// секунды — поправить достаточно в одном месте (умножение на 1000). +// --------------------------------------------------------------------------- + +/** Таймстамп (мс) для даты. */ +export function dateToEpochMs(date: Date): number { + return date.getTime(); +} + +/** Дата по таймстампу (мс). */ +export function epochMsToDate(ms: number): Date { + return new Date(ms); +} + +/** ISO-строка (UTC) по таймстампу (мс). */ +export function epochMsToIso(ms: number): string { + return epochMsToDate(ms).toISOString(); +} + +/** Таймстамп (мс) по ISO-строке. */ +export function isoToEpochMs(iso: string): number { + return Date.parse(iso); +} \ No newline at end of file diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 0000000..5aec0ab --- /dev/null +++ b/src/schema.sql @@ -0,0 +1,86 @@ +-- Схема базы данных JapLearn. +-- Источник истины: /dbschema.sql (корень проекта). +-- Здесь та же схема, но идемпотентная (CREATE TABLE IF NOT EXISTS), +-- чтобы выполнять её при инициализации БД без ошибок на уже готовой базе. +-- Все даты и таймстампы — числа. Поля-«даты» (fsrs_opened, fsrs_open_day) +-- хранят число дней от 1970-01-01 (UTC), остальные — таймстампы. + +create table if not exists custom_fields +( + id integer + primary key, + name text not null, + identifier text not null +); + +create table if not exists decks +( + id INTEGER + primary key autoincrement, + name TEXT not null, + forward TEXT not null, + backward TEXT not null, + filter TEXT not null +); + +create table if not exists settings +( + id text + primary key, + value text +); + +create table if not exists word_groups +( + id INTEGER + primary key autoincrement, + name TEXT not null +); + +create table if not exists words +( + id INTEGER + primary key autoincrement, + key TEXT not null, + value TEXT not null, + tags TEXT not null, + more TEXT, + group_id integer default 1 not null + constraint words_word_group_id_fk + references word_groups + on update cascade on delete cascade +); + +create table if not exists history +( + id INTEGER + primary key autoincrement, + deck_id INTEGER not null + references decks + on delete cascade, + word_id INTEGER not null + references words + on delete cascade, + timestamp INTEGER not null, + answer INTEGER not null, + before INTEGER not null, + after INTEGER not null, + fsrs INTEGER not null +); + +create table if not exists states +( + id INTEGER + primary key autoincrement, + word_id INTEGER not null + references words + on delete cascade, + deck_id INTEGER + references decks + on delete cascade, + score INTEGER default 1 not null, + last_opened integer not null, + fsrs_opened integer not null, + fsrs_memory integer not null, + fsrs_open_day integer not null +); \ No newline at end of file