feat: Added media_servers table, MediaServer type, and CRUD repository…
- "src/db/schema/media-servers.ts" - "src/db/repositories/media-server-repository.ts" - "src/types/index.ts" - "drizzle/0016_right_galactus.sql" GSD-Task: S04/T01
This commit is contained in:
parent
9e7d98c7c7
commit
6aa7e21b90
7 changed files with 1310 additions and 0 deletions
11
drizzle/0016_right_galactus.sql
Normal file
11
drizzle/0016_right_galactus.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE `media_servers` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`url` text NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`library_section` text,
|
||||
`enabled` integer DEFAULT true NOT NULL,
|
||||
`created_at` text DEFAULT (datetime('now')) NOT NULL,
|
||||
`updated_at` text DEFAULT (datetime('now')) NOT NULL
|
||||
);
|
||||
1117
drizzle/meta/0016_snapshot.json
Normal file
1117
drizzle/meta/0016_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -113,6 +113,13 @@
|
|||
"when": 1775280800944,
|
||||
"tag": "0015_perfect_lethal_legion",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "6",
|
||||
"when": 1775281783887,
|
||||
"tag": "0016_right_galactus",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
137
src/db/repositories/media-server-repository.ts
Normal file
137
src/db/repositories/media-server-repository.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import { type LibSQLDatabase } from 'drizzle-orm/libsql';
|
||||
import type * as schema from '../schema/index';
|
||||
import { mediaServers } from '../schema/index';
|
||||
import type { MediaServer, MediaServerType } from '../../types/index';
|
||||
|
||||
// ── Types ──
|
||||
|
||||
/** Fields needed to create a new media server. */
|
||||
export interface CreateMediaServerData {
|
||||
name: string;
|
||||
type: MediaServerType;
|
||||
url: string;
|
||||
token: string;
|
||||
librarySection?: string | null;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
/** Fields that can be updated on an existing media server. */
|
||||
export interface UpdateMediaServerData {
|
||||
name?: string;
|
||||
type?: MediaServerType;
|
||||
url?: string;
|
||||
token?: string;
|
||||
librarySection?: string | null;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
type Db = LibSQLDatabase<typeof schema>;
|
||||
|
||||
// ── Repository Functions ──
|
||||
|
||||
/** Insert a new media server. Returns the created row. */
|
||||
export async function createMediaServer(
|
||||
db: Db,
|
||||
data: CreateMediaServerData
|
||||
): Promise<MediaServer> {
|
||||
const result = await db
|
||||
.insert(mediaServers)
|
||||
.values({
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
url: data.url,
|
||||
token: data.token,
|
||||
librarySection: data.librarySection ?? null,
|
||||
enabled: data.enabled ?? true,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return mapRow(result[0]);
|
||||
}
|
||||
|
||||
/** Get all media servers, ordered by name. */
|
||||
export async function getAllMediaServers(db: Db): Promise<MediaServer[]> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(mediaServers)
|
||||
.orderBy(mediaServers.name);
|
||||
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
/** Get a media server by ID. Returns null if not found. */
|
||||
export async function getMediaServerById(
|
||||
db: Db,
|
||||
id: number
|
||||
): Promise<MediaServer | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(mediaServers)
|
||||
.where(eq(mediaServers.id, id))
|
||||
.limit(1);
|
||||
|
||||
return rows.length > 0 ? mapRow(rows[0]) : null;
|
||||
}
|
||||
|
||||
/** Get all enabled media servers. */
|
||||
export async function getEnabledMediaServers(
|
||||
db: Db
|
||||
): Promise<MediaServer[]> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(mediaServers)
|
||||
.where(eq(mediaServers.enabled, true));
|
||||
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a media server. Sets updatedAt to current time.
|
||||
* Returns updated server or null if not found.
|
||||
*/
|
||||
export async function updateMediaServer(
|
||||
db: Db,
|
||||
id: number,
|
||||
data: UpdateMediaServerData
|
||||
): Promise<MediaServer | null> {
|
||||
const result = await db
|
||||
.update(mediaServers)
|
||||
.set({
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
.where(eq(mediaServers.id, id))
|
||||
.returning();
|
||||
|
||||
return result.length > 0 ? mapRow(result[0]) : null;
|
||||
}
|
||||
|
||||
/** Delete a media server by ID. Returns true if a row was deleted. */
|
||||
export async function deleteMediaServer(
|
||||
db: Db,
|
||||
id: number
|
||||
): Promise<boolean> {
|
||||
const result = await db
|
||||
.delete(mediaServers)
|
||||
.where(eq(mediaServers.id, id))
|
||||
.returning({ id: mediaServers.id });
|
||||
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
// ── Row Mapping ──
|
||||
|
||||
function mapRow(row: typeof mediaServers.$inferSelect): MediaServer {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
type: row.type as MediaServerType,
|
||||
url: row.url,
|
||||
token: row.token,
|
||||
librarySection: row.librarySection,
|
||||
enabled: row.enabled,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
|
@ -6,3 +6,4 @@ export { downloadHistory } from './history';
|
|||
export { notificationSettings } from './notifications';
|
||||
export { platformSettings } from './platform-settings';
|
||||
export { playlists, contentPlaylist } from './playlists';
|
||||
export { mediaServers } from './media-servers';
|
||||
|
|
|
|||
19
src/db/schema/media-servers.ts
Normal file
19
src/db/schema/media-servers.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
/** Media server connections for triggering library scans (Plex, Jellyfin). */
|
||||
export const mediaServers = sqliteTable('media_servers', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
name: text('name').notNull(),
|
||||
type: text('type').notNull(), // 'plex' | 'jellyfin'
|
||||
url: text('url').notNull(),
|
||||
token: text('token').notNull(),
|
||||
librarySection: text('library_section'), // nullable — Plex section ID or Jellyfin library ID
|
||||
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(datetime('now'))`),
|
||||
updatedAt: text('updated_at')
|
||||
.notNull()
|
||||
.default(sql`(datetime('now'))`),
|
||||
});
|
||||
|
|
@ -196,6 +196,24 @@ export interface SystemConfig {
|
|||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const MediaServerType = {
|
||||
Plex: 'plex',
|
||||
Jellyfin: 'jellyfin',
|
||||
} as const;
|
||||
export type MediaServerType = (typeof MediaServerType)[keyof typeof MediaServerType];
|
||||
|
||||
export interface MediaServer {
|
||||
id: number;
|
||||
name: string;
|
||||
type: MediaServerType;
|
||||
url: string;
|
||||
token: string;
|
||||
librarySection: string | null;
|
||||
enabled: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
id: number;
|
||||
channelId: number;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue