diff --git a/src/idz/db/story.ts b/src/idz/db/story.ts new file mode 100644 index 0000000..fd4096b --- /dev/null +++ b/src/idz/db/story.ts @@ -0,0 +1,111 @@ +import { Client } from "pg"; +import * as sql from "sql-bricks-postgres"; + +import { _findProfile } from "./_util"; +import { ExtId } from "../model/base"; +import { Profile } from "../model/profile"; +import { Story, StoryRow, StoryCell } from "../model/story"; +import { FacetRepository } from "../repo"; +import { generateId, Id } from "../../db"; + +export class SqlStoryRepository implements FacetRepository { + constructor(private readonly _conn: Client) {} + + private async _load(extId: ExtId): Promise<[Story, Id]> { + const profileId = await _findProfile(this._conn, extId); + + const loadSql = sql + .select("s.*") + .from("idz.story_state s") + .where("s.id", profileId) + .toParams(); + + const headerResult = await this._conn.query(loadSql); + const header = headerResult.rows[0]; + + // Must succeed even if nonexistent (required by save method below) + + const result = { + x: header !== undefined ? header.x : 0, + y: header !== undefined ? header.y : 0, + rows: new Array(), + }; + + for (let i = 0; i < 9; i++) { + const row: StoryRow = { cells: new Array() }; + + for (let j = 0; j < 9; j++) { + row.cells.push({ a: 0, b: 0 }); + } + + result.rows.push(row); + } + + const loadCellSql = sql + .select("sc.*") + .from("idz.story_cell_state sc") + .where("sc.profile_id", profileId) + .toParams(); + + const { rows } = await this._conn.query(loadCellSql); + + for (const row of rows) { + const cell = result.rows[row.row_no].cells[row.cell_no]; + + cell.a = row.a; + cell.b = row.b; + } + + return [result, profileId]; + } + + async load(extId: ExtId): Promise { + const [story] = await this._load(extId); + + return story; + } + + async save(extId: ExtId, story: Story): Promise { + const [existing, profileId] = await this._load(extId); + + const headSql = sql + .insert("idz.story_state", { + id: profileId, + x: story.x, + y: story.y, + }) + .onConflict("id") + .doUpdate("x", "y") + .toParams(); + + await this._conn.query(headSql); + + for (let i = 0; i < story.rows.length; i++) { + const exRow = existing.rows[i]; + const row = story.rows[i]; + + for (let j = 0; j < row.cells.length; j++) { + const exCell = exRow.cells[j]; + const cell = row.cells[j]; + + if (cell.a === exCell.a && cell.b === exCell.b) { + continue; // Most if not all cells are unchanged on profile save. + } + + const cellSql = sql + .insert("idz.story_cell_state", { + id: generateId(), + profile_id: profileId, + row_no: i, + col_no: j, + a: cell.a, + b: cell.b, + }) + .onConflict("profile_id", "row_no", "col_no") + .doUpdate("a", "b"); + + await this._conn.query(cellSql); + } + } + } +}