mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 21:33:41 +00:00
refactor(db): collapse repository rollout scaffolding into single factory
Repositories are now the only data path. Replaces the 41 current-*-repository wrapper files, the DATABASE_LAYER_REPOSITORY_ROLLOUT flag/alias map and the unused database/runtime adapter with repositories/factory.ts, a plain DatabaseContext type and an in-memory TestSqliteDatabase test harness.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,548 +0,0 @@
|
|||||||
# Database Layer Refactor Phase 0 Audit
|
|
||||||
|
|
||||||
Status: Draft
|
|
||||||
Branch: `feature/database-layer-refactor`
|
|
||||||
Purpose: Establish the current database access inventory, domain map, sensitive field map, and first implementation boundaries before changing runtime persistence.
|
|
||||||
|
|
||||||
## 1. Scope
|
|
||||||
|
|
||||||
Phase 0 does not change runtime behavior.
|
|
||||||
|
|
||||||
It produces the evidence needed for the next implementation phases:
|
|
||||||
|
|
||||||
- where database access currently happens
|
|
||||||
- which modules write directly to the database
|
|
||||||
- which tables belong to which product domains
|
|
||||||
- which fields are sensitive
|
|
||||||
- which direct writes are risky under the current in-memory snapshot model
|
|
||||||
- which domains should move first into repositories
|
|
||||||
|
|
||||||
## 2. Current Evidence
|
|
||||||
|
|
||||||
Commands used for the initial audit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rg -n "getDb\\(|getSqlite\\(|DatabaseSaveTrigger|SimpleDBOps|db\\.\\$client|\\.prepare\\(" src/backend
|
|
||||||
rg -n "getDb\\(\\)\\.(insert|update|delete)|await db\\.(insert|update|delete)|db\\.(insert|update|delete)|\\.\\$client\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|DatabaseSaveTrigger\\.triggerSave|DatabaseSaveTrigger\\.forceSave" src/backend
|
|
||||||
rg -n "export const .* = sqliteTable\\(" src/backend/database/db/schema.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
High-level findings:
|
|
||||||
|
|
||||||
- Database infrastructure is concentrated in `src/backend/database/db/index.ts`.
|
|
||||||
- Business database access is spread across route modules, SSH modules, utilities, and auth helpers.
|
|
||||||
- `SimpleDBOps` is not the only write path.
|
|
||||||
- There are many direct Drizzle writes and raw SQLite writes.
|
|
||||||
- Some direct writes manually trigger `DatabaseSaveTrigger`; many write paths do not.
|
|
||||||
- Schema is SQLite-specific through `sqliteTable` and manual `CREATE TABLE IF NOT EXISTS` / `addColumnIfNotExists`.
|
|
||||||
|
|
||||||
## 3. Database Access Hotspots
|
|
||||||
|
|
||||||
The most database-heavy files by audit hits:
|
|
||||||
|
|
||||||
| File | Approx. hits | Notes |
|
|
||||||
| ----------------------------------------------------- | -----------: | --------------------------------------------------------------- |
|
|
||||||
| `src/backend/database/db/index.ts` | 75 | database init, schema creation, ad-hoc migration, snapshot save |
|
|
||||||
| `src/backend/database/routes/alert-rules-routes.ts` | 64 | alert rules, channels, firings, raw SQL deletes |
|
|
||||||
| `src/backend/database/routes/users.ts` | 54 | users, settings, OIDC/GitHub settings, admin flows |
|
|
||||||
| `src/backend/database/database.ts` | 27 | import/export, legacy SQLite handling |
|
|
||||||
| `src/backend/ssh/host-metrics.ts` | 26 | metrics connection and settings reads |
|
|
||||||
| `src/backend/database/routes/user-settings-routes.ts` | 16 | user settings |
|
|
||||||
| `src/backend/dashboard.ts` | 15 | dashboard aggregation reads |
|
|
||||||
| `src/backend/ssh/docker.ts` | 14 | host/credential reads for Docker SSH |
|
|
||||||
| `src/backend/ssh/managers/health.ts` | 13 | host health checks |
|
|
||||||
| `src/backend/ssh/alert-engine.ts` | 13 | alert evaluation and firing state |
|
|
||||||
| `src/backend/utils/user-crypto.ts` | 12 | settings writes for crypto metadata |
|
|
||||||
| `src/backend/ssh/host-metrics-settings-routes.ts` | 12 | metrics settings |
|
|
||||||
| `src/backend/ssh/tmux-monitor.ts` | 11 | tmux session tags |
|
|
||||||
| `src/backend/guacamole/routes.ts` | 11 | Guacamole config/routes |
|
|
||||||
| `src/backend/database/routes/host.ts` | 10 | host operations and related rows |
|
|
||||||
|
|
||||||
This confirms the refactor must be domain-by-domain. A mechanical replacement of `getDb()` would be noisy and unsafe.
|
|
||||||
|
|
||||||
## 4. Direct Write Risk Inventory
|
|
||||||
|
|
||||||
Under the current architecture, a direct write is risky when it bypasses `SimpleDBOps` and does not trigger `DatabaseSaveTrigger`.
|
|
||||||
|
|
||||||
### 4.1 Direct Writes That Need Repository Ownership
|
|
||||||
|
|
||||||
These areas perform direct writes and should move behind repositories/services:
|
|
||||||
|
|
||||||
| Area | Representative files | Examples |
|
|
||||||
| --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
||||||
| users/settings/auth | `routes/users.ts`, `utils/auth-manager.ts`, `utils/user-crypto.ts` | sessions, trusted devices, OIDC settings, registration settings |
|
|
||||||
| RBAC/sharing | `routes/rbac.ts`, `utils/shared-credential-manager.ts` | roles, host access, snippet access, shared credentials |
|
|
||||||
| hosts/credentials | `routes/host.ts`, `routes/credentials.ts`, `host-resolver.ts` | host access cleanup, credential usage |
|
|
||||||
| file manager metadata | `host-file-manager-bookmark-routes.ts` | recent, pinned, shortcuts |
|
|
||||||
| alerts | `alert-rules-routes.ts`, `ssh/alert-engine.ts` | channels, rules, firings |
|
|
||||||
| metrics | `host-metrics-preferences-routes.ts`, `managers/health.ts` | preferences, health checks, history |
|
|
||||||
| terminal logs | `terminal-session-manager.ts` | session recording metadata |
|
|
||||||
| tmux | `tmux-monitor.ts` | tmux session tags |
|
|
||||||
| import/export | `database/database.ts` | SQLite import and forced save |
|
|
||||||
| open tabs | `routes/open-tabs.ts` | tab persistence and cleanup |
|
|
||||||
| API keys | `user-api-key-routes.ts`, `utils/auth-manager.ts` | API key create/delete/last-used |
|
|
||||||
| SSO and identity | `sso-provider-routes.ts`, `termix-id.ts` | providers, identity keys/CA |
|
|
||||||
|
|
||||||
### 4.2 Immediate Compatibility Rule
|
|
||||||
|
|
||||||
Until a domain is migrated to repositories:
|
|
||||||
|
|
||||||
- Every direct write must either be moved into a repository or explicitly trigger persistence in the old runtime.
|
|
||||||
- New code should not add direct `getDb()` writes outside infrastructure or repositories.
|
|
||||||
- The draft branch should add an enforcement check before Phase 8, not immediately, because the current codebase still violates the target rule widely.
|
|
||||||
|
|
||||||
## 5. Table Domain Map
|
|
||||||
|
|
||||||
### 5.1 Identity and Authentication
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ---------------------- | -------------------------------------------- |
|
|
||||||
| `users` | local/OIDC users, TOTP fields, password hash |
|
|
||||||
| `sessions` | JWT sessions |
|
|
||||||
| `trusted_devices` | remembered devices |
|
|
||||||
| `api_keys` | API token hashes/prefixes |
|
|
||||||
| `sso_providers` | configured SSO providers |
|
|
||||||
| `termix_identities` | Termix identity records |
|
|
||||||
| `termix_identity_keys` | public/private identity key metadata |
|
|
||||||
| `termix_identity_ca` | CA material, private key is sensitive |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `userRepository`
|
|
||||||
- `sessionRepository`
|
|
||||||
- `trustedDeviceRepository`
|
|
||||||
- `apiKeyRepository`
|
|
||||||
- `ssoProviderRepository`
|
|
||||||
- `termixIdentityRepository`
|
|
||||||
|
|
||||||
### 5.2 Hosts and Credentials
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ---------------------- | ------------------------------------------------ |
|
|
||||||
| `ssh_data` | primary host table, many config JSON/text fields |
|
|
||||||
| `ssh_credentials` | reusable credentials |
|
|
||||||
| `ssh_credential_usage` | usage history |
|
|
||||||
| `ssh_folders` | folder metadata |
|
|
||||||
| `host_access` | sharing/RBAC access rows |
|
|
||||||
| `shared_credentials` | encrypted shared credential material |
|
|
||||||
| `network_topology` | topology graph/config |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `hostRepository`
|
|
||||||
- `credentialRepository`
|
|
||||||
- `credentialUsageRepository`
|
|
||||||
- `hostAccessRepository`
|
|
||||||
- `sharedCredentialRepository`
|
|
||||||
- `hostFolderRepository`
|
|
||||||
- `networkTopologyRepository`
|
|
||||||
|
|
||||||
### 5.3 RBAC
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ---------------- | ------------------------ |
|
|
||||||
| `roles` | role definitions |
|
|
||||||
| `user_roles` | user to role assignments |
|
|
||||||
| `host_access` | host-level grants |
|
|
||||||
| `snippet_access` | snippet-level grants |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `roleRepository`
|
|
||||||
- `accessRepository`
|
|
||||||
|
|
||||||
### 5.4 File Manager
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ------------------------ | ---------------------------------- |
|
|
||||||
| `file_manager_recent` | recently opened paths |
|
|
||||||
| `file_manager_pinned` | pinned paths |
|
|
||||||
| `file_manager_shortcuts` | saved shortcuts |
|
|
||||||
| `transfer_recent` | host-to-host transfer destinations |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `fileManagerRepository`
|
|
||||||
- `transferRecentRepository`
|
|
||||||
|
|
||||||
### 5.5 Snippets
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ----------------- | ----------------------- |
|
|
||||||
| `snippets` | command snippets |
|
|
||||||
| `snippet_folders` | snippet folder metadata |
|
|
||||||
| `snippet_access` | snippet sharing |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `snippetRepository`
|
|
||||||
|
|
||||||
### 5.6 Runtime Metadata and Audit
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| -------------------- | ------------------------------------------------------ |
|
|
||||||
| `audit_logs` | append-only audit trail |
|
|
||||||
| `session_recordings` | terminal recording metadata, log content is file-based |
|
|
||||||
| `recent_activity` | host activity feed |
|
|
||||||
| `command_history` | terminal command history |
|
|
||||||
| `user_open_tabs` | UI restore state; currently cleared on startup |
|
|
||||||
| `user_preferences` | per-user UI/preferences |
|
|
||||||
| `settings` | global settings |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `auditRepository`
|
|
||||||
- `sessionRecordingRepository`
|
|
||||||
- `activityRepository`
|
|
||||||
- `commandHistoryRepository`
|
|
||||||
- `openTabsRepository`
|
|
||||||
- `userPreferencesRepository`
|
|
||||||
- `settingsRepository`
|
|
||||||
|
|
||||||
### 5.7 Metrics and Alerts
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| -------------------------- | -------------------------- |
|
|
||||||
| `host_metrics_preferences` | metrics layout/preferences |
|
|
||||||
| `host_health_checks` | health check definitions |
|
|
||||||
| `host_health_history` | health check results |
|
|
||||||
| `host_metrics_history` | metrics history |
|
|
||||||
| `alert_rules` | alert definitions |
|
|
||||||
| `notification_channels` | webhook/ntfy/etc config |
|
|
||||||
| `alert_rule_channels` | rule/channel joins |
|
|
||||||
| `alert_firings` | firing/ack state |
|
|
||||||
| `dismissed_alerts` | dismissed system alerts |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `metricsRepository`
|
|
||||||
- `healthCheckRepository`
|
|
||||||
- `alertRepository`
|
|
||||||
- `notificationRepository`
|
|
||||||
|
|
||||||
### 5.8 Integrations and Feature Config
|
|
||||||
|
|
||||||
| Tables | Notes |
|
|
||||||
| ------------------------- | --------------------------------------- |
|
|
||||||
| `c2s_tunnel_presets` | tunnel preset config |
|
|
||||||
| `opkssh_tokens` | OPKSSH cert/private key cache |
|
|
||||||
| `vault_profiles` | Vault profile config, mostly non-secret |
|
|
||||||
| `vault_tokens` | Vault cert/private key cache |
|
|
||||||
| `dashboard_service_links` | dashboard links |
|
|
||||||
| `homepage_items` | homepage widgets/items |
|
|
||||||
| `homepage_layouts` | homepage layouts |
|
|
||||||
| `tmux_session_tags` | tmux tag metadata |
|
|
||||||
|
|
||||||
Suggested repository:
|
|
||||||
|
|
||||||
- `tunnelPresetRepository`
|
|
||||||
- `opksshTokenRepository`
|
|
||||||
- `vaultRepository`
|
|
||||||
- `dashboardRepository`
|
|
||||||
- `homepageRepository`
|
|
||||||
- `tmuxRepository`
|
|
||||||
|
|
||||||
## 6. Sensitive Field Map
|
|
||||||
|
|
||||||
The current explicit `FieldCrypto` map encrypts:
|
|
||||||
|
|
||||||
| Table | Fields |
|
|
||||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `users` | `passwordHash`, `clientSecret`, `totpSecret`, `totpBackupCodes`, `oidcIdentifier` |
|
|
||||||
| `ssh_data` | `password`, `key`, `keyPassword`, `sudoPassword`, `autostartPassword`, `autostartKey`, `autostartKeyPassword`, `socks5Password`, `rdpPassword`, `vncPassword`, `telnetPassword` |
|
|
||||||
| `ssh_credentials` | `password`, `privateKey`, `keyPassword`, `key`, `publicKey` |
|
|
||||||
| `opkssh_tokens` | `sshCert`, `privateKey` |
|
|
||||||
| `termix_identity_ca` | `privateKey` |
|
|
||||||
| `vault_tokens` | `sshCert`, `privateKey` |
|
|
||||||
|
|
||||||
Additional sensitive fields by table semantics:
|
|
||||||
|
|
||||||
| Table | Fields / reason |
|
|
||||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `ssh_credentials` | `systemPassword`, `systemKey`, `systemKeyPassword` are system-key encrypted credential copies |
|
|
||||||
| `shared_credentials` | `encryptedUsername`, `encryptedAuthType`, `encryptedPassword`, `encryptedKey`, `encryptedKeyPassword`, `encryptedKeyType` are already encrypted payload fields |
|
|
||||||
| `api_keys` | `tokenHash` is not plaintext but is authentication material; `tokenPrefix` may remain plaintext for display |
|
|
||||||
| `settings` | some keys may hold provider secrets or reset codes; repository must classify by key |
|
|
||||||
| `notification_channels` | config may include webhook URLs/tokens; treat config as sensitive unless split |
|
|
||||||
| `alert_rules` | rule definitions are usually not secret but can include host/resource metadata |
|
|
||||||
| `homepage_items` | widget config can include URLs/API config; classify per widget type |
|
|
||||||
| `c2s_tunnel_presets` | config can include connection details; review before plaintext external DB storage |
|
|
||||||
| `termix_identity_keys` | inspect key material fields before migration; public keys are not secret but private material must never be plaintext |
|
|
||||||
| `vault_profiles` | current comments say profile fields are non-secret; keep that invariant explicit |
|
|
||||||
|
|
||||||
Open privacy decision:
|
|
||||||
|
|
||||||
- `ssh_data.ip`, domain fields, usernames, folders, and tags are queryable today.
|
|
||||||
- Encrypting them improves confidentiality but breaks search/filter/sort unless blind indexes are added.
|
|
||||||
- Recommended initial migration: keep them plaintext and document privacy implications; add optional privacy mode later.
|
|
||||||
|
|
||||||
## 7. Repository Migration Order
|
|
||||||
|
|
||||||
Use a vertical slice instead of broad replacement.
|
|
||||||
|
|
||||||
### 7.1 First Slice
|
|
||||||
|
|
||||||
1. `settingsRepository`
|
|
||||||
2. `userRepository`
|
|
||||||
3. `sessionRepository`
|
|
||||||
4. `hostRepository`
|
|
||||||
5. `credentialRepository`
|
|
||||||
|
|
||||||
Reasons:
|
|
||||||
|
|
||||||
- Covers the app's boot/login/core host management path.
|
|
||||||
- Exercises field encryption.
|
|
||||||
- Exercises per-user data unlock requirements.
|
|
||||||
- Exercises transaction and migration behavior.
|
|
||||||
- Builds reusable patterns for the rest of the backend.
|
|
||||||
|
|
||||||
### 7.2 Second Slice
|
|
||||||
|
|
||||||
1. `hostAccessRepository`
|
|
||||||
2. `roleRepository`
|
|
||||||
3. `sharedCredentialRepository`
|
|
||||||
4. `auditRepository`
|
|
||||||
5. `userPreferencesRepository`
|
|
||||||
|
|
||||||
Reasons:
|
|
||||||
|
|
||||||
- Completes permission and sharing boundaries.
|
|
||||||
- Removes high-risk direct writes in admin/RBAC flows.
|
|
||||||
- Moves audit to an append-only repository.
|
|
||||||
|
|
||||||
### 7.3 Third Slice
|
|
||||||
|
|
||||||
1. `snippetRepository`
|
|
||||||
2. `fileManagerRepository`
|
|
||||||
3. `metricsRepository`
|
|
||||||
4. `alertRepository`
|
|
||||||
5. `homepageRepository`
|
|
||||||
|
|
||||||
Reasons:
|
|
||||||
|
|
||||||
- These are broad feature domains with many routes.
|
|
||||||
- They should reuse patterns from the core slices.
|
|
||||||
|
|
||||||
### 7.4 Fourth Slice
|
|
||||||
|
|
||||||
1. `vaultRepository`
|
|
||||||
2. `opksshTokenRepository`
|
|
||||||
3. `termixIdentityRepository`
|
|
||||||
4. `tunnelPresetRepository`
|
|
||||||
5. `tmuxRepository`
|
|
||||||
|
|
||||||
Reasons:
|
|
||||||
|
|
||||||
- Sensitive token/key cache domains need careful encryption tests.
|
|
||||||
- Some data is transient and should have retention cleanup.
|
|
||||||
|
|
||||||
## 8. Adapter Design Notes
|
|
||||||
|
|
||||||
The adapter boundary should expose:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface DatabaseAdapter {
|
|
||||||
dialect: "sqlite" | "postgres" | "mysql";
|
|
||||||
connect(): Promise<void>;
|
|
||||||
close(): Promise<void>;
|
|
||||||
migrate(): Promise<void>;
|
|
||||||
transaction<T>(fn: (tx: DatabaseTransaction) => Promise<T>): Promise<T>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The repository layer should not depend on `better-sqlite3`.
|
|
||||||
|
|
||||||
For Drizzle, likely options:
|
|
||||||
|
|
||||||
- keep dialect-specific Drizzle clients internally
|
|
||||||
- expose repositories instead of exposing the raw Drizzle client
|
|
||||||
- keep schema definitions close to migrations, not route handlers
|
|
||||||
|
|
||||||
Important: do not make route modules import dialect-specific schema objects after migration.
|
|
||||||
|
|
||||||
## 9. Compatibility Shims
|
|
||||||
|
|
||||||
During the migration, avoid a flag day.
|
|
||||||
|
|
||||||
Add a compatibility database module that lets old code continue to run while new repositories are introduced:
|
|
||||||
|
|
||||||
```text
|
|
||||||
legacy getDb() path
|
|
||||||
new adapter/repository path
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
|
|
||||||
- New modules use repositories only.
|
|
||||||
- Migrated modules must not fall back to `getDb()`.
|
|
||||||
- Legacy path is removed only after all domains move.
|
|
||||||
|
|
||||||
## 10. Phase 1 Entry Criteria
|
|
||||||
|
|
||||||
Before implementing the adapter skeleton:
|
|
||||||
|
|
||||||
- This audit document exists.
|
|
||||||
- The sensitive field map is reviewed.
|
|
||||||
- The first vertical slice is accepted.
|
|
||||||
- The draft PR remains draft.
|
|
||||||
- No runtime behavior has changed.
|
|
||||||
|
|
||||||
## 11. Phase 1 Deliverables
|
|
||||||
|
|
||||||
Recommended first implementation PR on this branch:
|
|
||||||
|
|
||||||
- `src/backend/database/runtime/config.ts`
|
|
||||||
- `src/backend/database/runtime/adapter.ts`
|
|
||||||
- `src/backend/database/runtime/sqlite-adapter.ts`
|
|
||||||
- `src/backend/database/repositories/settings-repository.ts`
|
|
||||||
- `src/backend/database/repositories/user-repository.ts`
|
|
||||||
- `src/backend/database/repositories/session-repository.ts`
|
|
||||||
- `src/backend/database/repositories/host-repository.ts`
|
|
||||||
- `src/backend/database/repositories/credential-repository.ts`
|
|
||||||
- `src/backend/database/repositories/field-encryption-boundary.ts`
|
|
||||||
- `src/backend/database/repositories/current-settings-repository.ts`
|
|
||||||
- tests for config parsing and SQLite adapter boot
|
|
||||||
|
|
||||||
Started:
|
|
||||||
|
|
||||||
- runtime config parser
|
|
||||||
- SQLite adapter skeleton
|
|
||||||
- migration metadata table bootstrap
|
|
||||||
- `SettingsRepository` skeleton and tests
|
|
||||||
- `UserRepository` and `SessionRepository` skeletons and tests
|
|
||||||
- `HostRepository` and `CredentialRepository` skeletons and tests
|
|
||||||
- `FieldEncryptionBoundary` skeleton and tests
|
|
||||||
- first settings route slice wired through `SettingsRepository`
|
|
||||||
- user settings route direct `settings` table access moved behind
|
|
||||||
`SettingsRepository`
|
|
||||||
- host metrics settings route direct `settings` table access moved behind
|
|
||||||
`SettingsRepository`
|
|
||||||
- ACME SSL settings route direct `settings` table access moved behind
|
|
||||||
`SettingsRepository`
|
|
||||||
- terminal route direct `settings` table access moved behind
|
|
||||||
`SettingsRepository`
|
|
||||||
- tailscale route direct `settings` table access moved behind
|
|
||||||
`SettingsRepository`
|
|
||||||
- Guacamole route and WebSocket server direct `settings` table access moved
|
|
||||||
behind the current settings repository boundary
|
|
||||||
- auth token expiry and terminal session timeout settings reads moved behind the
|
|
||||||
current settings repository boundary
|
|
||||||
- open tabs, TOTP, and LDAP auth route settings reads moved behind the current
|
|
||||||
settings repository boundary
|
|
||||||
- host metrics polling settings reads moved behind the current settings
|
|
||||||
repository boundary
|
|
||||||
- backend startup settings reads moved behind the current settings repository
|
|
||||||
boundary
|
|
||||||
- user deletion cleanup now removes per-user settings through
|
|
||||||
`SettingsRepository.deleteLike`
|
|
||||||
- password reset route reset code and temporary token settings access moved
|
|
||||||
behind `SettingsRepository`
|
|
||||||
- OIDC utility legacy config fallback reads `oidc_config` through
|
|
||||||
`SettingsRepository`
|
|
||||||
- user route registration/password flags and OIDC config administration moved
|
|
||||||
behind the current settings repository boundary
|
|
||||||
- OIDC authorize/callback temporary state and auto-provision reads moved behind
|
|
||||||
the current settings repository boundary
|
|
||||||
- user login settings reads moved behind the current settings repository
|
|
||||||
boundary, completing direct `settings` access cleanup in `routes/users.ts`
|
|
||||||
- user encryption metadata in `utils/user-crypto.ts` moved behind the current
|
|
||||||
settings repository boundary
|
|
||||||
- database startup and schema migration defaults in `database/db/index.ts`
|
|
||||||
moved to local raw settings helpers
|
|
||||||
- database import/export settings handling in `database/database.ts` moved to
|
|
||||||
local helper boundaries
|
|
||||||
- core `auth-manager.ts` session create/read/update/revoke/list paths started
|
|
||||||
using the current session repository boundary
|
|
||||||
- remaining `auth-manager.ts` session cleanup/middleware/logout paths and
|
|
||||||
`user-session-routes.ts` single-session lookup moved behind the current
|
|
||||||
session repository boundary
|
|
||||||
- current user repository factory/write-save hook added, and
|
|
||||||
`user-admin-routes.ts` list/admin promotion/admin removal/admin-create user
|
|
||||||
paths moved behind the current user repository boundary
|
|
||||||
- low-risk `routes/users.ts` current-user lookup and admin gate checks moved
|
|
||||||
behind the current user repository boundary
|
|
||||||
- user registration, self-delete, password change hash updates, and admin
|
|
||||||
delete-user lookup paths in `routes/users.ts` moved behind the current user
|
|
||||||
repository boundary, with first-user admin creation kept transactional inside
|
|
||||||
`UserRepository`
|
|
||||||
- traditional login username lookup and `auth-manager.ts` admin user checks
|
|
||||||
moved behind the current user repository boundary
|
|
||||||
- GitHub and standard OIDC callback user lookup/create/rollback/profile/admin
|
|
||||||
sync writes moved behind the current user repository boundary, removing direct
|
|
||||||
Drizzle `users` table access from `routes/users.ts`, `user-admin-routes.ts`,
|
|
||||||
and `auth-manager.ts`
|
|
||||||
- API key create/list/delete and API key authentication last-used updates moved
|
|
||||||
behind the current API key repository boundary
|
|
||||||
- trusted device check/add/remove and TOTP trusted-device cleanup moved behind
|
|
||||||
the current trusted device repository boundary
|
|
||||||
- user session routes moved user/admin lookups and admin session username
|
|
||||||
enrichment behind the current user repository boundary
|
|
||||||
- vault admin checks, Termix ID audit username lookup, permission manager admin
|
|
||||||
checks, and user data export user lookup moved behind the current user
|
|
||||||
repository boundary
|
|
||||||
- SSH credential OIDC username expansion and tmux monitor audit actor username
|
|
||||||
lookup moved behind the current user repository boundary
|
|
||||||
- user settings route admin checks and audit actor username lookups moved behind
|
|
||||||
the current user repository boundary
|
|
||||||
- ACME SSL route admin checks and audit actor username lookups moved behind the
|
|
||||||
current user repository boundary
|
|
||||||
- audit log route admin checks moved behind the current user repository boundary
|
|
||||||
- OIDC account link/unlink route user lookups and OIDC field updates moved
|
|
||||||
behind the current user repository boundary
|
|
||||||
- password reset route user lookups, password hash updates, and TOTP reset
|
|
||||||
fields moved behind the current user repository boundary
|
|
||||||
- user deletion helper now removes sessions through the current session
|
|
||||||
repository and the final user record through the current user repository
|
|
||||||
- snippet create/update/delete audit actor username lookups moved behind the
|
|
||||||
current user repository boundary
|
|
||||||
- LDAP login existing-user lookup, encryption rollback delete, admin sync, and
|
|
||||||
display-name sync moved behind the current user repository boundary
|
|
||||||
- TOTP setup/enable/disable/backup-code/login verification user updates and
|
|
||||||
session revocation moved behind the current user/session repository
|
|
||||||
boundaries
|
|
||||||
- RBAC host sharing, role assignment, and snippet sharing target-user existence
|
|
||||||
checks moved behind the current user repository boundary, with existing
|
|
||||||
RBAC/snippet owner username joins retained
|
|
||||||
- RBAC role list/create/update/delete, user-role assignment/removal/listing,
|
|
||||||
and shared host/snippet role-id lookups moved behind the current role
|
|
||||||
repository boundary, with existing RBAC/share credential joins retained
|
|
||||||
- permission manager role permission aggregation, role-id lookups for shared
|
|
||||||
host access, and admin role checks moved behind the current role repository
|
|
||||||
boundary
|
|
||||||
- RBAC host/snippet access-list read models moved behind the current RBAC access
|
|
||||||
repository boundary, and snippet route shared-access role-id lookups moved
|
|
||||||
behind the current role repository boundary
|
|
||||||
- RBAC shared host/shared snippet read models and the main snippet
|
|
||||||
shared-snippet read model moved behind the current RBAC access repository
|
|
||||||
boundary
|
|
||||||
- RBAC host/snippet access grant, revoke, and direct host-access credential
|
|
||||||
override writes moved behind the current RBAC access repository boundary,
|
|
||||||
with shared credential material creation retained in the existing manager
|
|
||||||
- permission manager host-access expiration cleanup, shared host-access lookup,
|
|
||||||
and last-access timestamp updates moved behind the current RBAC access
|
|
||||||
repository boundary
|
|
||||||
- repository rollout guard added through `DATABASE_LAYER_REPOSITORY_ROLLOUT`
|
|
||||||
for the migrated settings/users/sessions/API-key/trusted-device/role/RBAC-access
|
|
||||||
slice
|
|
||||||
|
|
||||||
Keep it small. Do not wire host or credential routes into the new repositories in
|
|
||||||
the same first implementation commit.
|
|
||||||
|
|
||||||
## 12. Current Unknowns
|
|
||||||
|
|
||||||
- Exact Drizzle multi-dialect strategy needs a spike.
|
|
||||||
- The project may need a migration generator or a custom migration runner.
|
|
||||||
- Some raw SQL in `routes/users.ts`, `alert-rules-routes.ts`, and `db/index.ts` must be rewritten or isolated.
|
|
||||||
- `settings` contains mixed public and sensitive values; key-level classification is required.
|
|
||||||
- `homepage_items.config`, `notification_channels.config`, and tunnel configs may contain embedded secrets.
|
|
||||||
- Legacy encrypted snapshot fixtures need to be created before migration implementation.
|
|
||||||
|
|
||||||
## 13. Decision Log
|
|
||||||
|
|
||||||
- Default upgrade target should be persistent SQLite, not PostgreSQL/MySQL.
|
|
||||||
- PostgreSQL/MySQL migration should be explicit and initially manual/experimental.
|
|
||||||
- Runtime SSH/WebSocket/tunnel state remains memory-only.
|
|
||||||
- Field-level encryption is mandatory for every database backend.
|
|
||||||
- Field-level encryption must use a stable record id; temporary encryption
|
|
||||||
contexts are forbidden for newly written repository data.
|
|
||||||
- Repository migration should start with settings/users/sessions/hosts/credentials.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,12 @@ import { dashboardLogger } from "./utils/logger.js";
|
|||||||
import { AuthManager } from "./utils/auth-manager.js";
|
import { AuthManager } from "./utils/auth-manager.js";
|
||||||
import type { AuthenticatedRequest } from "../types/index.js";
|
import type { AuthenticatedRequest } from "../types/index.js";
|
||||||
import { dashboardServiceLinksRouter } from "./database/routes/dashboard-service-links-routes.js";
|
import { dashboardServiceLinksRouter } from "./database/routes/dashboard-service-links-routes.js";
|
||||||
import { createCurrentHostResolutionRepository } from "./database/repositories/current-host-resolution-repository.js";
|
import {
|
||||||
import { createCurrentRbacAccessRepository } from "./database/repositories/current-rbac-access-repository.js";
|
createCurrentHostResolutionRepository,
|
||||||
import { createCurrentRecentActivityRepository } from "./database/repositories/current-recent-activity-repository.js";
|
createCurrentRbacAccessRepository,
|
||||||
import { createCurrentRoleRepository } from "./database/repositories/current-role-repository.js";
|
createCurrentRecentActivityRepository,
|
||||||
|
createCurrentRoleRepository,
|
||||||
|
} from "./database/repositories/factory.js";
|
||||||
import { DataCrypto } from "./utils/data-crypto.js";
|
import { DataCrypto } from "./utils/data-crypto.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|||||||
@@ -34,15 +34,16 @@ import { DatabaseFileEncryption } from "../utils/database-file-encryption.js";
|
|||||||
import { DatabaseMigration } from "../utils/database-migration.js";
|
import { DatabaseMigration } from "../utils/database-migration.js";
|
||||||
import { UserDataExport } from "../utils/user-data-export.js";
|
import { UserDataExport } from "../utils/user-data-export.js";
|
||||||
import { AutoSSLSetup } from "../utils/auto-ssl-setup.js";
|
import { AutoSSLSetup } from "../utils/auto-ssl-setup.js";
|
||||||
import { createCurrentCredentialRepository } from "./repositories/current-credential-repository.js";
|
import {
|
||||||
import { createCurrentDismissedAlertRepository } from "./repositories/current-dismissed-alert-repository.js";
|
createCurrentCredentialRepository,
|
||||||
import { createCurrentFileManagerBookmarkRepository } from "./repositories/current-file-manager-bookmark-repository.js";
|
createCurrentDismissedAlertRepository,
|
||||||
import { createCurrentHostRepository } from "./repositories/current-host-repository.js";
|
createCurrentFileManagerBookmarkRepository,
|
||||||
import { createCurrentSettingsRepository } from "./repositories/current-settings-repository.js";
|
createCurrentHostRepository,
|
||||||
import { createCurrentSshCredentialUsageRepository } from "./repositories/current-ssh-credential-usage-repository.js";
|
createCurrentSettingsRepository,
|
||||||
import { createCurrentUserRepository } from "./repositories/current-user-repository.js";
|
createCurrentSshCredentialUsageRepository,
|
||||||
import { getRepositoryRolloutStatus } from "./repositories/repository-rollout.js";
|
createCurrentUserRepository,
|
||||||
import { withCurrentSqliteForeignKeysDisabled } from "./runtime/sqlite-foreign-key-boundary.js";
|
} from "./repositories/factory.js";
|
||||||
|
import { withCurrentSqliteForeignKeysDisabled } from "./repositories/sqlite-foreign-keys.js";
|
||||||
import { parseUserAgent } from "../utils/user-agent-parser.js";
|
import { parseUserAgent } from "../utils/user-agent-parser.js";
|
||||||
import { getProxyAgent } from "../utils/proxy-agent.js";
|
import { getProxyAgent } from "../utils/proxy-agent.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -1895,7 +1896,6 @@ app.get(
|
|||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
migrationStatus: status,
|
migrationStatus: status,
|
||||||
repositoryRollout: getRepositoryRolloutStatus(),
|
|
||||||
files: {
|
files: {
|
||||||
unencryptedDbSize: unencryptedSize,
|
unencryptedDbSize: unencryptedSize,
|
||||||
encryptedDbSize: encryptedSize,
|
encryptedDbSize: encryptedSize,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { AlertRepository } from "./alert-repository.js";
|
import { AlertRepository } from "./alert-repository.js";
|
||||||
|
|
||||||
describe("AlertRepository", () => {
|
describe("AlertRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("AlertRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<AlertRepository> {
|
): Promise<AlertRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
hosts,
|
hosts,
|
||||||
notificationChannels,
|
notificationChannels,
|
||||||
} from "../db/schema.js";
|
} from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
type AlertRuleRecord = typeof alertRules.$inferSelect;
|
type AlertRuleRecord = typeof alertRules.$inferSelect;
|
||||||
type NotificationChannelRecord = typeof notificationChannels.$inferSelect;
|
type NotificationChannelRecord = typeof notificationChannels.$inferSelect;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { ApiKeyRepository } from "./api-key-repository.js";
|
import { ApiKeyRepository } from "./api-key-repository.js";
|
||||||
|
|
||||||
describe("ApiKeyRepository", () => {
|
describe("ApiKeyRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("ApiKeyRepository", () => {
|
|||||||
async function createRepository(onWrite?: () => void): Promise<{
|
async function createRepository(onWrite?: () => void): Promise<{
|
||||||
apiKeys: ApiKeyRepository;
|
apiKeys: ApiKeyRepository;
|
||||||
}> {
|
}> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { apiKeys, users } from "../db/schema.js";
|
import { apiKeys, users } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type ApiKeyRecord = typeof apiKeys.$inferSelect;
|
export type ApiKeyRecord = typeof apiKeys.$inferSelect;
|
||||||
export type NewApiKeyRecord = typeof apiKeys.$inferInsert;
|
export type NewApiKeyRecord = typeof apiKeys.$inferInsert;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { AuditLogRepository } from "./audit-log-repository.js";
|
import { AuditLogRepository } from "./audit-log-repository.js";
|
||||||
|
|
||||||
describe("AuditLogRepository", () => {
|
describe("AuditLogRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("AuditLogRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<AuditLogRepository> {
|
): Promise<AuditLogRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
|
import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
|
||||||
import { auditLogs } from "../db/schema.js";
|
import { auditLogs } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type AuditLogRecord = typeof auditLogs.$inferSelect;
|
export type AuditLogRecord = typeof auditLogs.$inferSelect;
|
||||||
export type NewAuditLogRecord = typeof auditLogs.$inferInsert;
|
export type NewAuditLogRecord = typeof auditLogs.$inferInsert;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
|
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
|
||||||
|
|
||||||
describe("C2sTunnelPresetRepository", () => {
|
describe("C2sTunnelPresetRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("C2sTunnelPresetRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<C2sTunnelPresetRepository> {
|
): Promise<C2sTunnelPresetRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, asc, eq, sql } from "drizzle-orm";
|
import { and, asc, eq, sql } from "drizzle-orm";
|
||||||
import { c2sTunnelPresets } from "../db/schema.js";
|
import { c2sTunnelPresets } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type C2sTunnelPresetRecord = typeof c2sTunnelPresets.$inferSelect;
|
export type C2sTunnelPresetRecord = typeof c2sTunnelPresets.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { CommandHistoryRepository } from "./command-history-repository.js";
|
import { CommandHistoryRepository } from "./command-history-repository.js";
|
||||||
|
|
||||||
describe("CommandHistoryRepository", () => {
|
describe("CommandHistoryRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("CommandHistoryRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<CommandHistoryRepository> {
|
): Promise<CommandHistoryRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
||||||
import { commandHistory } from "../db/schema.js";
|
import { commandHistory } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type CommandHistoryRecord = typeof commandHistory.$inferSelect;
|
export type CommandHistoryRecord = typeof commandHistory.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, desc, eq, isNull, or, sql } from "drizzle-orm";
|
import { and, desc, eq, isNull, or, sql } from "drizzle-orm";
|
||||||
import { sshCredentials, sshCredentialUsage } from "../db/schema.js";
|
import { sshCredentials, sshCredentialUsage } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
import { DataCrypto } from "../../utils/data-crypto.js";
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
||||||
import { SystemCrypto } from "../../utils/system-crypto.js";
|
import { SystemCrypto } from "../../utils/system-crypto.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { AlertRepository } from "./alert-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentAlertRepository(): AlertRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("alerts");
|
|
||||||
|
|
||||||
return new AlertRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("alert_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { ApiKeyRepository } from "./api-key-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentApiKeyRepository(): ApiKeyRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("api_keys");
|
|
||||||
|
|
||||||
return new ApiKeyRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("api_key_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { AuditLogRepository } from "./audit-log-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentAuditLogRepository(): AuditLogRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("audit_logs");
|
|
||||||
|
|
||||||
return new AuditLogRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("audit_log_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentC2sTunnelPresetRepository(): C2sTunnelPresetRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("c2s_tunnel_presets");
|
|
||||||
|
|
||||||
return new C2sTunnelPresetRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("c2s_tunnel_preset_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { CommandHistoryRepository } from "./command-history-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentCommandHistoryRepository(): CommandHistoryRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("command_history");
|
|
||||||
|
|
||||||
return new CommandHistoryRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("command_history_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CredentialRepository } from "./credential-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentCredentialRepository(): CredentialRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("credentials");
|
|
||||||
|
|
||||||
return new CredentialRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("credential_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentDashboardServiceLinkRepository(): DashboardServiceLinkRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("dashboard_service_links");
|
|
||||||
|
|
||||||
return new DashboardServiceLinkRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("dashboard_service_link_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentDismissedAlertRepository(): DismissedAlertRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("dismissed_alerts");
|
|
||||||
|
|
||||||
return new DismissedAlertRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("dismissed_alert_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmarkRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("file_manager_bookmarks");
|
|
||||||
|
|
||||||
return new FileManagerBookmarkRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("file_manager_bookmarks_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { HomepageItemRepository } from "./homepage-item-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentHomepageItemRepository(): HomepageItemRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("homepage_items");
|
|
||||||
|
|
||||||
return new HomepageItemRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("homepage_item_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentHomepageLayoutRepository(): HomepageLayoutRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("homepage_layouts");
|
|
||||||
|
|
||||||
return new HomepageLayoutRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("homepage_layout_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { HostFolderRepository } from "./host-folder-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentHostFolderRepository(): HostFolderRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("host_folders");
|
|
||||||
|
|
||||||
return new HostFolderRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("host_folder_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { HostHealthRepository } from "./host-health-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentHostHealthRepository(): HostHealthRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("host_health");
|
|
||||||
|
|
||||||
return new HostHealthRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("host_health_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentHostMetricsHistoryRepository(): HostMetricsHistoryRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("host_metrics_history");
|
|
||||||
|
|
||||||
return new HostMetricsHistoryRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("host_metrics_history_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentHostMetricsPreferenceRepository(): HostMetricsPreferenceRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("host_metrics_preferences");
|
|
||||||
|
|
||||||
return new HostMetricsPreferenceRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook(
|
|
||||||
"host_metrics_preference_repository_write",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { HostRepository } from "./host-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentHostRepository(): HostRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("hosts");
|
|
||||||
|
|
||||||
return new HostRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("host_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { HostResolutionRepository } from "./host-resolution-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentHostResolutionRepository(): HostResolutionRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("host_resolution");
|
|
||||||
|
|
||||||
return new HostResolutionRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("host_resolution_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { NetworkTopologyRepository } from "./network-topology-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentNetworkTopologyRepository(): NetworkTopologyRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("network_topology");
|
|
||||||
|
|
||||||
return new NetworkTopologyRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("network_topology_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { OpenTabRepository } from "./open-tab-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentOpenTabRepository(): OpenTabRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("open_tabs");
|
|
||||||
|
|
||||||
return new OpenTabRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("open_tab_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentOpksshTokenRepository(): OpksshTokenRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("opkssh_tokens");
|
|
||||||
|
|
||||||
return new OpksshTokenRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("opkssh_token_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import { RbacAccessRepository } from "./rbac-access-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
|
|
||||||
export function createCurrentRbacAccessRepository(): RbacAccessRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("rbac_access");
|
|
||||||
|
|
||||||
return new RbacAccessRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("rbac_access_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { RecentActivityRepository } from "./recent-activity-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentRecentActivityRepository(): RecentActivityRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("recent_activity");
|
|
||||||
|
|
||||||
return new RecentActivityRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("recent_activity_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
|
|
||||||
import { getDb, getSqlite } from "../db/index.js";
|
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
|
||||||
|
|
||||||
export function createCurrentRepositoryContext(): DatabaseContext {
|
|
||||||
return {
|
|
||||||
dialect: "sqlite",
|
|
||||||
drizzle: getDb(),
|
|
||||||
sqlite: getSqlite(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCurrentRepositoryWriteHook(
|
|
||||||
reason: string,
|
|
||||||
): () => Promise<void> {
|
|
||||||
return () => DatabaseSaveTrigger.forceSave(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentRepositorySqlite() {
|
|
||||||
return getSqlite();
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { RoleRepository } from "./role-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentRoleRepository(): RoleRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("roles");
|
|
||||||
|
|
||||||
return new RoleRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("role_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { SessionRecordingRepository } from "./session-recording-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentSessionRecordingRepository(): SessionRecordingRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("session_recordings");
|
|
||||||
|
|
||||||
return new SessionRecordingRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("session_recording_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { SessionRepository } from "./session-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentSessionRepository(): SessionRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("sessions");
|
|
||||||
|
|
||||||
return new SessionRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("session_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { SettingsRepository } from "./settings-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
getCurrentRepositorySqlite,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentSettingsRepository(): SettingsRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("settings");
|
|
||||||
|
|
||||||
return new SettingsRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("settings_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentSettingValue(key: string): string | null {
|
|
||||||
assertRepositoryRolloutDomainEnabled("settings");
|
|
||||||
|
|
||||||
const row = getCurrentRepositorySqlite()
|
|
||||||
.prepare("SELECT value FROM settings WHERE key = ?")
|
|
||||||
.get(key) as { value?: string } | undefined;
|
|
||||||
|
|
||||||
return row?.value ?? null;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { SharedCredentialRepository } from "./shared-credential-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentSharedCredentialRepository(): SharedCredentialRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("shared_credentials");
|
|
||||||
|
|
||||||
return new SharedCredentialRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("shared_credential_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { SnippetRepository } from "./snippet-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentSnippetRepository(): SnippetRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("snippets");
|
|
||||||
|
|
||||||
return new SnippetRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("snippet_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentSshCredentialUsageRepository(): SshCredentialUsageRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("ssh_credential_usage");
|
|
||||||
|
|
||||||
return new SshCredentialUsageRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("ssh_credential_usage_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { SsoProviderRepository } from "./sso-provider-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentSsoProviderRepository(): SsoProviderRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("sso_providers");
|
|
||||||
|
|
||||||
return new SsoProviderRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("sso_provider_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentTermixIdentityCaRepository(): TermixIdentityCaRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("termix_identity_ca");
|
|
||||||
|
|
||||||
return new TermixIdentityCaRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("termix_identity_ca_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { TermixIdentityRepository } from "./termix-identity-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentTermixIdentityRepository(): TermixIdentityRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("termix_identity");
|
|
||||||
|
|
||||||
return new TermixIdentityRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("termix_identity_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentTmuxSessionTagRepository(): TmuxSessionTagRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("tmux_session_tags");
|
|
||||||
|
|
||||||
return new TmuxSessionTagRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("tmux_session_tag_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { TransferRecentRepository } from "./transfer-recent-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentTransferRecentRepository(): TransferRecentRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("transfer_recent");
|
|
||||||
|
|
||||||
return new TransferRecentRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("transfer_recent_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { TrustedDeviceRepository } from "./trusted-device-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentTrustedDeviceRepository(): TrustedDeviceRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("trusted_devices");
|
|
||||||
|
|
||||||
return new TrustedDeviceRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("trusted_device_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import { createCurrentRepositoryContext } from "./current-repository-runtime.js";
|
|
||||||
import { UserDataExportRepository } from "./user-data-export-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentUserDataExportRepository(): UserDataExportRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("user_data_exports");
|
|
||||||
|
|
||||||
return new UserDataExportRepository(createCurrentRepositoryContext());
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { UserPreferenceRepository } from "./user-preference-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentUserPreferenceRepository(): UserPreferenceRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("user_preferences");
|
|
||||||
|
|
||||||
return new UserPreferenceRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("user_preference_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { UserRepository } from "./user-repository.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
|
|
||||||
export function createCurrentUserRepository(): UserRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("users");
|
|
||||||
|
|
||||||
return new UserRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("user_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { VaultProfileRepository } from "./vault-profile-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentVaultProfileRepository(): VaultProfileRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("vault_profiles");
|
|
||||||
|
|
||||||
return new VaultProfileRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("vault_profile_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { assertRepositoryRolloutDomainEnabled } from "./repository-rollout.js";
|
|
||||||
import {
|
|
||||||
createCurrentRepositoryContext,
|
|
||||||
createCurrentRepositoryWriteHook,
|
|
||||||
} from "./current-repository-runtime.js";
|
|
||||||
import { VaultTokenRepository } from "./vault-token-repository.js";
|
|
||||||
|
|
||||||
export function createCurrentVaultTokenRepository(): VaultTokenRepository {
|
|
||||||
assertRepositoryRolloutDomainEnabled("vault_tokens");
|
|
||||||
|
|
||||||
return new VaultTokenRepository(
|
|
||||||
createCurrentRepositoryContext(),
|
|
||||||
createCurrentRepositoryWriteHook("vault_token_repository_write"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
|
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
|
||||||
|
|
||||||
describe("DashboardServiceLinkRepository", () => {
|
describe("DashboardServiceLinkRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("DashboardServiceLinkRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<DashboardServiceLinkRepository> {
|
): Promise<DashboardServiceLinkRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, asc, eq } from "drizzle-orm";
|
import { and, asc, eq } from "drizzle-orm";
|
||||||
import { dashboardServiceLinks } from "../db/schema.js";
|
import { dashboardServiceLinks } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type DashboardServiceLinkRecord =
|
export type DashboardServiceLinkRecord =
|
||||||
typeof dashboardServiceLinks.$inferSelect;
|
typeof dashboardServiceLinks.$inferSelect;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
|
||||||
|
import type { Database as BetterSqliteDatabase } from "better-sqlite3";
|
||||||
|
import type * as schema from "../db/schema.js";
|
||||||
|
|
||||||
|
export interface DatabaseContext {
|
||||||
|
dialect: "sqlite";
|
||||||
|
drizzle: BetterSQLite3Database<typeof schema>;
|
||||||
|
sqlite?: BetterSqliteDatabase;
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
|
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
|
||||||
|
|
||||||
describe("DismissedAlertRepository", () => {
|
describe("DismissedAlertRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("DismissedAlertRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<DismissedAlertRepository> {
|
): Promise<DismissedAlertRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { dismissedAlerts } from "../db/schema.js";
|
import { dismissedAlerts } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type DismissedAlertRecord = typeof dismissedAlerts.$inferSelect;
|
export type DismissedAlertRecord = typeof dismissedAlerts.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,348 @@
|
|||||||
|
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
|
||||||
|
import { getDb, getSqlite } from "../db/index.js";
|
||||||
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
import { AlertRepository } from "./alert-repository.js";
|
||||||
|
import { ApiKeyRepository } from "./api-key-repository.js";
|
||||||
|
import { AuditLogRepository } from "./audit-log-repository.js";
|
||||||
|
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js";
|
||||||
|
import { CommandHistoryRepository } from "./command-history-repository.js";
|
||||||
|
import { CredentialRepository } from "./credential-repository.js";
|
||||||
|
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js";
|
||||||
|
import { DismissedAlertRepository } from "./dismissed-alert-repository.js";
|
||||||
|
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
|
||||||
|
import { HomepageItemRepository } from "./homepage-item-repository.js";
|
||||||
|
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
|
||||||
|
import { HostFolderRepository } from "./host-folder-repository.js";
|
||||||
|
import { HostHealthRepository } from "./host-health-repository.js";
|
||||||
|
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
|
||||||
|
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
|
||||||
|
import { HostRepository } from "./host-repository.js";
|
||||||
|
import { HostResolutionRepository } from "./host-resolution-repository.js";
|
||||||
|
import { NetworkTopologyRepository } from "./network-topology-repository.js";
|
||||||
|
import { OpenTabRepository } from "./open-tab-repository.js";
|
||||||
|
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
|
||||||
|
import { RbacAccessRepository } from "./rbac-access-repository.js";
|
||||||
|
import { RecentActivityRepository } from "./recent-activity-repository.js";
|
||||||
|
import { RoleRepository } from "./role-repository.js";
|
||||||
|
import { SessionRecordingRepository } from "./session-recording-repository.js";
|
||||||
|
import { SessionRepository } from "./session-repository.js";
|
||||||
|
import { SettingsRepository } from "./settings-repository.js";
|
||||||
|
import { SharedCredentialRepository } from "./shared-credential-repository.js";
|
||||||
|
import { SnippetRepository } from "./snippet-repository.js";
|
||||||
|
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js";
|
||||||
|
import { SsoProviderRepository } from "./sso-provider-repository.js";
|
||||||
|
import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js";
|
||||||
|
import { TermixIdentityRepository } from "./termix-identity-repository.js";
|
||||||
|
import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js";
|
||||||
|
import { TransferRecentRepository } from "./transfer-recent-repository.js";
|
||||||
|
import { TrustedDeviceRepository } from "./trusted-device-repository.js";
|
||||||
|
import { UserDataExportRepository } from "./user-data-export-repository.js";
|
||||||
|
import { UserPreferenceRepository } from "./user-preference-repository.js";
|
||||||
|
import { UserRepository } from "./user-repository.js";
|
||||||
|
import { VaultProfileRepository } from "./vault-profile-repository.js";
|
||||||
|
import { VaultTokenRepository } from "./vault-token-repository.js";
|
||||||
|
|
||||||
|
export function createCurrentRepositoryContext(): DatabaseContext {
|
||||||
|
return {
|
||||||
|
dialect: "sqlite",
|
||||||
|
drizzle: getDb(),
|
||||||
|
sqlite: getSqlite(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentRepositoryWriteHook(
|
||||||
|
reason: string,
|
||||||
|
): () => Promise<void> {
|
||||||
|
return () => DatabaseSaveTrigger.forceSave(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentRepositorySqlite() {
|
||||||
|
return getSqlite();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentSettingValue(key: string): string | null {
|
||||||
|
const row = getCurrentRepositorySqlite()
|
||||||
|
.prepare("SELECT value FROM settings WHERE key = ?")
|
||||||
|
.get(key) as { value?: string } | undefined;
|
||||||
|
|
||||||
|
return row?.value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentAlertRepository(): AlertRepository {
|
||||||
|
return new AlertRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("alert_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentApiKeyRepository(): ApiKeyRepository {
|
||||||
|
return new ApiKeyRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("api_key_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentAuditLogRepository(): AuditLogRepository {
|
||||||
|
return new AuditLogRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("audit_log_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentC2sTunnelPresetRepository(): C2sTunnelPresetRepository {
|
||||||
|
return new C2sTunnelPresetRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("c2s_tunnel_preset_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentCommandHistoryRepository(): CommandHistoryRepository {
|
||||||
|
return new CommandHistoryRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("command_history_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentCredentialRepository(): CredentialRepository {
|
||||||
|
return new CredentialRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("credential_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentDashboardServiceLinkRepository(): DashboardServiceLinkRepository {
|
||||||
|
return new DashboardServiceLinkRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("dashboard_service_link_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentDismissedAlertRepository(): DismissedAlertRepository {
|
||||||
|
return new DismissedAlertRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("dismissed_alert_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentFileManagerBookmarkRepository(): FileManagerBookmarkRepository {
|
||||||
|
return new FileManagerBookmarkRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("file_manager_bookmarks_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHomepageItemRepository(): HomepageItemRepository {
|
||||||
|
return new HomepageItemRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("homepage_item_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHomepageLayoutRepository(): HomepageLayoutRepository {
|
||||||
|
return new HomepageLayoutRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("homepage_layout_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostFolderRepository(): HostFolderRepository {
|
||||||
|
return new HostFolderRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("host_folder_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostHealthRepository(): HostHealthRepository {
|
||||||
|
return new HostHealthRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("host_health_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostMetricsHistoryRepository(): HostMetricsHistoryRepository {
|
||||||
|
return new HostMetricsHistoryRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("host_metrics_history_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostMetricsPreferenceRepository(): HostMetricsPreferenceRepository {
|
||||||
|
return new HostMetricsPreferenceRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook(
|
||||||
|
"host_metrics_preference_repository_write",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostRepository(): HostRepository {
|
||||||
|
return new HostRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("host_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentHostResolutionRepository(): HostResolutionRepository {
|
||||||
|
return new HostResolutionRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("host_resolution_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentNetworkTopologyRepository(): NetworkTopologyRepository {
|
||||||
|
return new NetworkTopologyRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("network_topology_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentOpenTabRepository(): OpenTabRepository {
|
||||||
|
return new OpenTabRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("open_tab_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentOpksshTokenRepository(): OpksshTokenRepository {
|
||||||
|
return new OpksshTokenRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("opkssh_token_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentRbacAccessRepository(): RbacAccessRepository {
|
||||||
|
return new RbacAccessRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("rbac_access_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentRecentActivityRepository(): RecentActivityRepository {
|
||||||
|
return new RecentActivityRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("recent_activity_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentRoleRepository(): RoleRepository {
|
||||||
|
return new RoleRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("role_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSessionRecordingRepository(): SessionRecordingRepository {
|
||||||
|
return new SessionRecordingRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("session_recording_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSessionRepository(): SessionRepository {
|
||||||
|
return new SessionRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("session_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSettingsRepository(): SettingsRepository {
|
||||||
|
return new SettingsRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("settings_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSharedCredentialRepository(): SharedCredentialRepository {
|
||||||
|
return new SharedCredentialRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("shared_credential_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSnippetRepository(): SnippetRepository {
|
||||||
|
return new SnippetRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("snippet_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSshCredentialUsageRepository(): SshCredentialUsageRepository {
|
||||||
|
return new SshCredentialUsageRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("ssh_credential_usage_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentSsoProviderRepository(): SsoProviderRepository {
|
||||||
|
return new SsoProviderRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("sso_provider_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentTermixIdentityCaRepository(): TermixIdentityCaRepository {
|
||||||
|
return new TermixIdentityCaRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("termix_identity_ca_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentTermixIdentityRepository(): TermixIdentityRepository {
|
||||||
|
return new TermixIdentityRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("termix_identity_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentTmuxSessionTagRepository(): TmuxSessionTagRepository {
|
||||||
|
return new TmuxSessionTagRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("tmux_session_tag_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentTransferRecentRepository(): TransferRecentRepository {
|
||||||
|
return new TransferRecentRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("transfer_recent_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentTrustedDeviceRepository(): TrustedDeviceRepository {
|
||||||
|
return new TrustedDeviceRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("trusted_device_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentUserDataExportRepository(): UserDataExportRepository {
|
||||||
|
return new UserDataExportRepository(createCurrentRepositoryContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentUserPreferenceRepository(): UserPreferenceRepository {
|
||||||
|
return new UserPreferenceRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("user_preference_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentUserRepository(): UserRepository {
|
||||||
|
return new UserRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("user_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentVaultProfileRepository(): VaultProfileRepository {
|
||||||
|
return new VaultProfileRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("vault_profile_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCurrentVaultTokenRepository(): VaultTokenRepository {
|
||||||
|
return new VaultTokenRepository(
|
||||||
|
createCurrentRepositoryContext(),
|
||||||
|
createCurrentRepositoryWriteHook("vault_token_repository_write"),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
|
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js";
|
||||||
|
|
||||||
describe("FileManagerBookmarkRepository", () => {
|
describe("FileManagerBookmarkRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("FileManagerBookmarkRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<FileManagerBookmarkRepository> {
|
): Promise<FileManagerBookmarkRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
fileManagerRecent,
|
fileManagerRecent,
|
||||||
fileManagerShortcuts,
|
fileManagerShortcuts,
|
||||||
} from "../db/schema.js";
|
} from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect;
|
export type FileManagerRecentRecord = typeof fileManagerRecent.$inferSelect;
|
||||||
export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect;
|
export type FileManagerPinnedRecord = typeof fileManagerPinned.$inferSelect;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HomepageItemRepository } from "./homepage-item-repository.js";
|
import { HomepageItemRepository } from "./homepage-item-repository.js";
|
||||||
|
|
||||||
describe("HomepageItemRepository", () => {
|
describe("HomepageItemRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("HomepageItemRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HomepageItemRepository> {
|
): Promise<HomepageItemRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, asc, eq } from "drizzle-orm";
|
import { and, asc, eq } from "drizzle-orm";
|
||||||
import { homepageItems } from "../db/schema.js";
|
import { homepageItems } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HomepageItemRecord = typeof homepageItems.$inferSelect;
|
export type HomepageItemRecord = typeof homepageItems.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
|
import { HomepageLayoutRepository } from "./homepage-layout-repository.js";
|
||||||
|
|
||||||
describe("HomepageLayoutRepository", () => {
|
describe("HomepageLayoutRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("HomepageLayoutRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HomepageLayoutRepository> {
|
): Promise<HomepageLayoutRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { homepageLayouts } from "../db/schema.js";
|
import { homepageLayouts } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HomepageLayoutRecord = typeof homepageLayouts.$inferSelect;
|
export type HomepageLayoutRecord = typeof homepageLayouts.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { CredentialRepository } from "./credential-repository.js";
|
import { CredentialRepository } from "./credential-repository.js";
|
||||||
import { HostRepository } from "./host-repository.js";
|
import { HostRepository } from "./host-repository.js";
|
||||||
import { DataCrypto } from "../../utils/data-crypto.js";
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
||||||
import { SystemCrypto } from "../../utils/system-crypto.js";
|
import { SystemCrypto } from "../../utils/system-crypto.js";
|
||||||
|
|
||||||
describe("HostRepository and CredentialRepository", () => {
|
describe("HostRepository and CredentialRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
@@ -23,14 +23,10 @@ describe("HostRepository and CredentialRepository", () => {
|
|||||||
credentials: CredentialRepository;
|
credentials: CredentialRepository;
|
||||||
hosts: HostRepository;
|
hosts: HostRepository;
|
||||||
sqlite: NonNullable<
|
sqlite: NonNullable<
|
||||||
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"]
|
Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
|
||||||
>;
|
>;
|
||||||
}> {
|
}> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HostFolderRepository } from "./host-folder-repository.js";
|
import { HostFolderRepository } from "./host-folder-repository.js";
|
||||||
|
|
||||||
describe("HostFolderRepository", () => {
|
describe("HostFolderRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -17,14 +17,10 @@ describe("HostFolderRepository", () => {
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
repository: HostFolderRepository;
|
repository: HostFolderRepository;
|
||||||
sqlite: NonNullable<
|
sqlite: NonNullable<
|
||||||
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"]
|
Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
|
||||||
>;
|
>;
|
||||||
}> {
|
}> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { and, eq, like, or, sql } from "drizzle-orm";
|
import { and, eq, like, or, sql } from "drizzle-orm";
|
||||||
import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
|
import type { SQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||||
import { hosts, sshCredentials, sshFolders } from "../db/schema.js";
|
import { hosts, sshCredentials, sshFolders } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HostFolderRecord = typeof sshFolders.$inferSelect;
|
export type HostFolderRecord = typeof sshFolders.$inferSelect;
|
||||||
export type HostFolderHostRecord = typeof hosts.$inferSelect;
|
export type HostFolderHostRecord = typeof hosts.$inferSelect;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HostHealthRepository } from "./host-health-repository.js";
|
import { HostHealthRepository } from "./host-health-repository.js";
|
||||||
|
|
||||||
describe("HostHealthRepository", () => {
|
describe("HostHealthRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("HostHealthRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HostHealthRepository> {
|
): Promise<HostHealthRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, desc, eq } from "drizzle-orm";
|
import { and, desc, eq } from "drizzle-orm";
|
||||||
import { hostHealthChecks, hostHealthHistory } from "../db/schema.js";
|
import { hostHealthChecks, hostHealthHistory } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HostHealthCheckRecord = typeof hostHealthChecks.$inferSelect;
|
export type HostHealthCheckRecord = typeof hostHealthChecks.$inferSelect;
|
||||||
export type HostHealthHistoryRecord = typeof hostHealthHistory.$inferSelect;
|
export type HostHealthHistoryRecord = typeof hostHealthHistory.$inferSelect;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
|
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js";
|
||||||
|
|
||||||
describe("HostMetricsHistoryRepository", () => {
|
describe("HostMetricsHistoryRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("HostMetricsHistoryRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HostMetricsHistoryRepository> {
|
): Promise<HostMetricsHistoryRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE hosts (
|
CREATE TABLE hosts (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, asc, eq, gte, lte } from "drizzle-orm";
|
import { and, asc, eq, gte, lte } from "drizzle-orm";
|
||||||
import { hostMetricsHistory } from "../db/schema.js";
|
import { hostMetricsHistory } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HostMetricsHistoryRecord = typeof hostMetricsHistory.$inferSelect;
|
export type HostMetricsHistoryRecord = typeof hostMetricsHistory.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
|
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js";
|
||||||
|
|
||||||
describe("HostMetricsPreferenceRepository", () => {
|
describe("HostMetricsPreferenceRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("HostMetricsPreferenceRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HostMetricsPreferenceRepository> {
|
): Promise<HostMetricsPreferenceRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { hostMetricsPreferences, hosts } from "../db/schema.js";
|
import { hostMetricsPreferences, hosts } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type HostMetricsPreferenceRecord =
|
export type HostMetricsPreferenceRecord =
|
||||||
typeof hostMetricsPreferences.$inferSelect;
|
typeof hostMetricsPreferences.$inferSelect;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
import { hostAccess, hosts } from "../db/schema.js";
|
import { hostAccess, hosts } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
import { DataCrypto } from "../../utils/data-crypto.js";
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
||||||
|
|
||||||
export type HostRecord = typeof hosts.$inferSelect;
|
export type HostRecord = typeof hosts.$inferSelect;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { DataCrypto } from "../../utils/data-crypto.js";
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
||||||
import { HostResolutionRepository } from "./host-resolution-repository.js";
|
import { HostResolutionRepository } from "./host-resolution-repository.js";
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ vi.mock("../../utils/data-crypto.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("HostResolutionRepository", () => {
|
describe("HostResolutionRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
vi.mocked(DataCrypto.getUserDataKey).mockReset();
|
vi.mocked(DataCrypto.getUserDataKey).mockReset();
|
||||||
@@ -25,11 +25,7 @@ describe("HostResolutionRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<HostResolutionRepository> {
|
): Promise<HostResolutionRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq, inArray, isNotNull } from "drizzle-orm";
|
import { and, eq, inArray, isNotNull } from "drizzle-orm";
|
||||||
import { hostAccess, hosts, sshCredentials } from "../db/schema.js";
|
import { hostAccess, hosts, sshCredentials } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
import { DataCrypto } from "../../utils/data-crypto.js";
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
||||||
|
|
||||||
export type HostResolutionHostRecord = typeof hosts.$inferSelect;
|
export type HostResolutionHostRecord = typeof hosts.$inferSelect;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { NetworkTopologyRepository } from "./network-topology-repository.js";
|
import { NetworkTopologyRepository } from "./network-topology-repository.js";
|
||||||
|
|
||||||
describe("NetworkTopologyRepository", () => {
|
describe("NetworkTopologyRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("NetworkTopologyRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<NetworkTopologyRepository> {
|
): Promise<NetworkTopologyRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { networkTopology } from "../db/schema.js";
|
import { networkTopology } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type NetworkTopologyRecord = typeof networkTopology.$inferSelect;
|
export type NetworkTopologyRecord = typeof networkTopology.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { OpenTabRepository } from "./open-tab-repository.js";
|
import { OpenTabRepository } from "./open-tab-repository.js";
|
||||||
|
|
||||||
describe("OpenTabRepository", () => {
|
describe("OpenTabRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("OpenTabRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<OpenTabRepository> {
|
): Promise<OpenTabRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq, gt } from "drizzle-orm";
|
import { and, eq, gt } from "drizzle-orm";
|
||||||
import { userOpenTabs } from "../db/schema.js";
|
import { userOpenTabs } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type OpenTabRecord = typeof userOpenTabs.$inferSelect;
|
export type OpenTabRecord = typeof userOpenTabs.$inferSelect;
|
||||||
export type NewOpenTabRecord = typeof userOpenTabs.$inferInsert;
|
export type NewOpenTabRecord = typeof userOpenTabs.$inferInsert;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
|
import { OpksshTokenRepository } from "./opkssh-token-repository.js";
|
||||||
|
|
||||||
describe("OpksshTokenRepository", () => {
|
describe("OpksshTokenRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("OpksshTokenRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<OpksshTokenRepository> {
|
): Promise<OpksshTokenRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { opksshTokens } from "../db/schema.js";
|
import { opksshTokens } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type OpksshTokenRecord = typeof opksshTokens.$inferSelect;
|
export type OpksshTokenRecord = typeof opksshTokens.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { RbacAccessRepository } from "./rbac-access-repository.js";
|
import { RbacAccessRepository } from "./rbac-access-repository.js";
|
||||||
|
|
||||||
describe("RbacAccessRepository", () => {
|
describe("RbacAccessRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
const activeAccessTime = "2026-06-26T12:00:00.000Z";
|
const activeAccessTime = "2026-06-26T12:00:00.000Z";
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -16,11 +16,7 @@ describe("RbacAccessRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<RbacAccessRepository> {
|
): Promise<RbacAccessRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
snippets,
|
snippets,
|
||||||
users,
|
users,
|
||||||
} from "../db/schema.js";
|
} from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type RbacAccessTargetType = "user" | "role";
|
export type RbacAccessTargetType = "user" | "role";
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { RecentActivityRepository } from "./recent-activity-repository.js";
|
import { RecentActivityRepository } from "./recent-activity-repository.js";
|
||||||
|
|
||||||
describe("RecentActivityRepository", () => {
|
describe("RecentActivityRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -17,14 +17,10 @@ describe("RecentActivityRepository", () => {
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
repository: RecentActivityRepository;
|
repository: RecentActivityRepository;
|
||||||
sqlite: NonNullable<
|
sqlite: NonNullable<
|
||||||
Awaited<ReturnType<SqliteDatabaseAdapter["connect"]>>["sqlite"]
|
Awaited<ReturnType<TestSqliteDatabase["connect"]>>["sqlite"]
|
||||||
>;
|
>;
|
||||||
}> {
|
}> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { desc, eq, inArray } from "drizzle-orm";
|
import { desc, eq, inArray } from "drizzle-orm";
|
||||||
import { recentActivity } from "../db/schema.js";
|
import { recentActivity } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type RecentActivityRecord = typeof recentActivity.$inferSelect;
|
export type RecentActivityRecord = typeof recentActivity.$inferSelect;
|
||||||
export type NewRecentActivityRecord = typeof recentActivity.$inferInsert;
|
export type NewRecentActivityRecord = typeof recentActivity.$inferInsert;
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import {
|
|
||||||
getRepositoryRolloutStatus,
|
|
||||||
getRepositoryRolloutWarnings,
|
|
||||||
isRepositoryRolloutDomainEnabled,
|
|
||||||
parseRepositoryRolloutConfig,
|
|
||||||
REPOSITORY_ROLLOUT_ENV,
|
|
||||||
} from "./repository-rollout.js";
|
|
||||||
|
|
||||||
describe("parseRepositoryRolloutConfig", () => {
|
|
||||||
it("defaults to the current migrated repository slice", () => {
|
|
||||||
const config = parseRepositoryRolloutConfig({});
|
|
||||||
|
|
||||||
expect(config).toEqual({
|
|
||||||
mode: "all",
|
|
||||||
enabledDomains: [
|
|
||||||
"settings",
|
|
||||||
"users",
|
|
||||||
"sessions",
|
|
||||||
"api_keys",
|
|
||||||
"trusted_devices",
|
|
||||||
"credentials",
|
|
||||||
"termix_identity",
|
|
||||||
"termix_identity_ca",
|
|
||||||
"hosts",
|
|
||||||
"snippets",
|
|
||||||
"roles",
|
|
||||||
"rbac_access",
|
|
||||||
"shared_credentials",
|
|
||||||
"sso_providers",
|
|
||||||
"audit_logs",
|
|
||||||
"user_preferences",
|
|
||||||
"open_tabs",
|
|
||||||
"dismissed_alerts",
|
|
||||||
"homepage_layouts",
|
|
||||||
"homepage_items",
|
|
||||||
"network_topology",
|
|
||||||
"dashboard_service_links",
|
|
||||||
"session_recordings",
|
|
||||||
"command_history",
|
|
||||||
"recent_activity",
|
|
||||||
"ssh_credential_usage",
|
|
||||||
"transfer_recent",
|
|
||||||
"file_manager_bookmarks",
|
|
||||||
"c2s_tunnel_presets",
|
|
||||||
"tmux_session_tags",
|
|
||||||
"opkssh_tokens",
|
|
||||||
"vault_tokens",
|
|
||||||
"vault_profiles",
|
|
||||||
"host_metrics_preferences",
|
|
||||||
"host_health",
|
|
||||||
"host_metrics_history",
|
|
||||||
"alerts",
|
|
||||||
"user_data_exports",
|
|
||||||
"host_folders",
|
|
||||||
"host_resolution",
|
|
||||||
],
|
|
||||||
explicit: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows explicitly disabling all migrated repository domains", () => {
|
|
||||||
const config = parseRepositoryRolloutConfig({
|
|
||||||
[REPOSITORY_ROLLOUT_ENV]: "off",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(config).toEqual({
|
|
||||||
mode: "none",
|
|
||||||
enabledDomains: [],
|
|
||||||
explicit: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("accepts a partial domain allowlist with aliases", () => {
|
|
||||||
const config = parseRepositoryRolloutConfig({
|
|
||||||
[REPOSITORY_ROLLOUT_ENV]:
|
|
||||||
"settings,user,api-key,credential,termix-id,termix-ca,host,dismissed,alerts,layout,items,topology,dashboard-link,recordings,history,activity,usage,transfer,user-data-export,ssh-folder,host-resolver,shared-credentials,bookmarks,c2s,tmux,opkssh,vault-token,vault-profile,metrics-preferences,host-health,metrics-history",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(config).toEqual({
|
|
||||||
mode: "partial",
|
|
||||||
enabledDomains: [
|
|
||||||
"settings",
|
|
||||||
"users",
|
|
||||||
"api_keys",
|
|
||||||
"credentials",
|
|
||||||
"termix_identity",
|
|
||||||
"termix_identity_ca",
|
|
||||||
"hosts",
|
|
||||||
"dismissed_alerts",
|
|
||||||
"alerts",
|
|
||||||
"homepage_layouts",
|
|
||||||
"homepage_items",
|
|
||||||
"network_topology",
|
|
||||||
"dashboard_service_links",
|
|
||||||
"session_recordings",
|
|
||||||
"command_history",
|
|
||||||
"recent_activity",
|
|
||||||
"ssh_credential_usage",
|
|
||||||
"transfer_recent",
|
|
||||||
"user_data_exports",
|
|
||||||
"host_folders",
|
|
||||||
"host_resolution",
|
|
||||||
"shared_credentials",
|
|
||||||
"file_manager_bookmarks",
|
|
||||||
"c2s_tunnel_presets",
|
|
||||||
"tmux_session_tags",
|
|
||||||
"opkssh_tokens",
|
|
||||||
"vault_tokens",
|
|
||||||
"vault_profiles",
|
|
||||||
"host_metrics_preferences",
|
|
||||||
"host_health",
|
|
||||||
"host_metrics_history",
|
|
||||||
],
|
|
||||||
explicit: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deduplicates allowlisted domains", () => {
|
|
||||||
const config = parseRepositoryRolloutConfig({
|
|
||||||
[REPOSITORY_ROLLOUT_ENV]: "users,user,users",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(config.enabledDomains).toEqual(["users"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects unknown domains", () => {
|
|
||||||
expect(() =>
|
|
||||||
parseRepositoryRolloutConfig({
|
|
||||||
[REPOSITORY_ROLLOUT_ENV]: "settings,unknown-domain",
|
|
||||||
}),
|
|
||||||
).toThrow("Unsupported DATABASE_LAYER_REPOSITORY_ROLLOUT domain");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks whether an individual domain is enabled", () => {
|
|
||||||
const env = { [REPOSITORY_ROLLOUT_ENV]: "sessions" };
|
|
||||||
|
|
||||||
expect(isRepositoryRolloutDomainEnabled("sessions", env)).toBe(true);
|
|
||||||
expect(isRepositoryRolloutDomainEnabled("users", env)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds a status payload for admin visibility", () => {
|
|
||||||
const status = getRepositoryRolloutStatus({
|
|
||||||
[REPOSITORY_ROLLOUT_ENV]: "settings,sessions",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toEqual({
|
|
||||||
mode: "partial",
|
|
||||||
enabledDomains: ["settings", "sessions"],
|
|
||||||
explicit: true,
|
|
||||||
envKey: REPOSITORY_ROLLOUT_ENV,
|
|
||||||
supportedDomains: [
|
|
||||||
"settings",
|
|
||||||
"users",
|
|
||||||
"sessions",
|
|
||||||
"api_keys",
|
|
||||||
"trusted_devices",
|
|
||||||
"credentials",
|
|
||||||
"termix_identity",
|
|
||||||
"termix_identity_ca",
|
|
||||||
"hosts",
|
|
||||||
"snippets",
|
|
||||||
"roles",
|
|
||||||
"rbac_access",
|
|
||||||
"shared_credentials",
|
|
||||||
"sso_providers",
|
|
||||||
"audit_logs",
|
|
||||||
"user_preferences",
|
|
||||||
"open_tabs",
|
|
||||||
"dismissed_alerts",
|
|
||||||
"homepage_layouts",
|
|
||||||
"homepage_items",
|
|
||||||
"network_topology",
|
|
||||||
"dashboard_service_links",
|
|
||||||
"session_recordings",
|
|
||||||
"command_history",
|
|
||||||
"recent_activity",
|
|
||||||
"ssh_credential_usage",
|
|
||||||
"transfer_recent",
|
|
||||||
"file_manager_bookmarks",
|
|
||||||
"c2s_tunnel_presets",
|
|
||||||
"tmux_session_tags",
|
|
||||||
"opkssh_tokens",
|
|
||||||
"vault_tokens",
|
|
||||||
"vault_profiles",
|
|
||||||
"host_metrics_preferences",
|
|
||||||
"host_health",
|
|
||||||
"host_metrics_history",
|
|
||||||
"alerts",
|
|
||||||
"user_data_exports",
|
|
||||||
"host_folders",
|
|
||||||
"host_resolution",
|
|
||||||
],
|
|
||||||
warnings: [
|
|
||||||
"Partial repository rollout enabled for domains: settings, sessions.",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("warns when gray rollout is implicit", () => {
|
|
||||||
const warnings = getRepositoryRolloutWarnings(
|
|
||||||
parseRepositoryRolloutConfig({}),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(warnings).toEqual([
|
|
||||||
"DATABASE_LAYER_REPOSITORY_ROLLOUT is not explicitly set; gray targets should set it so rollout state is visible in deployment config.",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("warns when migrated repository domains are disabled", () => {
|
|
||||||
const warnings = getRepositoryRolloutWarnings(
|
|
||||||
parseRepositoryRolloutConfig({ [REPOSITORY_ROLLOUT_ENV]: "off" }),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(warnings).toEqual([
|
|
||||||
"All migrated repository domains are disabled; migrated auth/settings/session paths will fail closed.",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
import { databaseLogger } from "../../utils/logger.js";
|
|
||||||
|
|
||||||
export const REPOSITORY_ROLLOUT_ENV = "DATABASE_LAYER_REPOSITORY_ROLLOUT";
|
|
||||||
|
|
||||||
export const REPOSITORY_ROLLOUT_DOMAINS = [
|
|
||||||
"settings",
|
|
||||||
"users",
|
|
||||||
"sessions",
|
|
||||||
"api_keys",
|
|
||||||
"trusted_devices",
|
|
||||||
"credentials",
|
|
||||||
"termix_identity",
|
|
||||||
"termix_identity_ca",
|
|
||||||
"hosts",
|
|
||||||
"snippets",
|
|
||||||
"roles",
|
|
||||||
"rbac_access",
|
|
||||||
"shared_credentials",
|
|
||||||
"sso_providers",
|
|
||||||
"audit_logs",
|
|
||||||
"user_preferences",
|
|
||||||
"open_tabs",
|
|
||||||
"dismissed_alerts",
|
|
||||||
"homepage_layouts",
|
|
||||||
"homepage_items",
|
|
||||||
"network_topology",
|
|
||||||
"dashboard_service_links",
|
|
||||||
"session_recordings",
|
|
||||||
"command_history",
|
|
||||||
"recent_activity",
|
|
||||||
"ssh_credential_usage",
|
|
||||||
"transfer_recent",
|
|
||||||
"file_manager_bookmarks",
|
|
||||||
"c2s_tunnel_presets",
|
|
||||||
"tmux_session_tags",
|
|
||||||
"opkssh_tokens",
|
|
||||||
"vault_tokens",
|
|
||||||
"vault_profiles",
|
|
||||||
"host_metrics_preferences",
|
|
||||||
"host_health",
|
|
||||||
"host_metrics_history",
|
|
||||||
"alerts",
|
|
||||||
"user_data_exports",
|
|
||||||
"host_folders",
|
|
||||||
"host_resolution",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type RepositoryRolloutDomain =
|
|
||||||
(typeof REPOSITORY_ROLLOUT_DOMAINS)[number];
|
|
||||||
|
|
||||||
export interface RepositoryRolloutConfig {
|
|
||||||
mode: "all" | "none" | "partial";
|
|
||||||
enabledDomains: RepositoryRolloutDomain[];
|
|
||||||
explicit: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RepositoryRolloutStatus extends RepositoryRolloutConfig {
|
|
||||||
envKey: typeof REPOSITORY_ROLLOUT_ENV;
|
|
||||||
supportedDomains: RepositoryRolloutDomain[];
|
|
||||||
warnings: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type EnvLike = Record<string, string | undefined>;
|
|
||||||
|
|
||||||
const DOMAIN_ALIASES: Record<string, RepositoryRolloutDomain> = {
|
|
||||||
api: "api_keys",
|
|
||||||
api_key: "api_keys",
|
|
||||||
api_keys: "api_keys",
|
|
||||||
apikey: "api_keys",
|
|
||||||
apikeys: "api_keys",
|
|
||||||
audit: "audit_logs",
|
|
||||||
audit_log: "audit_logs",
|
|
||||||
audit_logs: "audit_logs",
|
|
||||||
auditlog: "audit_logs",
|
|
||||||
auditlogs: "audit_logs",
|
|
||||||
alert: "alerts",
|
|
||||||
alerts: "alerts",
|
|
||||||
dashboard_link: "dashboard_service_links",
|
|
||||||
dashboard_links: "dashboard_service_links",
|
|
||||||
dashboard_service_link: "dashboard_service_links",
|
|
||||||
dashboard_service_links: "dashboard_service_links",
|
|
||||||
dashboardlink: "dashboard_service_links",
|
|
||||||
dashboardlinks: "dashboard_service_links",
|
|
||||||
command_history: "command_history",
|
|
||||||
commandhistory: "command_history",
|
|
||||||
credential: "credentials",
|
|
||||||
credentials: "credentials",
|
|
||||||
ca: "termix_identity_ca",
|
|
||||||
history: "command_history",
|
|
||||||
ssh_credential: "credentials",
|
|
||||||
ssh_credentials: "credentials",
|
|
||||||
sshcredential: "credentials",
|
|
||||||
sshcredentials: "credentials",
|
|
||||||
terminal_history: "command_history",
|
|
||||||
termix_ca: "termix_identity_ca",
|
|
||||||
termix_id: "termix_identity",
|
|
||||||
termix_id_ca: "termix_identity_ca",
|
|
||||||
termix_identity: "termix_identity",
|
|
||||||
termix_identity_ca: "termix_identity_ca",
|
|
||||||
termix_identity_cas: "termix_identity_ca",
|
|
||||||
termix_identity_key: "termix_identity",
|
|
||||||
termix_identity_keys: "termix_identity",
|
|
||||||
termix_identity_public_keys: "termix_identity",
|
|
||||||
termix_identities: "termix_identity",
|
|
||||||
termixidca: "termix_identity_ca",
|
|
||||||
termixidentity: "termix_identity",
|
|
||||||
dismissed_alert: "dismissed_alerts",
|
|
||||||
dismissed_alerts: "dismissed_alerts",
|
|
||||||
dismissedalert: "dismissed_alerts",
|
|
||||||
dismissedalerts: "dismissed_alerts",
|
|
||||||
dismissed: "dismissed_alerts",
|
|
||||||
homepage_layout: "homepage_layouts",
|
|
||||||
homepage_layouts: "homepage_layouts",
|
|
||||||
homepagelayout: "homepage_layouts",
|
|
||||||
homepagelayouts: "homepage_layouts",
|
|
||||||
layout: "homepage_layouts",
|
|
||||||
layouts: "homepage_layouts",
|
|
||||||
homepage_item: "homepage_items",
|
|
||||||
homepage_items: "homepage_items",
|
|
||||||
homepageitem: "homepage_items",
|
|
||||||
homepageitems: "homepage_items",
|
|
||||||
host_folder: "host_folders",
|
|
||||||
host_folders: "host_folders",
|
|
||||||
hostfolder: "host_folders",
|
|
||||||
hostfolders: "host_folders",
|
|
||||||
host: "hosts",
|
|
||||||
hosts: "hosts",
|
|
||||||
snippet: "snippets",
|
|
||||||
snippets: "snippets",
|
|
||||||
host_resolution: "host_resolution",
|
|
||||||
host_resolver: "host_resolution",
|
|
||||||
hostresolution: "host_resolution",
|
|
||||||
hostresolver: "host_resolution",
|
|
||||||
resolver: "host_resolution",
|
|
||||||
ssh_folder: "host_folders",
|
|
||||||
ssh_folders: "host_folders",
|
|
||||||
sshfolder: "host_folders",
|
|
||||||
sshfolders: "host_folders",
|
|
||||||
item: "homepage_items",
|
|
||||||
items: "homepage_items",
|
|
||||||
recording: "session_recordings",
|
|
||||||
recordings: "session_recordings",
|
|
||||||
session_recording: "session_recordings",
|
|
||||||
session_recordings: "session_recordings",
|
|
||||||
sessionrecording: "session_recordings",
|
|
||||||
sessionrecordings: "session_recordings",
|
|
||||||
network_topologies: "network_topology",
|
|
||||||
network_topology: "network_topology",
|
|
||||||
networktopologies: "network_topology",
|
|
||||||
networktopology: "network_topology",
|
|
||||||
topology: "network_topology",
|
|
||||||
activity: "recent_activity",
|
|
||||||
recent_activities: "recent_activity",
|
|
||||||
recent_activity: "recent_activity",
|
|
||||||
recentactivity: "recent_activity",
|
|
||||||
credential_usage: "ssh_credential_usage",
|
|
||||||
ssh_credential_usage: "ssh_credential_usage",
|
|
||||||
sshcredentialusage: "ssh_credential_usage",
|
|
||||||
usage: "ssh_credential_usage",
|
|
||||||
transfer: "transfer_recent",
|
|
||||||
transfer_recent: "transfer_recent",
|
|
||||||
transferrecent: "transfer_recent",
|
|
||||||
export: "user_data_exports",
|
|
||||||
exports: "user_data_exports",
|
|
||||||
user_data_export: "user_data_exports",
|
|
||||||
user_data_exports: "user_data_exports",
|
|
||||||
userdataexport: "user_data_exports",
|
|
||||||
userdataexports: "user_data_exports",
|
|
||||||
bookmark: "file_manager_bookmarks",
|
|
||||||
bookmarks: "file_manager_bookmarks",
|
|
||||||
file_bookmarks: "file_manager_bookmarks",
|
|
||||||
file_manager_bookmark: "file_manager_bookmarks",
|
|
||||||
file_manager_bookmarks: "file_manager_bookmarks",
|
|
||||||
filemanagerbookmarks: "file_manager_bookmarks",
|
|
||||||
c2s: "c2s_tunnel_presets",
|
|
||||||
c2s_preset: "c2s_tunnel_presets",
|
|
||||||
c2s_presets: "c2s_tunnel_presets",
|
|
||||||
c2s_tunnel_preset: "c2s_tunnel_presets",
|
|
||||||
c2s_tunnel_presets: "c2s_tunnel_presets",
|
|
||||||
c2stunnelpresets: "c2s_tunnel_presets",
|
|
||||||
tmux: "tmux_session_tags",
|
|
||||||
tmux_tag: "tmux_session_tags",
|
|
||||||
tmux_tags: "tmux_session_tags",
|
|
||||||
tmux_session_tag: "tmux_session_tags",
|
|
||||||
tmux_session_tags: "tmux_session_tags",
|
|
||||||
tmuxsessiontags: "tmux_session_tags",
|
|
||||||
opkssh: "opkssh_tokens",
|
|
||||||
opkssh_token: "opkssh_tokens",
|
|
||||||
opkssh_tokens: "opkssh_tokens",
|
|
||||||
opksshtoken: "opkssh_tokens",
|
|
||||||
opksshtokens: "opkssh_tokens",
|
|
||||||
vault_token: "vault_tokens",
|
|
||||||
vault_tokens: "vault_tokens",
|
|
||||||
vaulttoken: "vault_tokens",
|
|
||||||
vaulttokens: "vault_tokens",
|
|
||||||
vault_profile: "vault_profiles",
|
|
||||||
vault_profiles: "vault_profiles",
|
|
||||||
vaultprofile: "vault_profiles",
|
|
||||||
vaultprofiles: "vault_profiles",
|
|
||||||
host_metrics_preference: "host_metrics_preferences",
|
|
||||||
host_metrics_preferences: "host_metrics_preferences",
|
|
||||||
hostmetricspreference: "host_metrics_preferences",
|
|
||||||
hostmetricspreferences: "host_metrics_preferences",
|
|
||||||
metrics_preferences: "host_metrics_preferences",
|
|
||||||
health: "host_health",
|
|
||||||
host_health: "host_health",
|
|
||||||
host_health_checks: "host_health",
|
|
||||||
host_health_history: "host_health",
|
|
||||||
hosthealth: "host_health",
|
|
||||||
host_metrics_history: "host_metrics_history",
|
|
||||||
hostmetricshistory: "host_metrics_history",
|
|
||||||
metrics_history: "host_metrics_history",
|
|
||||||
open_tab: "open_tabs",
|
|
||||||
open_tabs: "open_tabs",
|
|
||||||
opentab: "open_tabs",
|
|
||||||
opentabs: "open_tabs",
|
|
||||||
setting: "settings",
|
|
||||||
settings: "settings",
|
|
||||||
session: "sessions",
|
|
||||||
sessions: "sessions",
|
|
||||||
trusted_device: "trusted_devices",
|
|
||||||
trusted_devices: "trusted_devices",
|
|
||||||
trusteddevice: "trusted_devices",
|
|
||||||
trusteddevices: "trusted_devices",
|
|
||||||
preference: "user_preferences",
|
|
||||||
preferences: "user_preferences",
|
|
||||||
user_preference: "user_preferences",
|
|
||||||
user_preferences: "user_preferences",
|
|
||||||
userpreference: "user_preferences",
|
|
||||||
userpreferences: "user_preferences",
|
|
||||||
role: "roles",
|
|
||||||
roles: "roles",
|
|
||||||
rbac: "rbac_access",
|
|
||||||
rbac_access: "rbac_access",
|
|
||||||
rbacaccess: "rbac_access",
|
|
||||||
shared_credential: "shared_credentials",
|
|
||||||
shared_credentials: "shared_credentials",
|
|
||||||
sharedcredential: "shared_credentials",
|
|
||||||
sharedcredentials: "shared_credentials",
|
|
||||||
sso: "sso_providers",
|
|
||||||
sso_provider: "sso_providers",
|
|
||||||
sso_providers: "sso_providers",
|
|
||||||
ssoprovider: "sso_providers",
|
|
||||||
ssoproviders: "sso_providers",
|
|
||||||
user: "users",
|
|
||||||
users: "users",
|
|
||||||
};
|
|
||||||
|
|
||||||
const DISABLED_VALUES = new Set(["0", "false", "none", "off", "disabled"]);
|
|
||||||
const ENABLED_VALUES = new Set(["1", "true", "all", "on", "enabled"]);
|
|
||||||
|
|
||||||
function parseDomainList(value: string): RepositoryRolloutDomain[] {
|
|
||||||
const domains = value
|
|
||||||
.split(",")
|
|
||||||
.map((part) => part.trim().toLowerCase().replaceAll("-", "_"))
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((part) => {
|
|
||||||
const domain = DOMAIN_ALIASES[part];
|
|
||||||
if (!domain) {
|
|
||||||
throw new Error(
|
|
||||||
`Unsupported ${REPOSITORY_ROLLOUT_ENV} domain '${part}'. Expected one of: ${REPOSITORY_ROLLOUT_DOMAINS.join(", ")}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return domain;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(new Set(domains));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseRepositoryRolloutConfig(
|
|
||||||
env: EnvLike = process.env,
|
|
||||||
): RepositoryRolloutConfig {
|
|
||||||
const raw = env[REPOSITORY_ROLLOUT_ENV];
|
|
||||||
const normalized = raw?.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (!normalized) {
|
|
||||||
return {
|
|
||||||
mode: "all",
|
|
||||||
enabledDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
|
|
||||||
explicit: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ENABLED_VALUES.has(normalized)) {
|
|
||||||
return {
|
|
||||||
mode: "all",
|
|
||||||
enabledDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
|
|
||||||
explicit: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DISABLED_VALUES.has(normalized)) {
|
|
||||||
return { mode: "none", enabledDomains: [], explicit: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabledDomains = parseDomainList(normalized);
|
|
||||||
return {
|
|
||||||
mode:
|
|
||||||
enabledDomains.length === REPOSITORY_ROLLOUT_DOMAINS.length
|
|
||||||
? "all"
|
|
||||||
: "partial",
|
|
||||||
enabledDomains,
|
|
||||||
explicit: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isRepositoryRolloutDomainEnabled(
|
|
||||||
domain: RepositoryRolloutDomain,
|
|
||||||
env: EnvLike = process.env,
|
|
||||||
): boolean {
|
|
||||||
return parseRepositoryRolloutConfig(env).enabledDomains.includes(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRepositoryRolloutStatus(
|
|
||||||
env: EnvLike = process.env,
|
|
||||||
): RepositoryRolloutStatus {
|
|
||||||
const config = parseRepositoryRolloutConfig(env);
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
envKey: REPOSITORY_ROLLOUT_ENV,
|
|
||||||
supportedDomains: [...REPOSITORY_ROLLOUT_DOMAINS],
|
|
||||||
warnings: getRepositoryRolloutWarnings(config),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRepositoryRolloutWarnings(
|
|
||||||
config: RepositoryRolloutConfig,
|
|
||||||
): string[] {
|
|
||||||
const warnings: string[] = [];
|
|
||||||
|
|
||||||
if (!config.explicit) {
|
|
||||||
warnings.push(
|
|
||||||
`${REPOSITORY_ROLLOUT_ENV} is not explicitly set; gray targets should set it so rollout state is visible in deployment config.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.mode === "none") {
|
|
||||||
warnings.push(
|
|
||||||
"All migrated repository domains are disabled; migrated auth/settings/session paths will fail closed.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.mode === "partial") {
|
|
||||||
warnings.push(
|
|
||||||
`Partial repository rollout enabled for domains: ${config.enabledDomains.join(", ")}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return warnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assertRepositoryRolloutDomainEnabled(
|
|
||||||
domain: RepositoryRolloutDomain,
|
|
||||||
): void {
|
|
||||||
if (isRepositoryRolloutDomainEnabled(domain)) return;
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Repository domain '${domain}' is disabled by ${REPOSITORY_ROLLOUT_ENV}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logRepositoryRolloutConfig(env: EnvLike = process.env): void {
|
|
||||||
const config = getRepositoryRolloutStatus(env);
|
|
||||||
databaseLogger.info("Database repository rollout configuration loaded", {
|
|
||||||
operation: "repository_rollout_config",
|
|
||||||
mode: config.mode,
|
|
||||||
enabledDomains: config.enabledDomains,
|
|
||||||
explicit: config.explicit,
|
|
||||||
envKey: REPOSITORY_ROLLOUT_ENV,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const warning of config.warnings) {
|
|
||||||
databaseLogger.warn(warning, {
|
|
||||||
operation: "repository_rollout_warning",
|
|
||||||
mode: config.mode,
|
|
||||||
enabledDomains: config.enabledDomains,
|
|
||||||
explicit: config.explicit,
|
|
||||||
envKey: REPOSITORY_ROLLOUT_ENV,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { RoleRepository } from "./role-repository.js";
|
import { RoleRepository } from "./role-repository.js";
|
||||||
|
|
||||||
describe("RoleRepository", () => {
|
describe("RoleRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("RoleRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<RoleRepository> {
|
): Promise<RoleRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
import { hostAccess, roles, userRoles } from "../db/schema.js";
|
import { hostAccess, roles, userRoles } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type RoleRecord = typeof roles.$inferSelect;
|
export type RoleRecord = typeof roles.$inferSelect;
|
||||||
export type NewRoleRecord = typeof roles.$inferInsert;
|
export type NewRoleRecord = typeof roles.$inferInsert;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { SessionRecordingRepository } from "./session-recording-repository.js";
|
import { SessionRecordingRepository } from "./session-recording-repository.js";
|
||||||
|
|
||||||
describe("SessionRecordingRepository", () => {
|
describe("SessionRecordingRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -15,11 +15,7 @@ describe("SessionRecordingRepository", () => {
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
onWrite?: () => void | Promise<void>,
|
onWrite?: () => void | Promise<void>,
|
||||||
): Promise<SessionRecordingRepository> {
|
): Promise<SessionRecordingRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, desc, eq, inArray, lt } from "drizzle-orm";
|
import { and, desc, eq, inArray, lt } from "drizzle-orm";
|
||||||
import { hosts, sessionRecordings } from "../db/schema.js";
|
import { hosts, sessionRecordings } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect;
|
export type SessionRecordingRecord = typeof sessionRecordings.$inferSelect;
|
||||||
|
|
||||||
@@ -74,9 +74,7 @@ export class SessionRecordingRepository {
|
|||||||
return rows[0] ?? null;
|
return rows[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findPathById(
|
async findPathById(id: number): Promise<
|
||||||
id: number,
|
|
||||||
): Promise<
|
|
||||||
| (SessionRecordingPathRecord & {
|
| (SessionRecordingPathRecord & {
|
||||||
userId: string;
|
userId: string;
|
||||||
format?: string | null;
|
format?: string | null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { and, eq, lte, ne } from "drizzle-orm";
|
import { and, eq, lte, ne } from "drizzle-orm";
|
||||||
import { sessions } from "../db/schema.js";
|
import { sessions } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export type SessionRecord = typeof sessions.$inferSelect;
|
export type SessionRecord = typeof sessions.$inferSelect;
|
||||||
export type NewSessionRecord = typeof sessions.$inferInsert;
|
export type NewSessionRecord = typeof sessions.$inferInsert;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { SqliteDatabaseAdapter } from "../runtime/sqlite-adapter.js";
|
import { TestSqliteDatabase } from "./test-support.js";
|
||||||
import { SettingsRepository } from "./settings-repository.js";
|
import { SettingsRepository } from "./settings-repository.js";
|
||||||
|
|
||||||
describe("SettingsRepository", () => {
|
describe("SettingsRepository", () => {
|
||||||
let adapter: SqliteDatabaseAdapter | null = null;
|
let adapter: TestSqliteDatabase | null = null;
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -13,11 +13,7 @@ describe("SettingsRepository", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function createRepository(): Promise<SettingsRepository> {
|
async function createRepository(): Promise<SettingsRepository> {
|
||||||
adapter = new SqliteDatabaseAdapter({
|
adapter = new TestSqliteDatabase();
|
||||||
dialect: "sqlite",
|
|
||||||
url: ":memory:",
|
|
||||||
sqlitePath: ":memory:",
|
|
||||||
});
|
|
||||||
const context = await adapter.connect();
|
const context = await adapter.connect();
|
||||||
context.sqlite?.exec(`
|
context.sqlite?.exec(`
|
||||||
CREATE TABLE settings (
|
CREATE TABLE settings (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { eq, like } from "drizzle-orm";
|
import { eq, like } from "drizzle-orm";
|
||||||
import { settings } from "../db/schema.js";
|
import { settings } from "../db/schema.js";
|
||||||
import type { DatabaseContext } from "../runtime/adapter.js";
|
import type { DatabaseContext } from "./database-context.js";
|
||||||
|
|
||||||
export class SettingsRepository {
|
export class SettingsRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user