mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
feat: finish the homepage plugin, move service links and canvas out of core (B19)
This commit is contained in:
1 parent
21937a6b02
commit
d4ec444558
256 files changed
+27365
-9077
No files matched your search
@@ -1,120 +0,0 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { TestSqliteDatabase } from "./test-support.js";
|
||||
import { DashboardServiceLinkRepository } from "../../../database/repositories/dashboard-service-link-repository.js";
|
||||
|
||||
describe("DashboardServiceLinkRepository", () => {
|
||||
let adapter: TestSqliteDatabase | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (adapter) {
|
||||
await adapter.close();
|
||||
adapter = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function createRepository(
|
||||
onWrite?: () => void | Promise<void>,
|
||||
): Promise<DashboardServiceLinkRepository> {
|
||||
adapter = new TestSqliteDatabase();
|
||||
const context = await adapter.connect();
|
||||
await adapter.exec(`
|
||||
INSERT INTO users (id, username, password_hash)
|
||||
VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash');
|
||||
`);
|
||||
|
||||
return new DashboardServiceLinkRepository(context, onWrite);
|
||||
}
|
||||
|
||||
it("creates and lists links ordered by order then id", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
const first = await repo.createForUser(
|
||||
"user-1",
|
||||
{ label: "Docs", url: "https://docs.example.com" },
|
||||
"2026-06-27T00:00:00.000Z",
|
||||
);
|
||||
const second = await repo.createForUser("user-1", {
|
||||
label: "Status",
|
||||
url: "https://status.example.com",
|
||||
});
|
||||
await repo.createForUser("user-2", {
|
||||
label: "Other",
|
||||
url: "https://other.example.com",
|
||||
});
|
||||
|
||||
expect(first).toMatchObject({
|
||||
userId: "user-1",
|
||||
label: "Docs",
|
||||
order: 0,
|
||||
createdAt: "2026-06-27T00:00:00.000Z",
|
||||
});
|
||||
expect(second.order).toBe(1);
|
||||
expect(
|
||||
(await repo.listByUserId("user-1")).map((link) => link.label),
|
||||
).toEqual(["Docs", "Status"]);
|
||||
});
|
||||
|
||||
it("finds, updates, and deletes a user-owned link", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
const link = await repo.createForUser("user-1", {
|
||||
label: "Docs",
|
||||
url: "https://docs.example.com",
|
||||
});
|
||||
expect(writeCount).toBe(1);
|
||||
|
||||
expect(await repo.findByIdForUser("user-2", link.id)).toBeNull();
|
||||
const updated = await repo.updateForUser("user-1", link.id, {
|
||||
label: "Docs renamed",
|
||||
});
|
||||
expect(updated).toMatchObject({
|
||||
id: link.id,
|
||||
label: "Docs renamed",
|
||||
url: "https://docs.example.com",
|
||||
});
|
||||
expect(writeCount).toBe(2);
|
||||
|
||||
expect(await repo.updateForUser("user-2", link.id, { label: "Nope" })).toBe(
|
||||
null,
|
||||
);
|
||||
expect(writeCount).toBe(2);
|
||||
|
||||
expect(await repo.deleteForUser("user-2", link.id)).toBeNull();
|
||||
expect(await repo.deleteForUser("user-1", link.id)).toEqual({
|
||||
syncId: expect.any(String),
|
||||
});
|
||||
expect(writeCount).toBe(3);
|
||||
});
|
||||
|
||||
it("deletes all dashboard links for a user", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
await repo.createForUser("user-1", {
|
||||
label: "Docs",
|
||||
url: "https://docs.example.com",
|
||||
});
|
||||
await repo.createForUser("user-1", {
|
||||
label: "Status",
|
||||
url: "https://status.example.com",
|
||||
});
|
||||
await repo.createForUser("user-2", {
|
||||
label: "Other",
|
||||
url: "https://other.example.com",
|
||||
});
|
||||
|
||||
await expect(repo.deleteByUserId("user-1")).resolves.toBe(2);
|
||||
await expect(repo.deleteByUserId("missing")).resolves.toBe(0);
|
||||
|
||||
expect(await repo.listByUserId("user-1")).toEqual([]);
|
||||
expect(
|
||||
(await repo.listByUserId("user-2")).map((link) => link.label),
|
||||
).toEqual(["Other"]);
|
||||
expect(writeCount).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -1,131 +0,0 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { TestSqliteDatabase } from "./test-support.js";
|
||||
import { HomepageItemRepository } from "../../../database/repositories/homepage-item-repository.js";
|
||||
|
||||
describe("HomepageItemRepository", () => {
|
||||
let adapter: TestSqliteDatabase | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (adapter) {
|
||||
await adapter.close();
|
||||
adapter = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function createRepository(
|
||||
onWrite?: () => void | Promise<void>,
|
||||
): Promise<HomepageItemRepository> {
|
||||
adapter = new TestSqliteDatabase();
|
||||
const context = await adapter.connect();
|
||||
await adapter.exec(`
|
||||
INSERT INTO users (id, username, password_hash)
|
||||
VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash');
|
||||
`);
|
||||
|
||||
return new HomepageItemRepository(context, onWrite);
|
||||
}
|
||||
|
||||
it("creates and lists homepage items by id", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
const first = await repo.createForUser(
|
||||
"user-1",
|
||||
{ typeId: "clock", title: "Clock", config: "{}" },
|
||||
"2026-06-27T00:00:00.000Z",
|
||||
);
|
||||
const second = await repo.createForUser("user-1", {
|
||||
typeId: "terminal",
|
||||
title: null,
|
||||
config: '{"hostId":1}',
|
||||
});
|
||||
await repo.createForUser("user-2", {
|
||||
typeId: "other",
|
||||
title: "Other",
|
||||
config: "{}",
|
||||
});
|
||||
|
||||
expect(first).toMatchObject({
|
||||
userId: "user-1",
|
||||
typeId: "clock",
|
||||
title: "Clock",
|
||||
config: "{}",
|
||||
createdAt: "2026-06-27T00:00:00.000Z",
|
||||
});
|
||||
expect((await repo.listByUserId("user-1")).map((item) => item.id)).toEqual([
|
||||
first.id,
|
||||
second.id,
|
||||
]);
|
||||
});
|
||||
|
||||
it("finds, updates, and deletes user-owned homepage items", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
const item = await repo.createForUser("user-1", {
|
||||
typeId: "clock",
|
||||
title: "Clock",
|
||||
config: "{}",
|
||||
});
|
||||
expect(writeCount).toBe(1);
|
||||
|
||||
expect(await repo.findByIdForUser("user-2", item.id)).toBeNull();
|
||||
const updated = await repo.updateForUser(
|
||||
"user-1",
|
||||
item.id,
|
||||
{ title: "Clock renamed", config: '{"timezone":"UTC"}' },
|
||||
"2026-06-27T01:00:00.000Z",
|
||||
);
|
||||
expect(updated).toMatchObject({
|
||||
id: item.id,
|
||||
title: "Clock renamed",
|
||||
config: '{"timezone":"UTC"}',
|
||||
updatedAt: "2026-06-27T01:00:00.000Z",
|
||||
});
|
||||
expect(writeCount).toBe(2);
|
||||
|
||||
expect(
|
||||
await repo.updateForUser("user-2", item.id, { title: "Nope" }),
|
||||
).toBeNull();
|
||||
expect(writeCount).toBe(2);
|
||||
|
||||
expect(await repo.deleteForUser("user-2", item.id)).toBeNull();
|
||||
expect(await repo.deleteForUser("user-1", item.id)).toEqual({
|
||||
syncId: expect.any(String),
|
||||
});
|
||||
expect(writeCount).toBe(3);
|
||||
});
|
||||
|
||||
it("deletes all homepage items for a user", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
await repo.createForUser("user-1", {
|
||||
typeId: "clock",
|
||||
title: "Clock",
|
||||
config: "{}",
|
||||
});
|
||||
await repo.createForUser("user-1", {
|
||||
typeId: "terminal",
|
||||
title: "Terminal",
|
||||
config: "{}",
|
||||
});
|
||||
await repo.createForUser("user-2", {
|
||||
typeId: "other",
|
||||
title: "Other",
|
||||
config: "{}",
|
||||
});
|
||||
|
||||
await expect(repo.deleteByUserId("user-1")).resolves.toBe(2);
|
||||
await expect(repo.deleteByUserId("missing")).resolves.toBe(0);
|
||||
|
||||
expect(await repo.listByUserId("user-1")).toEqual([]);
|
||||
expect(
|
||||
(await repo.listByUserId("user-2")).map((item) => item.title),
|
||||
).toEqual(["Other"]);
|
||||
expect(writeCount).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { TestSqliteDatabase } from "./test-support.js";
|
||||
import { HomepageLayoutRepository } from "../../../database/repositories/homepage-layout-repository.js";
|
||||
|
||||
describe("HomepageLayoutRepository", () => {
|
||||
let adapter: TestSqliteDatabase | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (adapter) {
|
||||
await adapter.close();
|
||||
adapter = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function createRepository(
|
||||
onWrite?: () => void | Promise<void>,
|
||||
): Promise<HomepageLayoutRepository> {
|
||||
adapter = new TestSqliteDatabase();
|
||||
const context = await adapter.connect();
|
||||
await adapter.exec(`
|
||||
INSERT INTO users (id, username, password_hash)
|
||||
VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash');
|
||||
`);
|
||||
|
||||
return new HomepageLayoutRepository(context, onWrite);
|
||||
}
|
||||
|
||||
it("finds, creates, and updates a layout by user id", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
expect(await repo.findByUserId("user-1")).toBeNull();
|
||||
|
||||
const created = await repo.upsertForUser(
|
||||
"user-1",
|
||||
JSON.stringify({ entries: [{ id: "w1" }], zoom: 1 }),
|
||||
"2026-06-27T00:00:00.000Z",
|
||||
);
|
||||
expect(created).toMatchObject({
|
||||
userId: "user-1",
|
||||
layout: '{"entries":[{"id":"w1"}],"zoom":1}',
|
||||
updatedAt: "2026-06-27T00:00:00.000Z",
|
||||
});
|
||||
|
||||
const updated = await repo.upsertForUser(
|
||||
"user-1",
|
||||
JSON.stringify({ entries: [], zoom: 1.25 }),
|
||||
"2026-06-27T01:00:00.000Z",
|
||||
);
|
||||
expect(updated).toMatchObject({
|
||||
id: created.id,
|
||||
userId: "user-1",
|
||||
layout: '{"entries":[],"zoom":1.25}',
|
||||
updatedAt: "2026-06-27T01:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("triggers writes after create and update", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
await repo.upsertForUser("user-1", "{}");
|
||||
await repo.upsertForUser("user-1", '{"zoom":2}');
|
||||
|
||||
expect(writeCount).toBe(2);
|
||||
});
|
||||
|
||||
it("deletes a layout for a user", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
await repo.upsertForUser("user-1", '{"zoom":1}');
|
||||
await repo.upsertForUser("user-2", '{"zoom":2}');
|
||||
|
||||
await expect(repo.deleteByUserId("user-1")).resolves.toBe(1);
|
||||
await expect(repo.deleteByUserId("missing")).resolves.toBe(0);
|
||||
|
||||
expect(await repo.findByUserId("user-1")).toBeNull();
|
||||
expect((await repo.findByUserId("user-2"))?.layout).toBe('{"zoom":2}');
|
||||
expect(writeCount).toBe(3);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user