197 lines
7.2 KiB
TypeScript
197 lines
7.2 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import {
|
|
Message,
|
|
User,
|
|
Room,
|
|
Reaction,
|
|
BanEntry
|
|
} from '../models/index';
|
|
|
|
/** CQRS API exposed by the Electron preload script via `contextBridge`. */
|
|
interface ElectronAPI {
|
|
command<T = unknown>(command: { type: string; payload: unknown }): Promise<T>;
|
|
query<T = unknown>(query: { type: string; payload: unknown }): Promise<T>;
|
|
}
|
|
|
|
/**
|
|
* Database service for the Electron (desktop) runtime.
|
|
*
|
|
* The SQLite database is managed by TypeORM in the Electron **main process**
|
|
* (`electron/main.ts`). This service is a thin CQRS IPC client that dispatches
|
|
* structured command/query objects through the unified `cqrs:command` and
|
|
* `cqrs:query` channels exposed by the preload script.
|
|
*
|
|
* No initialisation IPC call is needed - the database is initialised and
|
|
* migrations are run in main.ts before the renderer window is created.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class ElectronDatabaseService {
|
|
/** Shorthand accessor for the preload-exposed CQRS API. */
|
|
private get api(): ElectronAPI {
|
|
// eslint-disable-next-line
|
|
return (window as any).electronAPI as ElectronAPI;
|
|
}
|
|
|
|
/**
|
|
* No-op: the database is initialised in the main process before the
|
|
* renderer window opens and requires no explicit bootstrap call here.
|
|
*/
|
|
async initialize(): Promise<void> { /* no-op */ }
|
|
|
|
/** Persist a single chat message. */
|
|
saveMessage(message: Message): Promise<void> {
|
|
return this.api.command({ type: 'save-message', payload: { message } });
|
|
}
|
|
|
|
/**
|
|
* Retrieve messages for a room, sorted oldest-first.
|
|
*
|
|
* @param roomId - Target room.
|
|
* @param limit - Maximum number of messages to return.
|
|
* @param offset - Number of messages to skip (for pagination).
|
|
*/
|
|
getMessages(roomId: string, limit = 100, offset = 0): Promise<Message[]> {
|
|
return this.api.query<Message[]>({ type: 'get-messages', payload: { roomId, limit, offset } });
|
|
}
|
|
|
|
/** Permanently delete a message by ID. */
|
|
deleteMessage(messageId: string): Promise<void> {
|
|
return this.api.command({ type: 'delete-message', payload: { messageId } });
|
|
}
|
|
|
|
/** Apply partial updates to an existing message. */
|
|
updateMessage(messageId: string, updates: Partial<Message>): Promise<void> {
|
|
return this.api.command({ type: 'update-message', payload: { messageId, updates } });
|
|
}
|
|
|
|
/** Retrieve a single message by ID, or `null` if not found. */
|
|
getMessageById(messageId: string): Promise<Message | null> {
|
|
return this.api.query<Message | null>({ type: 'get-message-by-id', payload: { messageId } });
|
|
}
|
|
|
|
/** Remove every message belonging to a room. */
|
|
clearRoomMessages(roomId: string): Promise<void> {
|
|
return this.api.command({ type: 'clear-room-messages', payload: { roomId } });
|
|
}
|
|
|
|
/** Persist a reaction (deduplication is handled main-process side). */
|
|
saveReaction(reaction: Reaction): Promise<void> {
|
|
return this.api.command({ type: 'save-reaction', payload: { reaction } });
|
|
}
|
|
|
|
/** Remove a specific reaction (user + emoji + message). */
|
|
removeReaction(messageId: string, userId: string, emoji: string): Promise<void> {
|
|
return this.api.command({ type: 'remove-reaction', payload: { messageId, userId, emoji } });
|
|
}
|
|
|
|
/** Return all reactions for a given message. */
|
|
getReactionsForMessage(messageId: string): Promise<Reaction[]> {
|
|
return this.api.query<Reaction[]>({ type: 'get-reactions-for-message', payload: { messageId } });
|
|
}
|
|
|
|
/** Persist a user record. */
|
|
saveUser(user: User): Promise<void> {
|
|
return this.api.command({ type: 'save-user', payload: { user } });
|
|
}
|
|
|
|
/** Retrieve a user by ID, or `null` if not found. */
|
|
getUser(userId: string): Promise<User | null> {
|
|
return this.api.query<User | null>({ type: 'get-user', payload: { userId } });
|
|
}
|
|
|
|
/** Retrieve the last-authenticated ("current") user, or `null`. */
|
|
getCurrentUser(): Promise<User | null> {
|
|
return this.api.query<User | null>({ type: 'get-current-user', payload: {} });
|
|
}
|
|
|
|
/** Store which user ID is considered "current" (logged-in). */
|
|
setCurrentUserId(userId: string): Promise<void> {
|
|
return this.api.command({ type: 'set-current-user-id', payload: { userId } });
|
|
}
|
|
|
|
/** Retrieve users associated with a room. */
|
|
getUsersByRoom(roomId: string): Promise<User[]> {
|
|
return this.api.query<User[]>({ type: 'get-users-by-room', payload: { roomId } });
|
|
}
|
|
|
|
/** Apply partial updates to an existing user. */
|
|
updateUser(userId: string, updates: Partial<User>): Promise<void> {
|
|
return this.api.command({ type: 'update-user', payload: { userId, updates } });
|
|
}
|
|
|
|
/** Persist a room record. */
|
|
saveRoom(room: Room): Promise<void> {
|
|
return this.api.command({ type: 'save-room', payload: { room } });
|
|
}
|
|
|
|
/** Retrieve a room by ID, or `null` if not found. */
|
|
getRoom(roomId: string): Promise<Room | null> {
|
|
return this.api.query<Room | null>({ type: 'get-room', payload: { roomId } });
|
|
}
|
|
|
|
/** Return every persisted room. */
|
|
getAllRooms(): Promise<Room[]> {
|
|
return this.api.query<Room[]>({ type: 'get-all-rooms', payload: {} });
|
|
}
|
|
|
|
/** Delete a room by ID (also removes its messages). */
|
|
deleteRoom(roomId: string): Promise<void> {
|
|
return this.api.command({ type: 'delete-room', payload: { roomId } });
|
|
}
|
|
|
|
/** Apply partial updates to an existing room. */
|
|
updateRoom(roomId: string, updates: Partial<Room>): Promise<void> {
|
|
return this.api.command({ type: 'update-room', payload: { roomId, updates } });
|
|
}
|
|
|
|
/** Persist a ban entry. */
|
|
saveBan(ban: BanEntry): Promise<void> {
|
|
return this.api.command({ type: 'save-ban', payload: { ban } });
|
|
}
|
|
|
|
/** Remove a ban by the banned user's `oderId`. */
|
|
removeBan(oderId: string): Promise<void> {
|
|
return this.api.command({ type: 'remove-ban', payload: { oderId } });
|
|
}
|
|
|
|
/** Return active bans for a room. */
|
|
getBansForRoom(roomId: string): Promise<BanEntry[]> {
|
|
return this.api.query<BanEntry[]>({ type: 'get-bans-for-room', payload: { roomId } });
|
|
}
|
|
|
|
/** Check whether a user is currently banned from a room. */
|
|
isUserBanned(userId: string, roomId: string): Promise<boolean> {
|
|
return this.api.query<boolean>({ type: 'is-user-banned', payload: { userId, roomId } });
|
|
}
|
|
|
|
/** Persist attachment metadata. */
|
|
// eslint-disable-next-line
|
|
saveAttachment(attachment: any): Promise<void> {
|
|
return this.api.command({ type: 'save-attachment', payload: { attachment } });
|
|
}
|
|
|
|
/** Return all attachment records for a message. */
|
|
// eslint-disable-next-line
|
|
getAttachmentsForMessage(messageId: string): Promise<any[]> {
|
|
// eslint-disable-next-line
|
|
return this.api.query<any[]>({ type: 'get-attachments-for-message', payload: { messageId } });
|
|
}
|
|
|
|
/** Return every persisted attachment record. */
|
|
// eslint-disable-next-line
|
|
getAllAttachments(): Promise<any[]> {
|
|
// eslint-disable-next-line
|
|
return this.api.query<any[]>({ type: 'get-all-attachments', payload: {} });
|
|
}
|
|
|
|
/** Delete all attachment records for a message. */
|
|
deleteAttachmentsForMessage(messageId: string): Promise<void> {
|
|
return this.api.command({ type: 'delete-attachments-for-message', payload: { messageId } });
|
|
}
|
|
|
|
/** Wipe every table, removing all persisted data. */
|
|
clearAllData(): Promise<void> {
|
|
return this.api.command({ type: 'clear-all-data', payload: {} });
|
|
}
|
|
}
|