From bb14f33326b7da997efaf6157e70c912d4ba0209 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 16:05:13 +0200 Subject: [PATCH 01/16] project base repository + domain + schema --- src/lib/database/schemas.ts | 29 ++++++++ src/lib/server/domain/domain.ts | 15 +++++ src/lib/server/infra/repository.ts | 12 ++++ .../projects/application/project.service.ts | 67 ++++++++----------- src/modules/projects/domain/entities.ts | 5 -- src/modules/projects/domain/project.domain.ts | 35 ++++++++++ src/modules/projects/domain/repositories.ts | 7 -- .../repositories/project.repostitory.ts | 53 +++++++++++++++ .../repositories/sqlite-project.repository.ts | 41 ------------ 9 files changed, 173 insertions(+), 91 deletions(-) create mode 100644 src/lib/server/domain/domain.ts create mode 100644 src/lib/server/infra/repository.ts delete mode 100644 src/modules/projects/domain/entities.ts create mode 100644 src/modules/projects/domain/project.domain.ts delete mode 100644 src/modules/projects/domain/repositories.ts create mode 100644 src/modules/projects/infrastructure/repositories/project.repostitory.ts delete mode 100644 src/modules/projects/infrastructure/repositories/sqlite-project.repository.ts diff --git a/src/lib/database/schemas.ts b/src/lib/database/schemas.ts index 697122b..f6bde5d 100644 --- a/src/lib/database/schemas.ts +++ b/src/lib/database/schemas.ts @@ -44,4 +44,33 @@ relations.for(UserEntity, ({ one, many }) => ({ relations.for(ApiKeyEntity, ({ one }) => ({ user: one(UserEntity, { fields: ['userId'], references: ['id'] }), +})); + + +export const ProjectEntity = entity('projects', { + id: uuid().primaryKey(), + slug: text().notNull(), + name: text().notNull(), + description: text(), + status: text().notNull().default('active'), + createdAt: timestamp().notNull().$defaultFn(() => new Date().toISOString()), + updatedAt: timestamp().notNull().$defaultFn(() => new Date().toISOString()), +}); + +export const ProjectRoleEntity = entity('project_roles', { + id: uuid().primaryKey(), + projectId: uuid().notNull(), + slug: text().notNull(), + name: text().notNull(), + permissions: json().notNull().$defaultFn(() => []), + createdAt: timestamp().notNull().$defaultFn(() => new Date().toISOString()), + updatedAt: timestamp().notNull().$defaultFn(() => new Date().toISOString()), +}); + +relations.for(ProjectEntity, ({ many }) => ({ + roles: many(ProjectRoleEntity, { fields: ['id'], references: ['projectId'] }), +})); + +relations.for(ProjectRoleEntity, ({ one }) => ({ + project: one(ProjectEntity, { fields: ['projectId'], references: ['id'] }), })); \ No newline at end of file diff --git a/src/lib/server/domain/domain.ts b/src/lib/server/domain/domain.ts new file mode 100644 index 0000000..f4c2fc6 --- /dev/null +++ b/src/lib/server/domain/domain.ts @@ -0,0 +1,15 @@ + +export class Domain { + public id: string = '' + public createdAt: Date = new Date() + public updatedAt: Date = new Date() + + constructor(data: any) { + this.id = data.id + this.createdAt = new Date(data.createdAt) + this.updatedAt = new Date(data.updatedAt) + } + toJson() { + return JSON.parse(JSON.stringify(this)); + } +} \ No newline at end of file diff --git a/src/lib/server/infra/repository.ts b/src/lib/server/infra/repository.ts new file mode 100644 index 0000000..6105e33 --- /dev/null +++ b/src/lib/server/infra/repository.ts @@ -0,0 +1,12 @@ +import { getGitDb } from '$lib/server/gitdb'; +export class Repository { + protected readonly db = getGitDb(); + + protected toDomain(row: any): any { + return row; + } + + protected toJSON(row: any): any { + return row; + } +} \ No newline at end of file diff --git a/src/modules/projects/application/project.service.ts b/src/modules/projects/application/project.service.ts index c07c61e..b0af5d7 100644 --- a/src/modules/projects/application/project.service.ts +++ b/src/modules/projects/application/project.service.ts @@ -1,48 +1,39 @@ -import type { Project } from '../domain/entities'; -import type { ProjectRepository } from '../domain/repositories'; +import { ProjectRepository } from '../infrastructure/repositories/project.repostitory'; +import { ProjectDomain } from '../domain/project.domain'; export class ProjectService { constructor(private readonly repository: ProjectRepository) {} - listProjects(): Project[] { - return this.repository.list(); + async listProjects() { + const projects = await this.repository.findAll(); + return projects.map((project) => project.toJson()); } - createProject(name: string): Project { - const normalizedName = name.trim(); - if (!normalizedName) { - throw new Error('Project name is required'); - } - const id = normalizedName.toLowerCase().replace(/[^a-z0-9-]/g, '-'); - this.repository.upsertIfMissing({ id, name: normalizedName }); - return { id, name: normalizedName }; - } + // syncFromPulumiStateKeys(keys: string[]): number { + // const projects = new Set(); - syncFromPulumiStateKeys(keys: string[]): number { - const projects = new Set(); - - for (const key of keys) { - let id = key; - if (id.startsWith('.pulumi/stacks/')) { - id = id.replace('.pulumi/stacks/', ''); - } - if (id.endsWith('.json')) { - id = id.slice(0, -5); - } - - const parts = id.split('/'); - projects.add(parts.length > 1 ? parts[0] : 'default'); - } - - this.repository.upsertManyIfMissing( - Array.from(projects).map((project) => ({ - id: project, - name: project, - })), - ); - - return projects.size; - } + // for (const key of keys) { + // let id = key; + // if (id.startsWith('.pulumi/stacks/')) { + // id = id.replace('.pulumi/stacks/', ''); + // } + // if (id.endsWith('.json')) { + // id = id.slice(0, -5); + // } + + // const parts = id.split('/'); + // projects.add(parts.length > 1 ? parts[0] : 'default'); + // } + + // this.repository.upsertManyIfMissing( + // Array.from(projects).map((project) => ({ + // id: project, + // name: project, + // })), + // ); + + // return projects.size; + // } } diff --git a/src/modules/projects/domain/entities.ts b/src/modules/projects/domain/entities.ts deleted file mode 100644 index 6514b74..0000000 --- a/src/modules/projects/domain/entities.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Project { - id: string; - name: string; - createdAt?: string; -} diff --git a/src/modules/projects/domain/project.domain.ts b/src/modules/projects/domain/project.domain.ts new file mode 100644 index 0000000..32f479f --- /dev/null +++ b/src/modules/projects/domain/project.domain.ts @@ -0,0 +1,35 @@ + + +import { Domain } from '$lib/server/domain/domain'; + +export interface ProjectStatus { + ACTIVE: 'active'; + INACTIVE: 'inactive'; +} + +export class ProjectDomain extends Domain { + public name: string = '' + public slug: string | null = null + public description?: string | null = null + public status: ProjectStatus[keyof ProjectStatus] = 'active' + constructor(data: any) { + super(data); + this.name = data.name + this.slug = data.slug + this.description = data.description + this.status = data.status + + } + + toJson() { + return { + id: this.id, + name: this.name, + slug: this.slug, + description: this.description, + createdAt: this.createdAt, + status: this.status, + updatedAt: this.updatedAt + } + } +} \ No newline at end of file diff --git a/src/modules/projects/domain/repositories.ts b/src/modules/projects/domain/repositories.ts deleted file mode 100644 index faa1e4b..0000000 --- a/src/modules/projects/domain/repositories.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Project } from './entities'; - -export interface ProjectRepository { - list(): Project[]; - upsertIfMissing(project: { id: string; name: string }): void; - upsertManyIfMissing(projects: Array<{ id: string; name: string }>): void; -} diff --git a/src/modules/projects/infrastructure/repositories/project.repostitory.ts b/src/modules/projects/infrastructure/repositories/project.repostitory.ts new file mode 100644 index 0000000..833b0ad --- /dev/null +++ b/src/modules/projects/infrastructure/repositories/project.repostitory.ts @@ -0,0 +1,53 @@ + +import { Repository } from '$lib/server/infra/repository'; +import { ProjectDomain } from '../../domain/project.domain'; +import { ProjectEntity } from '$lib/database/schemas'; + +export class ProjectRepository extends Repository { + + async findByName(name: string): Promise { + const role = await this.db.select().from(ProjectEntity).where({ name }).limit(1); + return role.rows[0] ? new ProjectDomain(role.rows[0]) : null; + } + + async findAll(): Promise { + const result = await this.db.select().from(ProjectEntity).orderBy('createdAt', 'asc'); + return result.rows.map((row: any) => new ProjectDomain(row)); + } + + async findById(id: string): Promise { + const result = await this.db.select().from(ProjectEntity).where({ id }).limit(1); + const row = result.rows[0]; + return row ? new ProjectDomain(row) : null; + } + + async findBySlug(slug: string): Promise { + const result = await this.db.select().from(ProjectEntity).where({ slug }).limit(1); + const row = result.rows[0]; + return row ? new ProjectDomain(row) : null; + } + + async create(input: { id: string; slug: string; name: string; description?: string, status: string }): Promise { + await this.db.insert(ProjectEntity).values({ + id: input.id, + slug: input.slug, + name: input.name, + description: input.description, + status: input.status, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + } + + async update(id: string, changes: { name?: string; slug?: string; description?: string; status?: string }): Promise { + await this.db + .update(ProjectEntity) + .set({ ...changes, updatedAt: new Date().toISOString() }) + .where({ id }); + } + + async deleteById(id: string): Promise { + // TODO: Aqui deberemos borrar roles, usuarios y demás entidades relacionadas con el proyecto antes de borrarlo + await this.db.delete(ProjectEntity).where({ id }); + } +} \ No newline at end of file diff --git a/src/modules/projects/infrastructure/repositories/sqlite-project.repository.ts b/src/modules/projects/infrastructure/repositories/sqlite-project.repository.ts deleted file mode 100644 index 6a6ff83..0000000 --- a/src/modules/projects/infrastructure/repositories/sqlite-project.repository.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { DatabaseClient } from '$lib/database/types'; -import type { Project } from '../../domain/entities'; -import type { ProjectRepository } from '../../domain/repositories'; - -type ProjectRow = { - id: string; - name: string; - created_at?: string; -}; - -export class SqliteProjectRepository implements ProjectRepository { - constructor(private readonly db: DatabaseClient) {} - - list(): Project[] { - const rows = this.db.all('SELECT * FROM projects ORDER BY created_at DESC'); - return rows.map((row) => ({ - id: row.id, - name: row.name, - createdAt: row.created_at, - })); - } - - upsertIfMissing(project: { id: string; name: string }): void { - this.db.run('INSERT INTO projects (id, name) VALUES (@id, @name) ON CONFLICT(id) DO NOTHING', { - id: project.id, - name: project.name, - }); - } - - upsertManyIfMissing(projects: Array<{ id: string; name: string }>): void { - if (!projects.length) { - return; - } - - this.db.transaction(() => { - for (const project of projects) { - this.upsertIfMissing(project); - } - }); - } -} From 4c80adcc3d76527531ffc68ae670c959de38f095 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 16:56:32 +0200 Subject: [PATCH 02/16] project WIP --- src/lib/components/AppSidebar.svelte | 96 ++-- .../projects/application/project.service.ts | 136 +++++- src/modules/projects/index.ts | 5 +- src/routes/api/projects/+server.ts | 18 +- src/routes/api/projects/[id]/+server.ts | 58 +++ src/routes/settings/projects/+page.svelte | 434 ++++++++++++++++++ .../settings/projects/[id]/+page.svelte | 312 +++++++++++++ 7 files changed, 1002 insertions(+), 57 deletions(-) create mode 100644 src/routes/api/projects/[id]/+server.ts create mode 100644 src/routes/settings/projects/+page.svelte create mode 100644 src/routes/settings/projects/[id]/+page.svelte diff --git a/src/lib/components/AppSidebar.svelte b/src/lib/components/AppSidebar.svelte index e67296d..80a43d2 100644 --- a/src/lib/components/AppSidebar.svelte +++ b/src/lib/components/AppSidebar.svelte @@ -5,6 +5,7 @@ ChevronDown, ChevronRight, Database, + FolderKanban, GitBranch, HelpCircle, KeyRound, @@ -15,7 +16,6 @@ Shield, Users, } from 'lucide-svelte'; - export let pathname = '/'; export let isConfigured = false; export let collapsed = false; @@ -84,6 +84,11 @@ href: '/settings/roles-permissions', icon: Users, }, + { + label: 'Projects', + href: '/settings/projects', + icon: FolderKanban, + }, { label: 'System & Backup', href: '/settings/system-backup', @@ -100,7 +105,10 @@ let pulumiStateMenuOpen = pathname.startsWith('/pulumi-state'); function getPulumiStateActiveItem() { - if (currentPath.startsWith('/pulumi-state/backends') || currentPath.startsWith('/settings/storage')) { + if ( + currentPath.startsWith('/pulumi-state/backends') || + currentPath.startsWith('/settings/storage') + ) { return '/pulumi-state/backends'; } @@ -151,7 +159,9 @@
{#if !collapsed}
-

Modules

+

+ Modules +

Navigation

{/if} @@ -175,7 +185,9 @@ {#if !isConfigured}
@@ -194,7 +206,9 @@
{#if !collapsed}
-

{section.title}

+

+ {section.title} +

{/if} @@ -210,9 +224,11 @@ on:click={() => (pulumiStateMenuOpen = !pulumiStateMenuOpen)} aria-expanded={pulumiStateMenuOpen} > -
+

Pulumi State

@@ -229,7 +245,9 @@ {#each pulumiStateItems as pulumiItem}
@@ -244,18 +262,24 @@ {:else} -
+
@@ -272,21 +296,29 @@
{#if !collapsed}
-

Sistema

+

+ Sistema +

{/if} {#if collapsed}
-
+
@@ -294,15 +326,21 @@
- \ No newline at end of file + diff --git a/src/modules/projects/application/project.service.ts b/src/modules/projects/application/project.service.ts index b0af5d7..b1d62e9 100644 --- a/src/modules/projects/application/project.service.ts +++ b/src/modules/projects/application/project.service.ts @@ -1,6 +1,8 @@ - +import crypto from 'crypto'; import { ProjectRepository } from '../infrastructure/repositories/project.repostitory'; -import { ProjectDomain } from '../domain/project.domain'; + +export type ProjectStatusValue = 'active' | 'inactive'; + export class ProjectService { constructor(private readonly repository: ProjectRepository) {} @@ -9,31 +11,119 @@ export class ProjectService { return projects.map((project) => project.toJson()); } + async getProject(id: string) { + const project = await this.repository.findById(id); + if (!project) { + throw new Error('Project not found'); + } + return project.toJson(); + } + + async createProject(input: { + name: string; + slug?: string; + description?: string; + status?: string; + }) { + const name = input.name.trim(); + if (!name) { + throw new Error('Project name is required'); + } + + const slug = this.normalizeSlug(input.slug || name); + if (!slug) { + throw new Error('Project slug is required'); + } + + const existing = await this.repository.findBySlug(slug); + if (existing) { + throw new Error('A project with this slug already exists'); + } + + await this.repository.create({ + id: crypto.randomUUID(), + slug, + name, + description: input.description?.trim() || undefined, + status: this.sanitizeStatus(input.status), + }); + + const created = await this.repository.findBySlug(slug); + if (!created) { + throw new Error('Failed to create project'); + } + + return created.toJson(); + } + + async updateProject( + id: string, + changes: { name?: string; slug?: string; description?: string; status?: string }, + ) { + const project = await this.repository.findById(id); + if (!project) { + throw new Error('Project not found'); + } + const patch: { name?: string; slug?: string; description?: string; status?: string } = {}; - // syncFromPulumiStateKeys(keys: string[]): number { - // const projects = new Set(); + if (changes.name !== undefined) { + const name = changes.name.trim(); + if (!name) { + throw new Error('Project name is required'); + } + patch.name = name; + } - // for (const key of keys) { - // let id = key; - // if (id.startsWith('.pulumi/stacks/')) { - // id = id.replace('.pulumi/stacks/', ''); - // } - // if (id.endsWith('.json')) { - // id = id.slice(0, -5); - // } + if (changes.slug !== undefined) { + const slug = this.normalizeSlug(changes.slug); + if (!slug) { + throw new Error('Project slug is required'); + } - // const parts = id.split('/'); - // projects.add(parts.length > 1 ? parts[0] : 'default'); - // } + const existing = await this.repository.findBySlug(slug); + if (existing && existing.id !== id) { + throw new Error('A project with this slug already exists'); + } + patch.slug = slug; + } - // this.repository.upsertManyIfMissing( - // Array.from(projects).map((project) => ({ - // id: project, - // name: project, - // })), - // ); + if (changes.description !== undefined) { + patch.description = changes.description.trim(); + } - // return projects.size; - // } + if (changes.status !== undefined) { + patch.status = this.sanitizeStatus(changes.status); + } + + await this.repository.update(id, patch); + + const updated = await this.repository.findById(id); + if (!updated) { + throw new Error('Failed to update project'); + } + + return updated.toJson(); + } + + async deleteProject(id: string) { + const project = await this.repository.findById(id); + if (!project) { + throw new Error('Project not found'); + } + + await this.repository.deleteById(id); + } + + private normalizeSlug(value: string): string { + return value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + } + + private sanitizeStatus(status?: string): ProjectStatusValue { + return status === 'inactive' ? 'inactive' : 'active'; + } } diff --git a/src/modules/projects/index.ts b/src/modules/projects/index.ts index caa06a1..aab224f 100644 --- a/src/modules/projects/index.ts +++ b/src/modules/projects/index.ts @@ -1,7 +1,6 @@ -import { databaseClient } from '$lib/db'; import { ProjectService } from './application/project.service'; -import { SqliteProjectRepository } from './infrastructure/repositories/sqlite-project.repository'; +import { ProjectRepository } from './infrastructure/repositories/project.repostitory'; -const projectRepository = new SqliteProjectRepository(databaseClient); +const projectRepository = new ProjectRepository(); export const projectService = new ProjectService(projectRepository); diff --git a/src/routes/api/projects/+server.ts b/src/routes/api/projects/+server.ts index b98d165..6fb71d1 100644 --- a/src/routes/api/projects/+server.ts +++ b/src/routes/api/projects/+server.ts @@ -8,7 +8,7 @@ export async function GET({ locals }) { } try { - const projects = projectService.listProjects(); + const projects = await projectService.listProjects(); return json({ projects }); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; @@ -22,8 +22,20 @@ export async function POST({ request, locals }) { } try { - const { name } = await request.json(); - const project = projectService.createProject(String(name || '')); + const data = (await request.json()) as { + name?: string; + slug?: string; + description?: string; + status?: string; + }; + + const project = await projectService.createProject({ + name: String(data.name || ''), + slug: data.slug ? String(data.slug) : undefined, + description: data.description ? String(data.description) : undefined, + status: data.status ? String(data.status) : undefined, + }); + return json({ success: true, project }); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; diff --git a/src/routes/api/projects/[id]/+server.ts b/src/routes/api/projects/[id]/+server.ts new file mode 100644 index 0000000..7db04b0 --- /dev/null +++ b/src/routes/api/projects/[id]/+server.ts @@ -0,0 +1,58 @@ +import { json } from '@sveltejs/kit'; +import { projectService } from '../../../../modules/projects'; +import { can } from '../../../../modules/auth'; + +export async function GET({ params, locals }) { + if (!can(locals.user, 'stateiac:read')) { + return json({ error: 'Forbidden' }, { status: 403 }); + } + + try { + const project = await projectService.getProject(params.id); + return json({ project }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return json({ error: message }, { status: 404 }); + } +} + +export async function PATCH({ request, params, locals }) { + if (!can(locals.user, 'stateiac:update')) { + return json({ error: 'Forbidden' }, { status: 403 }); + } + + try { + const data = (await request.json()) as { + name?: string; + slug?: string; + description?: string; + status?: string; + }; + + const project = await projectService.updateProject(params.id, { + name: data.name, + slug: data.slug, + description: data.description, + status: data.status, + }); + + return json({ success: true, project }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return json({ error: message }, { status: 400 }); + } +} + +export async function DELETE({ params, locals }) { + if (!can(locals.user, 'stateiac:delete')) { + return json({ error: 'Forbidden' }, { status: 403 }); + } + + try { + await projectService.deleteProject(params.id); + return json({ success: true }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return json({ error: message }, { status: 400 }); + } +} diff --git a/src/routes/settings/projects/+page.svelte b/src/routes/settings/projects/+page.svelte new file mode 100644 index 0000000..a400caa --- /dev/null +++ b/src/routes/settings/projects/+page.svelte @@ -0,0 +1,434 @@ + + + + Projects - Settings + + +
+
+
+

Projects

+

+ Create, organize and manage the projects used across State IaC. +

+
+ + +
+ +
+
+ + +
+ + +
+ + {#if error} +
+ {error} +
+ {/if} + + {#if success} +
+ {success} +
+ {/if} + + {#if loading} +
+ Loading projects... +
+ {:else if filteredProjects.length === 0} +
+ {projects.length === 0 ? 'No projects found.' : 'No projects match your filters.'} +
+ {:else} +
+
+ + + + + + + + + + + + {#each filteredProjects as project (project.id)} + + + + + + + + {/each} + +
NameSlugStatusCreatedActions
+
+ +
+

{project.name}

+ {#if project.description} +

{project.description}

+ {/if} +
+
+
{project.slug} + + {project.status === 'active' ? 'Active' : 'Inactive'} + + {formatDate(project.createdAt)} +
+ + + View + + +
+
+
+
+ {/if} +
+ +{#if createModalOpen} + +
+ +
+{/if} + +{#if deleteModalProject} + +
+ +
+{/if} diff --git a/src/routes/settings/projects/[id]/+page.svelte b/src/routes/settings/projects/[id]/+page.svelte new file mode 100644 index 0000000..4230b14 --- /dev/null +++ b/src/routes/settings/projects/[id]/+page.svelte @@ -0,0 +1,312 @@ + + + + Project detail - Settings + + +
+ + + {#if error} +
+ {error} +
+ {/if} + + {#if success} +
+ {success} +
+ {/if} + + {#if loading} +
+ Loading project... +
+ {:else if !project} +
+ Project not found. +
+ {:else} +
+
+
+ +
+

{project.name}

+

+ Created {formatDate(project.createdAt)} · Updated {formatDate(project.updatedAt)} +

+
+
+ + +
+ +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+ {/if} +
+ +{#if deleteModalOpen && project} + +
+ +
+{/if} From e0548128f7b1718835912db551264dd342ca77d6 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 17:00:51 +0200 Subject: [PATCH 03/16] Removed Pulumi state and config! --- bun.lock | 55 -------- package.json | 1 - src/lib/database/sqlite.client.ts | 40 ------ src/lib/db.ts | 125 ------------------ .../config/application/config.service.ts | 22 --- .../application/storage-backend.service.ts | 50 ------- src/modules/config/domain/entities.ts | 51 ------- src/modules/config/domain/repositories.ts | 13 -- src/modules/config/index.ts | 11 -- .../repositories/sqlite-config.repository.ts | 75 ----------- .../sqlite-storage-backend.repository.ts | 89 ------------- src/routes/+layout.server.ts | 25 ++-- src/routes/pulumi-state/+page.server.ts | 81 ------------ src/routes/pulumi-state/+page.svelte | 31 ----- .../pulumi-state/[...id]/+page.server.ts | 68 ---------- src/routes/pulumi-state/[...id]/+page.svelte | 7 - src/routes/pulumi-state/backends/+page.svelte | 25 ---- .../pulumi-state/cli-guide/+page.svelte | 122 ----------------- 18 files changed, 13 insertions(+), 878 deletions(-) delete mode 100644 src/lib/database/sqlite.client.ts delete mode 100644 src/lib/db.ts delete mode 100644 src/modules/config/application/config.service.ts delete mode 100644 src/modules/config/application/storage-backend.service.ts delete mode 100644 src/modules/config/domain/entities.ts delete mode 100644 src/modules/config/domain/repositories.ts delete mode 100644 src/modules/config/index.ts delete mode 100644 src/modules/config/infrastructure/repositories/sqlite-config.repository.ts delete mode 100644 src/modules/config/infrastructure/repositories/sqlite-storage-backend.repository.ts delete mode 100644 src/routes/pulumi-state/+page.server.ts delete mode 100644 src/routes/pulumi-state/+page.svelte delete mode 100644 src/routes/pulumi-state/[...id]/+page.server.ts delete mode 100644 src/routes/pulumi-state/[...id]/+page.svelte delete mode 100644 src/routes/pulumi-state/backends/+page.svelte delete mode 100644 src/routes/pulumi-state/cli-guide/+page.svelte diff --git a/bun.lock b/bun.lock index 4879342..911687d 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,6 @@ "@aws-sdk/client-s3": "^3.500.0", "@getgitops/gitdb": "^0.2.0", "@google-cloud/storage": "^7.21.0", - "better-sqlite3": "^12.11.1", "lucide-svelte": "^0.320.0", }, "devDependencies": { @@ -424,22 +423,14 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="], - "better-sqlite3": ["better-sqlite3@12.11.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA=="], - "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], - "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], - - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], "brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], "browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="], - "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -454,8 +445,6 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - "code-red": ["code-red@1.0.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1", "acorn": "^8.10.0", "estree-walker": "^3.0.3", "periscopic": "^3.1.0" } }, "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -478,10 +467,6 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], - - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], @@ -548,8 +533,6 @@ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -568,8 +551,6 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], @@ -580,8 +561,6 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -594,8 +573,6 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], @@ -628,8 +605,6 @@ "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -640,8 +615,6 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], @@ -736,14 +709,8 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -752,12 +719,8 @@ "nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], - "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "node-abi": ["node-abi@3.94.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g=="], - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="], @@ -800,20 +763,14 @@ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.9.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg=="], "prettier-plugin-svelte": ["prettier-plugin-svelte@3.5.2", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ=="], - "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -842,10 +799,6 @@ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -880,10 +833,6 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tar-fs": ["tar-fs@2.1.5", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw=="], - - "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - "teeny-request": ["teeny-request@9.0.0", "", { "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.9", "stream-events": "^1.0.5", "uuid": "^9.0.0" } }, "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], @@ -902,8 +851,6 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -970,8 +917,6 @@ "http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "svelte-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], "svelte-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], diff --git a/package.json b/package.json index 9c89225..a7ca65c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@aws-sdk/client-s3": "^3.500.0", "@getgitops/gitdb": "^0.2.0", "@google-cloud/storage": "^7.21.0", - "better-sqlite3": "^12.11.1", "lucide-svelte": "^0.320.0" } } diff --git a/src/lib/database/sqlite.client.ts b/src/lib/database/sqlite.client.ts deleted file mode 100644 index 1b7f717..0000000 --- a/src/lib/database/sqlite.client.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type Database from 'better-sqlite3'; -import type { DatabaseClient, DatabaseParams } from './types'; - -export class SqliteDatabaseClient implements DatabaseClient { - constructor(private readonly sqlite: Database.Database) {} - - exec(sql: string): void { - this.sqlite.exec(sql); - } - - get(sql: string, params?: DatabaseParams): T | undefined { - if (params === undefined) { - return this.sqlite.prepare(sql).get() as T | undefined; - } - - return this.sqlite.prepare(sql).get(params as never) as T | undefined; - } - - all(sql: string, params?: DatabaseParams): T[] { - if (params === undefined) { - return this.sqlite.prepare(sql).all() as T[]; - } - - return this.sqlite.prepare(sql).all(params as never) as T[]; - } - - run(sql: string, params?: DatabaseParams): void { - if (params === undefined) { - this.sqlite.prepare(sql).run(); - return; - } - - this.sqlite.prepare(sql).run(params as never); - } - - transaction(callback: () => T): T { - const wrapped = this.sqlite.transaction(callback); - return wrapped(); - } -} diff --git a/src/lib/db.ts b/src/lib/db.ts deleted file mode 100644 index b37a6ae..0000000 --- a/src/lib/db.ts +++ /dev/null @@ -1,125 +0,0 @@ -import Database from 'better-sqlite3'; -import path from 'path'; -import fs from 'fs'; -import { SqliteDatabaseClient } from './database/sqlite.client'; - -const DB_DIR = path.resolve(process.cwd(), 'data', 'db'); -const DB_PATH = path.join(DB_DIR, 'states.sqlite'); - -if (!fs.existsSync(DB_DIR)) { - fs.mkdirSync(DB_DIR, { recursive: true }); -} - -export const db = new Database(DB_PATH); -export const databaseClient = new SqliteDatabaseClient(db); - -db.pragma('journal_mode = WAL'); - -db.exec(` - CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT, - password_hash TEXT NOT NULL, - role TEXT NOT NULL DEFAULT 'developer', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS config ( - id INTEGER PRIMARY KEY CHECK (id = 1), - auth_method TEXT NOT NULL DEFAULT 'none', - encryption_key TEXT, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS projects ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS storage_backends ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - provider TEXT NOT NULL, - bucket TEXT NOT NULL, - region TEXT, - access_key_id TEXT, - secret_access_key TEXT, - endpoint TEXT, - gcp_project_id TEXT, - gcp_credentials TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS stacks ( - id TEXT PRIMARY KEY, - project_id TEXT NOT NULL, - name TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS states ( - id TEXT PRIMARY KEY, - stack_id TEXT NOT NULL, - version INTEGER NOT NULL, - checkpoint JSON NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (stack_id) REFERENCES stacks (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS history ( - id TEXT PRIMARY KEY, - stack_id TEXT NOT NULL, - kind TEXT NOT NULL, - start_time INTEGER NOT NULL, - end_time INTEGER, - message TEXT, - environment JSON, - config JSON, - result TEXT, - resource_changes JSON, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (stack_id) REFERENCES stacks (id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS api_keys ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - name TEXT NOT NULL, - key_prefix TEXT NOT NULL, - key_hash TEXT NOT NULL, - last_used_at DATETIME, - revoked_at DATETIME, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE - ); -`); - -try { - db.exec('ALTER TABLE config ADD COLUMN encryption_key TEXT'); -} catch { - // Ignore if column already exists -} - -const migrations = [ - 'ALTER TABLE users ADD COLUMN email TEXT', - 'ALTER TABLE config ADD COLUMN public_access INTEGER DEFAULT 1', - 'ALTER TABLE config ADD COLUMN google_sso_enabled INTEGER DEFAULT 0', - 'ALTER TABLE config ADD COLUMN google_client_id TEXT', - 'ALTER TABLE config ADD COLUMN google_client_secret TEXT', - 'ALTER TABLE config ADD COLUMN saml_enabled INTEGER DEFAULT 0', - 'ALTER TABLE config ADD COLUMN saml_entry_point TEXT', - 'ALTER TABLE config ADD COLUMN saml_issuer TEXT', - 'ALTER TABLE config ADD COLUMN saml_cert TEXT', - "ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'developer'", -]; - -for (const query of migrations) { - try { - db.exec(query); - } catch { - // Ignore if already applied - } -} diff --git a/src/modules/config/application/config.service.ts b/src/modules/config/application/config.service.ts deleted file mode 100644 index 39fdac7..0000000 --- a/src/modules/config/application/config.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { InstanceConfig } from '../domain/entities'; -import type { ConfigRepository } from '../domain/repositories'; - -const DEFAULT_CONFIG: InstanceConfig = { - publicAccess: false, - googleSsoEnabled: false, - samlEnabled: false, -}; - -export class ConfigService { - constructor(private readonly configRepository: ConfigRepository) {} - - getConfig(): InstanceConfig | null { - return this.configRepository.getConfig(); - } - - saveConfig(partial: Partial): void { - const existing = this.getConfig() ?? DEFAULT_CONFIG; - const merged: InstanceConfig = { ...existing, ...partial }; - this.configRepository.saveConfig(merged); - } -} diff --git a/src/modules/config/application/storage-backend.service.ts b/src/modules/config/application/storage-backend.service.ts deleted file mode 100644 index 70976f6..0000000 --- a/src/modules/config/application/storage-backend.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import crypto from 'crypto'; -import type { - StorageBackend, - StorageBackendPublic, - UpsertStorageBackendInput, -} from '../domain/entities'; -import type { StorageBackendRepository } from '../domain/repositories'; - -export class StorageBackendService { - constructor(private readonly repository: StorageBackendRepository) {} - - list(): StorageBackendPublic[] { - return this.repository.list().map((backend) => ({ - ...backend, - secretAccessKey: backend.secretAccessKey ? '***' : null, - gcpCredentials: backend.gcpCredentials ? '***' : null, - })); - } - - getById(id: string): StorageBackend | null { - return this.repository.findById(id); - } - - upsert(input: UpsertStorageBackendInput): string { - const id = input.id || crypto.randomUUID(); - const existing = input.id ? this.repository.findById(input.id) : null; - - const secretAccessKey = - input.secretAccessKey === '***' ? (existing?.secretAccessKey ?? null) : (input.secretAccessKey ?? null); - const gcpCredentials = - input.gcpCredentials === '***' ? (existing?.gcpCredentials ?? null) : (input.gcpCredentials ?? null); - - return this.repository.upsert({ - id, - name: input.name || 'Unnamed Backend', - provider: input.provider, - bucket: input.bucket, - region: input.region ?? null, - accessKeyId: input.accessKeyId ?? null, - secretAccessKey, - endpoint: input.endpoint ?? null, - gcpProjectId: input.gcpProjectId ?? null, - gcpCredentials, - }); - } - - deleteById(id: string): void { - this.repository.deleteById(id); - } -} diff --git a/src/modules/config/domain/entities.ts b/src/modules/config/domain/entities.ts deleted file mode 100644 index 142783b..0000000 --- a/src/modules/config/domain/entities.ts +++ /dev/null @@ -1,51 +0,0 @@ -export interface InstanceConfig { - publicAccess: boolean; - googleSsoEnabled: boolean; - googleClientId?: string | null; - googleClientSecret?: string | null; - samlEnabled: boolean; - samlEntryPoint?: string | null; - samlIssuer?: string | null; - samlCert?: string | null; -} - -export type StorageProvider = 's3' | 'gcs'; - -export interface StorageBackend { - id: string; - name: string; - provider: StorageProvider; - bucket: string; - region?: string | null; - accessKeyId?: string | null; - secretAccessKey?: string | null; - endpoint?: string | null; - gcpProjectId?: string | null; - gcpCredentials?: string | null; -} - -export interface StorageBackendPublic { - id: string; - name: string; - provider: StorageProvider; - bucket: string; - region?: string | null; - accessKeyId?: string | null; - secretAccessKey?: string | null; - endpoint?: string | null; - gcpProjectId?: string | null; - gcpCredentials?: string | null; -} - -export interface UpsertStorageBackendInput { - id?: string; - name?: string; - provider: StorageProvider; - bucket: string; - region?: string | null; - accessKeyId?: string | null; - secretAccessKey?: string | null; - endpoint?: string | null; - gcpProjectId?: string | null; - gcpCredentials?: string | null; -} diff --git a/src/modules/config/domain/repositories.ts b/src/modules/config/domain/repositories.ts deleted file mode 100644 index 3a849fc..0000000 --- a/src/modules/config/domain/repositories.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { InstanceConfig, StorageBackend } from './entities'; - -export interface ConfigRepository { - getConfig(): InstanceConfig | null; - saveConfig(config: InstanceConfig): void; -} - -export interface StorageBackendRepository { - list(): StorageBackend[]; - findById(id: string): StorageBackend | null; - upsert(backend: StorageBackend): string; - deleteById(id: string): void; -} diff --git a/src/modules/config/index.ts b/src/modules/config/index.ts deleted file mode 100644 index ff4f7fa..0000000 --- a/src/modules/config/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { databaseClient } from '$lib/db'; -import { ConfigService } from './application/config.service'; -import { StorageBackendService } from './application/storage-backend.service'; -import { SqliteConfigRepository } from './infrastructure/repositories/sqlite-config.repository'; -import { SqliteStorageBackendRepository } from './infrastructure/repositories/sqlite-storage-backend.repository'; - -const configRepository = new SqliteConfigRepository(databaseClient); -const storageBackendRepository = new SqliteStorageBackendRepository(databaseClient); - -export const configService = new ConfigService(configRepository); -export const storageBackendService = new StorageBackendService(storageBackendRepository); diff --git a/src/modules/config/infrastructure/repositories/sqlite-config.repository.ts b/src/modules/config/infrastructure/repositories/sqlite-config.repository.ts deleted file mode 100644 index dad8522..0000000 --- a/src/modules/config/infrastructure/repositories/sqlite-config.repository.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { DatabaseClient } from '$lib/database/types'; -import type { InstanceConfig } from '../../domain/entities'; -import type { ConfigRepository } from '../../domain/repositories'; - -type ConfigRow = { - public_access?: number | null; - google_sso_enabled?: number | null; - google_client_id?: string | null; - google_client_secret?: string | null; - saml_enabled?: number | null; - saml_entry_point?: string | null; - saml_issuer?: string | null; - saml_cert?: string | null; -}; - -export class SqliteConfigRepository implements ConfigRepository { - constructor(private readonly db: DatabaseClient) {} - - getConfig(): InstanceConfig | null { - const row = this.db.get('SELECT * FROM config WHERE id = 1'); - if (!row) { - return null; - } - - const publicAccess = row.public_access !== undefined ? row.public_access === 1 : true; - - return { - publicAccess, - googleSsoEnabled: row.google_sso_enabled === 1, - googleClientId: row.google_client_id, - googleClientSecret: row.google_client_secret, - samlEnabled: row.saml_enabled === 1, - samlEntryPoint: row.saml_entry_point, - samlIssuer: row.saml_issuer, - samlCert: row.saml_cert, - }; - } - - saveConfig(config: InstanceConfig): void { - this.db.run( - ` - INSERT INTO config ( - id, updated_at, public_access, google_sso_enabled, - google_client_id, google_client_secret, - saml_enabled, saml_entry_point, saml_issuer, saml_cert - ) - VALUES ( - 1, CURRENT_TIMESTAMP, @publicAccess, @googleSsoEnabled, - @googleClientId, @googleClientSecret, - @samlEnabled, @samlEntryPoint, @samlIssuer, @samlCert - ) - ON CONFLICT(id) DO UPDATE SET - public_access = excluded.public_access, - google_sso_enabled = excluded.google_sso_enabled, - google_client_id = excluded.google_client_id, - google_client_secret = excluded.google_client_secret, - saml_enabled = excluded.saml_enabled, - saml_entry_point = excluded.saml_entry_point, - saml_issuer = excluded.saml_issuer, - saml_cert = excluded.saml_cert, - updated_at = CURRENT_TIMESTAMP - `, - { - publicAccess: config.publicAccess ? 1 : 0, - googleSsoEnabled: config.googleSsoEnabled ? 1 : 0, - googleClientId: config.googleClientId || null, - googleClientSecret: config.googleClientSecret || null, - samlEnabled: config.samlEnabled ? 1 : 0, - samlEntryPoint: config.samlEntryPoint || null, - samlIssuer: config.samlIssuer || null, - samlCert: config.samlCert || null, - }, - ); - } -} diff --git a/src/modules/config/infrastructure/repositories/sqlite-storage-backend.repository.ts b/src/modules/config/infrastructure/repositories/sqlite-storage-backend.repository.ts deleted file mode 100644 index 6a73ae0..0000000 --- a/src/modules/config/infrastructure/repositories/sqlite-storage-backend.repository.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { DatabaseClient } from '$lib/database/types'; -import type { StorageBackend, StorageProvider } from '../../domain/entities'; -import type { StorageBackendRepository } from '../../domain/repositories'; - -type StorageBackendRow = { - id: string; - name: string; - provider: string; - bucket: string; - region?: string | null; - access_key_id?: string | null; - secret_access_key?: string | null; - endpoint?: string | null; - gcp_project_id?: string | null; - gcp_credentials?: string | null; -}; - -export class SqliteStorageBackendRepository implements StorageBackendRepository { - constructor(private readonly db: DatabaseClient) {} - - list(): StorageBackend[] { - const rows = this.db.all('SELECT * FROM storage_backends ORDER BY created_at DESC'); - return rows.map((row) => this.toEntity(row)); - } - - findById(id: string): StorageBackend | null { - const row = this.db.get('SELECT * FROM storage_backends WHERE id = ?', [id]); - return row ? this.toEntity(row) : null; - } - - upsert(backend: StorageBackend): string { - this.db.run( - ` - INSERT INTO storage_backends ( - id, name, provider, bucket, region, access_key_id, secret_access_key, endpoint, gcp_project_id, gcp_credentials - ) VALUES ( - @id, @name, @provider, @bucket, @region, @accessKeyId, @secretAccessKey, @endpoint, @gcpProjectId, @gcpCredentials - ) - ON CONFLICT(id) DO UPDATE SET - name = excluded.name, - provider = excluded.provider, - bucket = excluded.bucket, - region = excluded.region, - access_key_id = excluded.access_key_id, - secret_access_key = excluded.secret_access_key, - endpoint = excluded.endpoint, - gcp_project_id = excluded.gcp_project_id, - gcp_credentials = excluded.gcp_credentials - `, - { - id: backend.id, - name: backend.name, - provider: backend.provider, - bucket: backend.bucket, - region: backend.region || null, - accessKeyId: backend.accessKeyId || null, - secretAccessKey: backend.secretAccessKey || null, - endpoint: backend.endpoint || null, - gcpProjectId: backend.gcpProjectId || null, - gcpCredentials: backend.gcpCredentials || null, - }, - ); - - return backend.id; - } - - deleteById(id: string): void { - this.db.run('DELETE FROM storage_backends WHERE id = ?', [id]); - } - - private toEntity(row: StorageBackendRow): StorageBackend { - return { - id: row.id, - name: row.name, - provider: this.normalizeProvider(row.provider), - bucket: row.bucket, - region: row.region, - accessKeyId: row.access_key_id, - secretAccessKey: row.secret_access_key, - endpoint: row.endpoint, - gcpProjectId: row.gcp_project_id, - gcpCredentials: row.gcp_credentials, - }; - } - - private normalizeProvider(provider: string): StorageProvider { - return provider === 'gcs' ? 'gcs' : 's3'; - } -} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 3ce3a4f..50c23e9 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,21 +1,22 @@ -import { configService, storageBackendService } from '../modules/config'; +// import { configService, storageBackendService } from '../modules/config'; export async function load({ cookies, locals }) { - const config = configService.getConfig(); - const backends = storageBackendService.list(); + // const config = configService.getConfig(); + // const backends = storageBackendService.list(); - let activeBackendId = cookies.get('active_backend'); - let activeBackend = backends.find((backend) => backend.id === activeBackendId); + // let activeBackendId = cookies.get('active_backend'); + // let activeBackend = backends.find((backend) => backend.id === activeBackendId); - if (!activeBackend && backends.length > 0) { - activeBackend = backends[0]; - activeBackendId = activeBackend.id; - } + // if (!activeBackend && backends.length > 0) { + // activeBackend = backends[0]; + // activeBackendId = activeBackend.id; + // } return { - isConfigured: !!config && backends.length > 0, - backends, - activeBackendId, + // isConfigured: !!config && backends.length > 0, + isConfigured: true, + // backends, + // activeBackendId, user: locals.user, }; } diff --git a/src/routes/pulumi-state/+page.server.ts b/src/routes/pulumi-state/+page.server.ts deleted file mode 100644 index 75cd2e0..0000000 --- a/src/routes/pulumi-state/+page.server.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { error } from '@sveltejs/kit'; -import { storageBackendService } from '../../modules/config'; -import { projectService } from '../../modules/projects'; -import { storageService } from '../../modules/storage'; -import { can } from '../../modules/auth'; - -export async function load({ cookies, locals }) { - if (!can(locals.user, 'stateiac:read')) { - throw error(403, 'Forbidden'); - } - - const backends = storageBackendService.list(); - - if (!backends.length) { - return { - hasBackends: false, - }; - } - - const requestedActiveId = cookies.get('active_backend') || backends[0].id; - const activeBackend = - storageBackendService.getById(requestedActiveId) || storageBackendService.getById(backends[0].id) || backends[0]; - const config = activeBackend; - - if (!config) { - return { - hasBackends: false, - }; - } - - const dbProjects = projectService.listProjects(); - - try { - const { states: files, locks } = await storageService.listPulumiStates(config); - const stateSummaries: Record< - string, - { version?: string; resourceCount: number; error?: boolean } - > = {}; - - const fetchPromises = files.map(async (fileKey) => { - try { - const state = await storageService.getPulumiState(config, fileKey); - stateSummaries[fileKey] = { - version: state.version, - resourceCount: state.checkpoint?.latest?.resources?.length || 0, - }; - } catch { - stateSummaries[fileKey] = { resourceCount: 0, error: true }; - } - }); - - await Promise.allSettled(fetchPromises); - - return { - hasBackends: true, - files, - locks, - stateSummaries, - dbProjects, - backends: backends.map((backend) => ({ id: backend.id, name: backend.name })), - activeBackendId: activeBackend.id, - bucket: config.bucket, - provider: config.provider, - region: config.region, - }; - } catch (error: unknown) { - return { - hasBackends: true, - files: [], - locks: [], - stateSummaries: {}, - dbProjects, - backends: backends.map((backend) => ({ id: backend.id, name: backend.name })), - activeBackendId: activeBackend.id, - bucket: config.bucket, - provider: config.provider, - region: config.region, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } -} \ No newline at end of file diff --git a/src/routes/pulumi-state/+page.svelte b/src/routes/pulumi-state/+page.svelte deleted file mode 100644 index 798831c..0000000 --- a/src/routes/pulumi-state/+page.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - Pulumi State - GitVault Suite - - -{#if !data.hasBackends} -
-
- -
-

No storage backend configured

-

- Pulumi State needs at least one storage backend to read stacks and checkpoints. -

- - Configure backends - - -
-{:else} - -{/if} \ No newline at end of file diff --git a/src/routes/pulumi-state/[...id]/+page.server.ts b/src/routes/pulumi-state/[...id]/+page.server.ts deleted file mode 100644 index 3d42177..0000000 --- a/src/routes/pulumi-state/[...id]/+page.server.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { storageBackendService } from '../../../modules/config'; -import { storageService } from '../../../modules/storage'; -import { redirect, error } from '@sveltejs/kit'; -import { can } from '../../../modules/auth'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ params, url, cookies, locals }) => { - if (!can(locals.user, 'stateiac:read')) { - throw error(403, 'Forbidden'); - } - - const backends = storageBackendService.list(); - if (!backends.length) throw redirect(302, '/pulumi-state/backends'); - - const activeId = cookies.get('active_backend') || backends[0].id; - const config = storageBackendService.getById(activeId) || storageBackendService.getById(backends[0].id); - - if (!config) throw redirect(302, '/pulumi-state/backends'); - - const cleanId = params.id; - const checkpointKey = url.searchParams.get('checkpoint'); - - let state = null; - let finalKey = ''; - let history: Array> = []; - - if (checkpointKey) { - try { - state = await storageService.getPulumiState(config, checkpointKey); - finalKey = checkpointKey; - } catch (error: unknown) { - console.error('Failed to load checkpoint state', error); - } - } else { - const tryKeys = [`.pulumi/stacks/${cleanId}.json`, `${cleanId}.json`, cleanId]; - - for (const key of tryKeys) { - try { - state = await storageService.getPulumiState(config, key); - finalKey = key; - break; - } catch (error: unknown) { - console.error('Failed to load stack state', error); - } - } - } - - if (!state) { - return { - error: `Stack not found for ID '${cleanId}'.`, - cleanId, - }; - } - - try { - history = await storageService.listPulumiHistory(config, cleanId); - } catch (error) { - console.error('Failed to load history', error); - } - - return { - state, - key: finalKey, - cleanId, - history, - isHistorical: !!checkpointKey, - }; -}; diff --git a/src/routes/pulumi-state/[...id]/+page.svelte b/src/routes/pulumi-state/[...id]/+page.svelte deleted file mode 100644 index 2ee2890..0000000 --- a/src/routes/pulumi-state/[...id]/+page.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/src/routes/pulumi-state/backends/+page.svelte b/src/routes/pulumi-state/backends/+page.svelte deleted file mode 100644 index a80b4fa..0000000 --- a/src/routes/pulumi-state/backends/+page.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - - Storage Backends - Pulumi State - - - - - \ No newline at end of file diff --git a/src/routes/pulumi-state/cli-guide/+page.svelte b/src/routes/pulumi-state/cli-guide/+page.svelte deleted file mode 100644 index 18df412..0000000 --- a/src/routes/pulumi-state/cli-guide/+page.svelte +++ /dev/null @@ -1,122 +0,0 @@ - - - - CLI Guide - Pulumi State - - - - -
-
-
-
- -
-
-

Pulumi CLI Guide

-

- Basic workflow from login to create/select/remove/destroy stacks. -

-
-
-
- -
-

- - 1) Login to your backend -

-

- Use the backend URL that matches your selected storage provider. -

-
-
-

S3

-
pulumi login s3://<bucket>?region=<region>
-
-
-

GCS

-
pulumi login gs://<bucket>
-
-
-
- -
-

- - 2) Create a Pulumi project and stack -

-
-
pulumi new typescript --name my-project
-pulumi stack init dev
-pulumi stack select dev
-
-

- If your stack naming convention includes project prefixes, use format like - my-project/dev. -

-
- -
-

- - 3) Day-to-day stack commands -

-
-
-

Select Stack

-
pulumi stack select my-project/dev
-
-
-

Preview

-
pulumi preview
-
-
-

Deploy

-
pulumi up
-
-
-

Check Outputs

-
pulumi stack output
-pulumi stack output --json
-
-
-
- -
-

- - 4) Remove resources and delete stacks -

-
-
# Destroy infrastructure in selected stack
-pulumi destroy
-
-# Remove stack from backend (after destroy)
-pulumi stack rm my-project/dev
-
-# Force remove if needed
-pulumi stack rm my-project/dev --force
-
-

- Recommended order: destroy first, then - stack rm. -

-
-
From 8dbf3645476582243139155d4e0a08b342909c26 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 17:02:28 +0200 Subject: [PATCH 04/16] fixes --- src/routes/api/config/+server.ts | 38 -------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 src/routes/api/config/+server.ts diff --git a/src/routes/api/config/+server.ts b/src/routes/api/config/+server.ts deleted file mode 100644 index 23d8528..0000000 --- a/src/routes/api/config/+server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { json } from '@sveltejs/kit'; -import { configService } from '../../../modules/config'; -import type { InstanceConfig } from '../../../modules/config/domain/entities'; - -export async function GET() { - const config = configService.getConfig(); - - if (!config) { - return json({ configured: false }); - } - - const safeConfig = { ...config }; - if (safeConfig.googleClientSecret) safeConfig.googleClientSecret = '***'; - if (safeConfig.samlCert) safeConfig.samlCert = '***'; - - return json({ configured: true, config: safeConfig }); -} - -export async function POST({ request }) { - try { - const newConfig = (await request.json()) as Partial; - const existing = configService.getConfig(); - - if (newConfig.googleClientSecret === '***') { - newConfig.googleClientSecret = existing?.googleClientSecret || null; - } - - if (newConfig.samlCert === '***') { - newConfig.samlCert = existing?.samlCert || null; - } - - configService.saveConfig(newConfig); - return json({ success: true }); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : 'Failed to save config'; - return json({ error: message }, { status: 500 }); - } -} \ No newline at end of file From d329008c5ad1cef6d8113bb79d872420523f1048 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 17:04:34 +0200 Subject: [PATCH 05/16] fix config unused --- src/routes/profile/+page.server.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/routes/profile/+page.server.ts b/src/routes/profile/+page.server.ts index d26db62..8719a70 100644 --- a/src/routes/profile/+page.server.ts +++ b/src/routes/profile/+page.server.ts @@ -1,6 +1,5 @@ import { fail, redirect } from '@sveltejs/kit'; import { apiKeysService, profileService } from '../../modules/auth'; -import { storageBackendService } from '../../modules/config'; function expiresAtFromDays(expiresInDays: string | null): string | null { if (!expiresInDays) { @@ -28,10 +27,6 @@ export async function load({ locals, cookies }) { throw redirect(302, '/login'); } - const backends = storageBackendService.list(); - const activeBackendId = cookies.get('active_backend') || backends[0]?.id || null; - const activeBackend = backends.find((backend) => backend.id === activeBackendId) || null; - const gcpConnected = activeBackend?.provider === 'gcs'; const apiKeys = await apiKeysService.listActiveApiKeys(user.id); @@ -42,7 +37,7 @@ export async function load({ locals, cookies }) { email: user.email, role: user.role, }, - gcpConnected, + gcpConnected: false, apiKeys, }; } From c5f41c43684e50a1843536ef883cbe2e55e93be6 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 17:49:52 +0200 Subject: [PATCH 06/16] project --- src/lib/components/AppSidebar.svelte | 25 +- .../projects/application/project.service.ts | 8 + src/routes/+page.server.ts | 10 +- src/routes/overview/+page.server.ts | 14 + src/routes/overview/+page.svelte | 91 ++++ .../{projects => project}/+page.server.ts | 2 +- src/routes/project/[slug]/+layout.server.ts | 16 + src/routes/project/[slug]/+layout.svelte | 68 +++ .../[id] => project/[slug]}/+page.svelte | 223 ++++----- src/routes/project/[slug]/audit/+page.svelte | 26 + .../project/[slug]/overview/+page.svelte | 73 +++ .../[slug]/roles-permissions/+page.svelte | 24 + .../project/[slug]/users-groups/+page.svelte | 26 + src/routes/projects/+page.svelte | 372 -------------- src/routes/projects/[...id]/+page.server.ts | 6 - src/routes/projects/[...id]/+page.svelte | 457 ------------------ src/routes/settings/projects/+page.svelte | 6 +- 17 files changed, 454 insertions(+), 993 deletions(-) create mode 100644 src/routes/overview/+page.server.ts create mode 100644 src/routes/overview/+page.svelte rename src/routes/{projects => project}/+page.server.ts (60%) create mode 100644 src/routes/project/[slug]/+layout.server.ts create mode 100644 src/routes/project/[slug]/+layout.svelte rename src/routes/{settings/projects/[id] => project/[slug]}/+page.svelte (50%) create mode 100644 src/routes/project/[slug]/audit/+page.svelte create mode 100644 src/routes/project/[slug]/overview/+page.svelte create mode 100644 src/routes/project/[slug]/roles-permissions/+page.svelte create mode 100644 src/routes/project/[slug]/users-groups/+page.svelte delete mode 100644 src/routes/projects/+page.svelte delete mode 100644 src/routes/projects/[...id]/+page.server.ts delete mode 100644 src/routes/projects/[...id]/+page.svelte diff --git a/src/lib/components/AppSidebar.svelte b/src/lib/components/AppSidebar.svelte index 80a43d2..027f9fa 100644 --- a/src/lib/components/AppSidebar.svelte +++ b/src/lib/components/AppSidebar.svelte @@ -9,6 +9,7 @@ GitBranch, HelpCircle, KeyRound, + LayoutDashboard, Lock, PanelLeftClose, PanelLeftOpen, @@ -23,6 +24,16 @@ let currentPath = pathname; const sections = [ + { + title: 'Inicio', + items: [ + { + label: 'Overview', + href: '/overview', + icon: LayoutDashboard, + }, + ], + }, { title: 'Seguridad', items: [ @@ -74,6 +85,11 @@ ]; const settingsItems = [ + { + label: 'Projects', + href: '/settings/projects', + icon: FolderKanban, + }, { label: 'Autentication', href: '/settings/authentication', @@ -84,11 +100,6 @@ href: '/settings/roles-permissions', icon: Users, }, - { - label: 'Projects', - href: '/settings/projects', - icon: FolderKanban, - }, { label: 'System & Backup', href: '/settings/system-backup', @@ -116,7 +127,7 @@ return '/pulumi-state/cli-guide'; } - if (currentPath.startsWith('/pulumi-state') || currentPath.startsWith('/projects')) { + if (currentPath.startsWith('/pulumi-state')) { return '/pulumi-state'; } @@ -149,7 +160,7 @@ settingsMenuOpen = true; } - $: if (currentPath.startsWith('/pulumi-state') || currentPath.startsWith('/projects')) { + $: if (currentPath.startsWith('/pulumi-state')) { pulumiStateMenuOpen = true; } diff --git a/src/modules/projects/application/project.service.ts b/src/modules/projects/application/project.service.ts index b1d62e9..43c7d41 100644 --- a/src/modules/projects/application/project.service.ts +++ b/src/modules/projects/application/project.service.ts @@ -19,6 +19,14 @@ export class ProjectService { return project.toJson(); } + async getProjectBySlug(slug: string) { + const project = await this.repository.findBySlug(slug); + if (!project) { + throw new Error('Project not found'); + } + return project.toJson(); + } + async createProject(input: { name: string; slug?: string; diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 9e07b6d..957b07d 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,11 +1,5 @@ import { redirect } from '@sveltejs/kit'; -export async function load({ parent }) { - const { isConfigured } = await parent(); - - if (isConfigured) { - throw redirect(302, '/pulumi-state'); - } - - throw redirect(302, '/settings'); +export function load() { + throw redirect(302, '/overview'); } diff --git a/src/routes/overview/+page.server.ts b/src/routes/overview/+page.server.ts new file mode 100644 index 0000000..cea2664 --- /dev/null +++ b/src/routes/overview/+page.server.ts @@ -0,0 +1,14 @@ +import { error } from '@sveltejs/kit'; +import { projectService } from '../../modules/projects'; +import { can } from '../../modules/auth'; + +export async function load({ locals }) { + if (!can(locals.user, 'stateiac:read')) { + throw error(403, 'Forbidden'); + } + + const projects = await projectService.listProjects(); + const activeProjects = projects.filter((project) => project.status === 'active'); + + return { projects: activeProjects }; +} diff --git a/src/routes/overview/+page.svelte b/src/routes/overview/+page.svelte new file mode 100644 index 0000000..b24b748 --- /dev/null +++ b/src/routes/overview/+page.svelte @@ -0,0 +1,91 @@ + + + + Overview - GitVault Suite + + +
+
+

Overview

+

Selecciona un proyecto activo para ver su detalle.

+
+ +
+ + +
+ + {#if filteredProjects.length === 0} +
+ {data.projects.length === 0 + ? 'No hay proyectos activos.' + : 'Ningún proyecto coincide con la búsqueda.'} +
+ {:else} +
+ {#each filteredProjects as project (project.id)} + +
+
+ +

{project.name}

+
+ {#if project.description} +

{project.description}

+ {/if} +
+ +
+ + Active + + +
+
+ {/each} +
+ {/if} +
diff --git a/src/routes/projects/+page.server.ts b/src/routes/project/+page.server.ts similarity index 60% rename from src/routes/projects/+page.server.ts rename to src/routes/project/+page.server.ts index 29dfdcd..3ff99af 100644 --- a/src/routes/projects/+page.server.ts +++ b/src/routes/project/+page.server.ts @@ -1,5 +1,5 @@ import { redirect } from '@sveltejs/kit'; export function load() { - throw redirect(302, '/pulumi-state'); + throw redirect(302, '/settings/projects'); } diff --git a/src/routes/project/[slug]/+layout.server.ts b/src/routes/project/[slug]/+layout.server.ts new file mode 100644 index 0000000..bd8e1c9 --- /dev/null +++ b/src/routes/project/[slug]/+layout.server.ts @@ -0,0 +1,16 @@ +import { error } from '@sveltejs/kit'; +import { projectService } from '../../../modules/projects'; +import { can } from '../../../modules/auth'; + +export async function load({ params, locals }) { + if (!can(locals.user, 'stateiac:read')) { + throw error(403, 'Forbidden'); + } + + try { + const project = await projectService.getProjectBySlug(params.slug); + return { project }; + } catch { + throw error(404, 'Project not found'); + } +} diff --git a/src/routes/project/[slug]/+layout.svelte b/src/routes/project/[slug]/+layout.svelte new file mode 100644 index 0000000..645611c --- /dev/null +++ b/src/routes/project/[slug]/+layout.svelte @@ -0,0 +1,68 @@ + + +
+
+
+ + + Back to projects + + +
+ +

{project.name}

+
+ + +
+
+ +
+
+
diff --git a/src/routes/settings/projects/[id]/+page.svelte b/src/routes/project/[slug]/+page.svelte similarity index 50% rename from src/routes/settings/projects/[id]/+page.svelte rename to src/routes/project/[slug]/+page.svelte index 4230b14..c8d854c 100644 --- a/src/routes/settings/projects/[id]/+page.svelte +++ b/src/routes/project/[slug]/+page.svelte @@ -1,8 +1,6 @@ - Project detail - Settings + Información - {project.name}
- - {#if error}
{error} @@ -160,112 +125,94 @@
{/if} - {#if loading} -
- Loading project... -
- {:else if !project} +
- Project not found. -
- {:else} -
-
+ Created {formatDate(project.createdAt)} · Updated {formatDate(project.updatedAt)} +

+ + -
- -
-
-
- - -
- -
- - -
-
+ + Delete project + +
+
+
- - + />
-
- + - + />
-
-
+ +
+ - - {saving ? 'Saving...' : 'Save changes'} - +
-
- {/if} +
+ +
+ +
+
-{#if deleteModalOpen && project} +{#if deleteModalOpen} - - CLI Setup - -
- - -
- {#each filteredProjects as project} -
-
-
-
- -
-
-

{project.name}

-

Project ID: {project.id}

-
-
-
- - -
-
- - -
- {/each} - - {#if filteredProjects.length === 0 && !data.error} -
- -

No projects found.

-

Try adjusting your search or create a new project.

-
- {/if} -
- -{#if actionModalType !== null && actionProject !== null} - - - -
-
- {#if actionModalType === 'rename'} -

Rename Project

-

- Use the Pulumi CLI to rename stack names and update your local Pulumi project - structure. -

- {:else} -

Delete Project

-

- This only removes the local registry entry. The remote state data in your storage - backend remains untouched. -

- {/if} -
- -
-
- -
-
-
{actionModalType === 'rename'
-                ? `# 1. Update the 'name' field in your Pulumi.yaml\n# 2. Rename your stack(s)\npulumi stack rename  -s ${actionProject?.id}/`
-                : `# 1. Destroy resources (optional but recommended)\npulumi destroy -s ${actionProject?.id}/\n\n# 2. Remove the stack from the backend\npulumi stack rm ${actionProject?.id}/ --force`}
-
-
-
- - -{/if} diff --git a/src/routes/projects/[...id]/+page.server.ts b/src/routes/projects/[...id]/+page.server.ts deleted file mode 100644 index 3bb6906..0000000 --- a/src/routes/projects/[...id]/+page.server.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export function load({ params, url }: { params: { id: string }; url: URL }) { - const query = url.search || ''; - throw redirect(302, `/pulumi-state/${params.id}${query}`); -} diff --git a/src/routes/projects/[...id]/+page.svelte b/src/routes/projects/[...id]/+page.svelte deleted file mode 100644 index c292ad9..0000000 --- a/src/routes/projects/[...id]/+page.svelte +++ /dev/null @@ -1,457 +0,0 @@ - - - - {getStackName(data.cleanId || '')} - Pulumi State UI - - - - -{#if data.error} -
-

Failed to load Stack

-

{data.error}

-
-{:else if data.state} - {#if data.isHistorical} -
-
- -
-

Historical State View

-

- You are inspecting a checkpoint from the past. The data below does not represent the - current live state. -

-
-
- Return to Current State -
- {/if} - -
-
-

{getStackName(data.cleanId)}

-

Actual file: {data.key}

-
-
-
-

Resources

-

- {data.state?.checkpoint?.latest?.resources?.length || 0} -

-
-
-

Version

-

{data.state.version}

-
-
-
- -
-
-
- - - - -
- - {#if activeTab === 'resources'} -
-
-
-

Resources

-
-
- - -
- -
-
- -
- {#if filteredResourceRoots.length > 0} - {#each filteredResourceRoots as rootNode} - - {/each} - {:else} -

No resources match your search.

- {/if} -
-
-
- {:else if activeTab === 'configuration'} -
- {#if Object.keys(environmentConfig).length > 0} -
- {:else} -
- -

No environment configuration found.

-
- {/if} -
- {:else if activeTab === 'outputs'} -
- {#if Object.keys(stackOutputs).length > 0} -
- {:else} -
- -

No stack outputs found.

-
- {/if} -
- {:else if activeTab === 'history'} -
- {#if data.history && data.history.length > 0} -
- {#each data.history as update} -
-
-
-
- {update.result} - {update.kind || 'update'} -
-

- {update.message || 'No update message'} -

-
-
-
- {formatDate(update.startTime)} - {#if update.environment && update.environment['git.author']} - by {update.environment['git.author']} - {/if} -
- {#if update.key} - - Inspect State - - {/if} -
-
- {#if update.resourceChanges} -
- {#each Object.entries(update.resourceChanges) as [operation, count]} -
- {operation}: {count} -
- {/each} -
- {/if} -
- {/each} -
- {:else} -
- -

No history found for this stack.

-
- {/if} -
- {/if} -
- -
-
-
-

- Resource Inspector -

-
- -
- {#if selectedResource} -
- - {#if selectedResource.custom} - Custom Resource - {:else} - Component Resource - {/if} - -

- {selectedResource.urn.split('::').pop()} -

-
- -
-
-

- Type -

-

- {selectedResource.type} -

-
-
-

URN

-

- {selectedResource.urn} -

-
- {#if selectedResource.provider} -
-

- Provider -

-

- {selectedResource.provider.split('::')[0]} -

-
- {/if} -
- {#if selectedResource.inputs && Object.keys(selectedResource.inputs).length > 0} -
-

- Inputs -

- -
- {/if} - {#if selectedResource.outputs && Object.keys(selectedResource.outputs).length > 0} -
-

- Outputs -

- -
- {/if} -
- {:else} -
- -

- Select a resource from the tree to view its details. -

-
- {/if} -
-
-
-
-{/if} diff --git a/src/routes/settings/projects/+page.svelte b/src/routes/settings/projects/+page.svelte index a400caa..6e6d7c1 100644 --- a/src/routes/settings/projects/+page.svelte +++ b/src/routes/settings/projects/+page.svelte @@ -161,9 +161,7 @@

Projects

-

- Create, organize and manage the projects used across State IaC. -

+

Create, organize and manage the projects.

+ + {#if showProjectDropdown} +
+ {#if projects.length === 0} +

No hay proyectos activos.

+ {:else} +
+ {#each projects as project (project.id)} + + + {project.name} + + {/each} +
+ {/if} + + + View all projects + +
+ {/if} + + {/if} + +
+ -
{#if user} -
+
{#if showUserDropdown} - - -
+

{user.username}

{user.role.name} Account

- - - Profile - -
+ + + Profile + +
- \ No newline at end of file + diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 50c23e9..50019d5 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,3 +1,5 @@ +import { projectService } from '../modules/projects'; +import { can } from '../modules/auth'; // import { configService, storageBackendService } from '../modules/config'; export async function load({ cookies, locals }) { @@ -12,11 +14,17 @@ export async function load({ cookies, locals }) { // activeBackendId = activeBackend.id; // } + const projects = + locals.user && can(locals.user, 'stateiac:read') + ? (await projectService.listProjects()).filter((project) => project.status === 'active') + : []; + return { // isConfigured: !!config && backends.length > 0, isConfigured: true, // backends, // activeBackendId, user: locals.user, + projects, }; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a16430b..9df905c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -26,10 +26,14 @@ style={`--sidebar-width:${sidebarCollapsed ? '96px' : '340px'}`} > {#if $page.url.pathname !== '/login'} - - -
-
+ +
+
+

Danger Zone

+
+ +
+
+
+

Transferir a organización

+

Mueve este proyecto a otra organización.

+
+ +
+ +
+
+

Archivar proyecto

+

+ El proyecto pasará a estado inactivo y dejará de listarse como activo. +

+
+ +
+ +
+
+

Delete project

+

Esta acción no se puede deshacer.

+
+ +
+
+
+{#if archiveModalOpen} + +
+ +
+{/if} + {#if deleteModalOpen} + {/each} @@ -319,6 +342,38 @@ +
+
+

Integraciones

+

Conecta este proyecto con las herramientas GitOps.

+
+ +
+ {#each integrationCards as card (card.label)} +
+
+ +
+
+
+

{card.label}

+ + Próximamente + +
+

{card.description}

+
+
+ {/each} +
+
+

Danger Zone

From 58917a1ae047e8bd43d5a1404cb3f04bc63be829 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 20:54:14 +0200 Subject: [PATCH 13/16] sidebar reactivo y modulos donde toca --- src/hooks.server.ts | 2 +- src/lib/components/AppNavbar.svelte | 4 +- src/lib/components/AppSidebar.svelte | 222 ++++++------------ src/routes/+layout.svelte | 2 + src/routes/how-to/+page.svelte | 52 ---- src/routes/login/+page.svelte | 37 +-- .../org/[org]/projects/[slug]/+layout.svelte | 29 ++- .../projects/[slug]/overview/+page.svelte | 24 +- .../projects/[slug]/report}/+page.svelte | 0 .../[slug]/settings/overview/+page.svelte | 21 +- .../projects/[slug]/state-iac/+page.svelte | 69 ++++++ .../[org]/projects/[slug]}/vault/+page.svelte | 0 12 files changed, 226 insertions(+), 236 deletions(-) delete mode 100644 src/routes/how-to/+page.svelte rename src/routes/{open-report => org/[org]/projects/[slug]/report}/+page.svelte (100%) create mode 100644 src/routes/org/[org]/projects/[slug]/state-iac/+page.svelte rename src/routes/{ => org/[org]/projects/[slug]}/vault/+page.svelte (100%) diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 754e89c..02dcc9c 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -24,7 +24,7 @@ export const handle: Handle = async ({ event, resolve }) => { if (event.url.pathname.startsWith('/settings') || event.url.pathname.startsWith('/api/system')) { if (!canAccessAdminArea(currentUser)) { - return new Response(null, { status: 302, headers: { location: '/vault' } }); + return new Response(null, { status: 302, headers: { location: '/' } }); } } diff --git a/src/lib/components/AppNavbar.svelte b/src/lib/components/AppNavbar.svelte index 7d13a60..0b08a16 100644 --- a/src/lib/components/AppNavbar.svelte +++ b/src/lib/components/AppNavbar.svelte @@ -1,10 +1,10 @@ diff --git a/src/lib/components/AppSidebar.svelte b/src/lib/components/AppSidebar.svelte index abcf3dd..cf2451b 100644 --- a/src/lib/components/AppSidebar.svelte +++ b/src/lib/components/AppSidebar.svelte @@ -7,7 +7,6 @@ Database, FolderKanban, GitBranch, - HelpCircle, Info, KeyRound, LayoutDashboard, @@ -23,9 +22,15 @@ export let isConfigured = false; export let collapsed = false; export let organizationSlug = 'gitops'; + export let projects: { + slug: string; + modules?: { vault: boolean; openreport: boolean; stateiac: boolean }; + }[] = []; let currentPath = pathname; + $: currentProject = projects.find((project) => project.slug === currentProjectSlug) ?? null; + $: sections = [ { title: 'Inicio', @@ -37,54 +42,48 @@ }, ], }, - { - title: 'Seguridad', - items: [ - { - label: 'Vault', - href: '/vault', - icon: Shield, - }, - ], - }, - { - title: 'Analisis', - items: [ - { - label: 'Open Report', - href: '/open-report', - icon: BarChart3, - }, - ], - }, - { - title: 'GitOps', - items: [ - { - label: 'Pulumi State', - href: '/pulumi-state', - icon: GitBranch, - }, - ], - }, - ]; - - const pulumiStateItems = [ - { - label: 'Overview', - href: '/pulumi-state', - icon: GitBranch, - }, - { - label: 'Storage Backends', - href: '/pulumi-state/backends', - icon: Database, - }, - { - label: 'CLI Guide', - href: '/pulumi-state/cli-guide', - icon: HelpCircle, - }, + ...(currentProject?.modules?.vault + ? [ + { + title: 'Seguridad', + items: [ + { + label: 'Vault', + href: `/org/${currentProjectOrgSlug}/projects/${currentProject.slug}/vault`, + icon: Shield, + }, + ], + }, + ] + : []), + ...(currentProject?.modules?.openreport + ? [ + { + title: 'Analisis', + items: [ + { + label: 'Open Report', + href: `/org/${currentProjectOrgSlug}/projects/${currentProject.slug}/report`, + icon: BarChart3, + }, + ], + }, + ] + : []), + ...(currentProject?.modules?.stateiac + ? [ + { + title: 'GitOps', + items: [ + { + label: 'State IaC', + href: `/org/${currentProjectOrgSlug}/projects/${currentProject.slug}/state-iac`, + icon: GitBranch, + }, + ], + }, + ] + : []), ]; const settingsItems = [ @@ -116,7 +115,6 @@ ]; let settingsMenuOpen = pathname.startsWith('/settings'); - let pulumiStateMenuOpen = pathname.startsWith('/pulumi-state'); let projectSettingsMenuOpen = true; $: currentProjectOrgSlug = ($page.params.org as string | undefined) ?? organizationSlug; @@ -151,34 +149,7 @@ return currentPath.startsWith(href); } - function getPulumiStateActiveItem() { - if ( - currentPath.startsWith('/pulumi-state/backends') || - currentPath.startsWith('/settings/storage') - ) { - return '/pulumi-state/backends'; - } - - if (currentPath.startsWith('/pulumi-state/cli-guide')) { - return '/pulumi-state/cli-guide'; - } - - if (currentPath.startsWith('/pulumi-state')) { - return '/pulumi-state'; - } - - return null; - } - - function isPulumiStateSectionActive() { - return getPulumiStateActiveItem() !== null; - } - function isActive(path: string) { - if (path.startsWith('/pulumi-state')) { - return getPulumiStateActiveItem() === path; - } - if (path.startsWith('/settings')) { return currentPath.startsWith('/settings'); } @@ -195,10 +166,6 @@ $: if (currentPath.startsWith('/settings')) { settingsMenuOpen = true; } - - $: if (currentPath.startsWith('/pulumi-state')) { - pulumiStateMenuOpen = true; - }
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f406163..ede3a41 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -30,6 +30,7 @@ user={data.user} projects={data.projects} organizationSlug={data.organization.slug} + projectSlug={$page.params.slug ?? ''} />
diff --git a/src/routes/how-to/+page.svelte b/src/routes/how-to/+page.svelte deleted file mode 100644 index 9376f30..0000000 --- a/src/routes/how-to/+page.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - How To - Pulumi Open State - - -
-
-

How to use Pulumi Open State

-

- This starter mirrors the main flows from the original Pulumi Open State UI: configure storage - backends, connect Pulumi projects, and inspect stack state and history. -

-
- -
-
-
- -

Configure storage

-
-

- Add an S3 or GCS backend from Pulumi State → Storage Backends to connect your Pulumi state - buckets. -

-
- -
-
- -

Use the Pulumi CLI

-
-

- Run the generated login and stack init commands from the Projects view to connect a local - stack. -

-
-
- -
-
- -

Inspect state

-
-

- Open a stack to browse resources, outputs, configuration, and historical checkpoints from the - backend. -

-
-
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 0c0ab0d..f84d47b 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -21,7 +21,7 @@ const payload = await response.json().catch(() => ({})); if (response.ok && payload?.success) { - window.location.href = '/vault'; + window.location.href = '/'; return; } @@ -38,15 +38,23 @@ Login - GitVault Suite -
-
+
+
-
+
GitOps
-

Workspace

+

+ Workspace +

Sign in to GitVault Suite

Continue with your private admin workspace.

@@ -58,14 +66,11 @@
{/if} - +
- +
- +
- Default credentials for first start: admin / 12345678 + Default credentials for first start: admin / 12345678
diff --git a/src/routes/org/[org]/projects/[slug]/+layout.svelte b/src/routes/org/[org]/projects/[slug]/+layout.svelte index edb720b..3e88447 100644 --- a/src/routes/org/[org]/projects/[slug]/+layout.svelte +++ b/src/routes/org/[org]/projects/[slug]/+layout.svelte @@ -1,6 +1,15 @@ + + + State IaC - GitVault Suite + + +
+
+
+
+
+

+ GitOps +

+

State IaC

+

+ Inspecciona el estado de Pulumi, backends de almacenamiento e historial de stacks para + este proyecto. +

+
+ {#if canCreate} + + + CLI Guide + + {/if} +
+
+ +
+
+ +

Stacks

+

Recursos, outputs y configuración.

+
+
+ +

Storage Backends

+

Buckets S3 o GCS conectados.

+
+
+ +

History

+

Checkpoints e historial de despliegues.

+
+
+
+ +
+

Próximo paso

+

+ Esta pantalla es sólo la base visual. El siguiente paso será conectar el estado real de Pulumi + para este proyecto. +

+
+
diff --git a/src/routes/vault/+page.svelte b/src/routes/org/[org]/projects/[slug]/vault/+page.svelte similarity index 100% rename from src/routes/vault/+page.svelte rename to src/routes/org/[org]/projects/[slug]/vault/+page.svelte From 3bf77b9cbe0cb4aaca979c4ac75218a435ce4ad6 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 21:31:08 +0200 Subject: [PATCH 14/16] sidebar menus --- src/lib/components/AppSidebar.svelte | 450 ++++++++++++--------------- 1 file changed, 194 insertions(+), 256 deletions(-) diff --git a/src/lib/components/AppSidebar.svelte b/src/lib/components/AppSidebar.svelte index cf2451b..fe63604 100644 --- a/src/lib/components/AppSidebar.svelte +++ b/src/lib/components/AppSidebar.svelte @@ -1,4 +1,5 @@ From f0b328e39924bec44f8fcc74ec575f648ab380c9 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 20 Aug 2026 22:35:49 +0200 Subject: [PATCH 15/16] create project modal fix --- .../[slug]/settings/overview/+page.svelte | 62 ++++++++++++++----- src/routes/settings/projects/+page.svelte | 30 ++++++--- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/routes/org/[org]/projects/[slug]/settings/overview/+page.svelte b/src/routes/org/[org]/projects/[slug]/settings/overview/+page.svelte index f6dbb87..b5d3bad 100644 --- a/src/routes/org/[org]/projects/[slug]/settings/overview/+page.svelte +++ b/src/routes/org/[org]/projects/[slug]/settings/overview/+page.svelte @@ -3,6 +3,7 @@ import { page } from '$app/stores'; import { Archive, + ArchiveRestore, BarChart3, Bot, Building2, @@ -37,6 +38,7 @@ $: project = data.project; $: orgSlug = $page.params.org; + $: isArchived = project.status === 'inactive'; let saving = false; let error = ''; @@ -168,10 +170,11 @@ archiveLoading = true; try { + const nextStatus = isArchived ? 'active' : 'inactive'; const res = await fetch(`/api/projects/${project.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: 'inactive' }), + body: JSON.stringify({ status: nextStatus }), }); const payload = await res.json(); @@ -179,9 +182,9 @@ project = payload.project; closeArchiveModal(); - flashSuccess('Project archived.'); + flashSuccess(isArchived ? 'Project reactivated.' : 'Project archived.'); } catch (err: unknown) { - error = err instanceof Error ? err.message : 'Failed to archive project.'; + error = err instanceof Error ? err.message : 'Failed to update project status.'; } finally { archiveLoading = false; } @@ -233,7 +236,16 @@ {/if}
-
+
+ {#if isArchived} + + + Archivado + + {/if} +
@@ -461,13 +482,21 @@ aria-label="Archive project modal" >
-
Archive project
+
+ {isArchived ? 'Activate project' : 'Archive project'} +
- Are you sure you want to archive {project.name}? It will be marked as inactive. + {#if isArchived} + Are you sure you want to activate {project.name}? It will be marked as active again. + {:else} + Are you sure you want to archive {project.name}? It will be marked as inactive. + {/if}
@@ -484,8 +513,13 @@ disabled={archiveLoading} class="btn-primary inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium" > - - {archiveLoading ? 'Archiving...' : 'Archive project'} + {#if isArchived} + + {archiveLoading ? 'Activating...' : 'Activate project'} + {:else} + + {archiveLoading ? 'Archiving...' : 'Archive project'} + {/if}
diff --git a/src/routes/settings/projects/+page.svelte b/src/routes/settings/projects/+page.svelte index 422fb34..d47cb1a 100644 --- a/src/routes/settings/projects/+page.svelte +++ b/src/routes/settings/projects/+page.svelte @@ -24,6 +24,7 @@ let createModalOpen = false; let creating = false; + let createError = ''; let newName = ''; let newSlug = ''; let newDescription = ''; @@ -31,6 +32,7 @@ let deleteModalProject: ProjectRow | null = null; let deleteLoading = false; + let deleteError = ''; onMount(fetchProjects); @@ -69,7 +71,7 @@ }); function openCreateModal() { - error = ''; + createError = ''; newName = ''; newSlug = ''; newDescription = ''; @@ -82,10 +84,10 @@ } async function createProject() { - error = ''; + createError = ''; if (!newName.trim()) { - error = 'Project name is required.'; + createError = 'Project name is required.'; return; } @@ -109,7 +111,7 @@ flashSuccess('Project created.'); await fetchProjects(); } catch (err: unknown) { - error = err instanceof Error ? err.message : 'Failed to create project.'; + createError = err instanceof Error ? err.message : 'Failed to create project.'; } finally { creating = false; } @@ -117,7 +119,7 @@ function openDeleteModal(project: ProjectRow) { deleteModalProject = project; - error = ''; + deleteError = ''; } function closeDeleteModal() { @@ -127,7 +129,7 @@ async function confirmDelete() { if (!deleteModalProject) return; - error = ''; + deleteError = ''; deleteLoading = true; try { @@ -139,7 +141,7 @@ flashSuccess('Project deleted.'); await fetchProjects(); } catch (err: unknown) { - error = err instanceof Error ? err.message : 'Failed to delete project.'; + deleteError = err instanceof Error ? err.message : 'Failed to delete project.'; } finally { deleteLoading = false; } @@ -313,6 +315,12 @@
+ {#if createError} +
+ {createError} +
+ {/if} +
@@ -405,6 +413,14 @@
Delete project
+ {#if deleteError} +
+
+ {deleteError} +
+
+ {/if} +
Are you sure you want to delete {deleteModalProject.name} Date: Thu, 20 Aug 2026 22:49:46 +0200 Subject: [PATCH 16/16] lint + unit test project --- .../application/project.service.test.ts | 225 ++++++++++++++++++ src/routes/+layout.server.ts | 2 +- src/routes/profile/+page.server.ts | 2 +- vitest.config.ts | 8 +- 4 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 src/modules/projects/application/project.service.test.ts diff --git a/src/modules/projects/application/project.service.test.ts b/src/modules/projects/application/project.service.test.ts new file mode 100644 index 0000000..6616a25 --- /dev/null +++ b/src/modules/projects/application/project.service.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { ProjectService } from './project.service'; +import { ProjectDomain } from '../domain/project.domain'; + +class FakeProjectRepository { + rows: ProjectDomain[] = []; + + async findAll() { + return [...this.rows]; + } + + async findById(id: string) { + return this.rows.find((p) => p.id === id) ?? null; + } + + async findBySlug(slug: string) { + return this.rows.find((p) => p.slug === slug) ?? null; + } + + async create(input: { + id: string; + slug: string; + name: string; + description?: string; + status: string; + modules: { vault: boolean; openreport: boolean; stateiac: boolean }; + }) { + this.rows.push( + new ProjectDomain({ + id: input.id, + slug: input.slug, + name: input.name, + description: input.description, + status: input.status, + modules: input.modules, + createdAt: '2024-01-01T00:00:00.000Z', + updatedAt: '2024-01-01T00:00:00.000Z', + }), + ); + } + + async update( + id: string, + changes: { + name?: string; + slug?: string; + description?: string; + status?: string; + modules?: { vault: boolean; openreport: boolean; stateiac: boolean }; + }, + ) { + const project = this.rows.find((p) => p.id === id); + if (!project) return; + if (changes.name !== undefined) project.name = changes.name; + if (changes.slug !== undefined) project.slug = changes.slug; + if (changes.description !== undefined) project.description = changes.description; + if (changes.status !== undefined) project.status = changes.status as 'active' | 'inactive'; + if (changes.modules !== undefined) project.modules = changes.modules; + } + + async deleteById(id: string) { + this.rows = this.rows.filter((p) => p.id !== id); + } +} + +describe('ProjectService', () => { + let repository: FakeProjectRepository; + let service: ProjectService; + + beforeEach(() => { + repository = new FakeProjectRepository(); + service = new ProjectService(repository as any); + }); + + describe('listProjects', () => { + it('returns an empty list when there are no projects', async () => { + expect(await service.listProjects()).toEqual([]); + }); + + it('lists created projects', async () => { + await service.createProject({ name: 'Platform Core' }); + const projects = await service.listProjects(); + expect(projects).toHaveLength(1); + expect(projects[0].slug).toBe('platform-core'); + }); + }); + + describe('getProject / getProjectBySlug', () => { + it('throws when the project does not exist', async () => { + await expect(service.getProject('missing-id')).rejects.toThrow(/not found/); + await expect(service.getProjectBySlug('missing-slug')).rejects.toThrow(/not found/); + }); + + it('returns the project by id and by slug', async () => { + const created = await service.createProject({ name: 'Kettu Studio' }); + + const byId = await service.getProject(created.id); + expect(byId.slug).toBe('kettu-studio'); + + const bySlug = await service.getProjectBySlug('kettu-studio'); + expect(bySlug.id).toBe(created.id); + }); + }); + + describe('createProject', () => { + it('requires a non-empty name', async () => { + await expect(service.createProject({ name: ' ' })).rejects.toThrow(/name is required/); + }); + + it('auto-generates a normalized slug from the name when none is provided', async () => { + const project = await service.createProject({ name: 'Kettu Studio!!' }); + expect(project.slug).toBe('kettu-studio'); + }); + + it('uses the provided slug, normalized', async () => { + const project = await service.createProject({ name: 'Kettu Studio', slug: 'Custom Slug' }); + expect(project.slug).toBe('custom-slug'); + }); + + it('rejects creating a project with a duplicate slug', async () => { + await service.createProject({ name: 'Kettu Studio' }); + await expect(service.createProject({ name: 'Kettu Studio' })).rejects.toThrow( + /already exists/, + ); + }); + + it('defaults status to active for missing or invalid values', async () => { + const project = await service.createProject({ name: 'Project A' }); + expect(project.status).toBe('active'); + }); + + it('accepts an explicit inactive status', async () => { + const project = await service.createProject({ name: 'Project B', status: 'inactive' }); + expect(project.status).toBe('inactive'); + }); + + it('defaults all modules to enabled when none are provided', async () => { + const project = await service.createProject({ name: 'Project C' }); + expect(project.modules).toEqual({ vault: true, openreport: true, stateiac: true }); + }); + + it('accepts a partial modules override, defaulting the rest to enabled', async () => { + const project = await service.createProject({ + name: 'Project D', + modules: { vault: false }, + }); + expect(project.modules).toEqual({ vault: false, openreport: true, stateiac: true }); + }); + }); + + describe('updateProject', () => { + it('throws when the project does not exist', async () => { + await expect(service.updateProject('missing-id', { name: 'X' })).rejects.toThrow(/not found/); + }); + + it('updates only the fields provided', async () => { + const created = await service.createProject({ name: 'Original Name' }); + + const updated = await service.updateProject(created.id, { description: 'New description' }); + expect(updated.name).toBe('Original Name'); + expect(updated.description).toBe('New description'); + }); + + it('rejects clearing the name', async () => { + const created = await service.createProject({ name: 'Original Name' }); + await expect(service.updateProject(created.id, { name: ' ' })).rejects.toThrow( + /name is required/, + ); + }); + + it('normalizes the slug when updating it', async () => { + const created = await service.createProject({ name: 'Original Name' }); + const updated = await service.updateProject(created.id, { slug: 'New Slug!!' }); + expect(updated.slug).toBe('new-slug'); + }); + + it('rejects updating to a slug already used by another project', async () => { + await service.createProject({ name: 'Project A', slug: 'taken' }); + const created = await service.createProject({ name: 'Project B' }); + + await expect(service.updateProject(created.id, { slug: 'taken' })).rejects.toThrow( + /already exists/, + ); + }); + + it('allows keeping the same slug on the same project', async () => { + const created = await service.createProject({ name: 'Project A', slug: 'same-slug' }); + const updated = await service.updateProject(created.id, { slug: 'same-slug' }); + expect(updated.slug).toBe('same-slug'); + }); + + it('merges partial module updates with the existing modules', async () => { + const created = await service.createProject({ name: 'Project A' }); + + const updated = await service.updateProject(created.id, { modules: { vault: false } }); + expect(updated.modules).toEqual({ vault: false, openreport: true, stateiac: true }); + + const updatedAgain = await service.updateProject(created.id, { + modules: { openreport: false }, + }); + expect(updatedAgain.modules).toEqual({ vault: false, openreport: false, stateiac: true }); + }); + + it('updates the status', async () => { + const created = await service.createProject({ name: 'Project A' }); + const updated = await service.updateProject(created.id, { status: 'inactive' }); + expect(updated.status).toBe('inactive'); + }); + }); + + describe('deleteProject', () => { + it('throws when the project does not exist', async () => { + await expect(service.deleteProject('missing-id')).rejects.toThrow(/not found/); + }); + + it('deletes an existing project', async () => { + const created = await service.createProject({ name: 'Project A' }); + + await service.deleteProject(created.id); + + const projects = await service.listProjects(); + expect(projects.find((p) => p.id === created.id)).toBeUndefined(); + }); + }); +}); diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index d268e40..5de5c53 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -3,7 +3,7 @@ import { organizationService } from '../modules/organization'; import { can } from '../modules/auth'; // import { configService, storageBackendService } from '../modules/config'; -export async function load({ cookies, locals }) { +export async function load({ locals }) { // const config = configService.getConfig(); // const backends = storageBackendService.list(); diff --git a/src/routes/profile/+page.server.ts b/src/routes/profile/+page.server.ts index 8719a70..1fde3ee 100644 --- a/src/routes/profile/+page.server.ts +++ b/src/routes/profile/+page.server.ts @@ -16,7 +16,7 @@ function expiresAtFromDays(expiresInDays: string | null): string | null { return date.toISOString(); } -export async function load({ locals, cookies }) { +export async function load({ locals }) { if (!locals.user) { throw redirect(302, '/login'); } diff --git a/vitest.config.ts b/vitest.config.ts index 30c7f9d..aea1a28 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,15 @@ +import path from 'path'; import { defineConfig } from 'vitest/config'; export default defineConfig({ + resolve: { + alias: { + $lib: path.resolve(import.meta.dirname, 'src/lib'), + }, + }, test: { environment: 'node', include: ['src/**/*.test.ts', 'src/**/*.spec.ts'], globals: true, }, -}); \ No newline at end of file +});