detached 22cc76c

refactor: Centralize default chat room configuration and improve modularity

- Move default chat room logic to a dedicated utility module
- Use environment variable for default chat room ID
- Remove default chat room creation from schema module
- Update imports and references across components and routes
- Enhance logging and error handling for chat room initialization
commit
22cc76c81db2b349f185cc7ea1778d307d82a59f
author
Kit · · 2025-02-15 09:29 UTC
parent
2960188
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
7 files · +82 -71
M src/hooks.server.ts +2 -0
@@ -7,6 +7,7 @@ import { handleRateLimit } from "$lib/api/rate-limiter";
77 import { users } from "$lib/db/schema";
88 import { initializeMessageCache } from "$lib/cache/initialize-cache";
99 import { createLogger } from "$lib/utils/logger.server";
10+import { ensureDefaultChatRoom } from "$lib/utils/chat.server";
1011
1112 const log = createLogger('hooks-server');
1213
@@ -27,6 +28,7 @@ async function setAllUsersOffline() {
2728 log.debug("Initializing database on server start...");
2829 initializeDatabase()
2930 .then(async () => {
31+ await ensureDefaultChatRoom(db);
3032 await setAllUsersOffline();
3133 await initializeMessageCache();
3234 })
@@ -7,6 +7,7 @@ import { handleRateLimit } from "$lib/api/rate-limiter";
7 import { users } from "$lib/db/schema";7 import { users } from "$lib/db/schema";
8 import { initializeMessageCache } from "$lib/cache/initialize-cache";8 import { initializeMessageCache } from "$lib/cache/initialize-cache";
9 import { createLogger } from "$lib/utils/logger.server";9 import { createLogger } from "$lib/utils/logger.server";
10+import { ensureDefaultChatRoom } from "$lib/utils/chat.server";
10 11
11 const log = createLogger('hooks-server');12 const log = createLogger('hooks-server');
12 13
@@ -27,6 +28,7 @@ async function setAllUsersOffline() {
27 log.debug("Initializing database on server start...");28 log.debug("Initializing database on server start...");
28 initializeDatabase()29 initializeDatabase()
29 .then(async () => {30 .then(async () => {
31+ await ensureDefaultChatRoom(db);
30 await setAllUsersOffline();32 await setAllUsersOffline();
31 await initializeMessageCache();33 await initializeMessageCache();
32 })34 })
M src/lib/cache/message-cache.ts +11 -8
@@ -1,5 +1,8 @@
11 import type { Message } from '$lib/types/chat';
2-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
2+import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('message-cache');
36
47 interface RoomCache {
58 messages: Message[];
@@ -18,7 +21,7 @@ class MessageCache {
1821 this.maxMessagesPerRoom = 100; // Keep last 100 messages per room
1922 this.maxRooms = 100; // Maximum number of rooms to cache
2023 this.cacheExpiryMs = 30 * 60 * 1000; // 30 minutes cache expiry
21- console.debug('[MessageCache] Initialized with settings:', {
24+ log.debug('[MessageCache] Initialized with settings:', {
2225 maxMessagesPerRoom: this.maxMessagesPerRoom,
2326 maxRooms: this.maxRooms,
2427 cacheExpiryMs: this.cacheExpiryMs
@@ -44,7 +47,7 @@ class MessageCache {
4447 }
4548
4649 if (expiredCount > 0) {
47- console.debug('[MessageCache] Cleaned expired rooms:', { expiredCount });
50+ log.debug('[MessageCache] Cleaned expired rooms:', { expiredCount });
4851 }
4952 }
5053
@@ -59,7 +62,7 @@ class MessageCache {
5962 this.rooms.delete(roomId);
6063 }
6164
62- console.debug('[MessageCache] Removed LRU rooms:', {
65+ log.debug('[MessageCache] Removed LRU rooms:', {
6366 removedCount: toRemove.length,
6467 remainingRooms: this.rooms.size
6568 });
@@ -67,7 +70,7 @@ class MessageCache {
6770 }
6871
6972 public async initialize(messages: Message[]): Promise<void> {
70- console.debug('[MessageCache] Initializing with messages:', messages.length);
73+ log.debug('[MessageCache] Initializing with messages:', { messageCount: messages.length });
7174 // Group messages by chat room
7275 const messagesByRoom = new Map<string, Message[]>();
7376
@@ -89,7 +92,7 @@ class MessageCache {
8992 }
9093
9194 this.ensureRoomCapacity();
92- console.debug('[MessageCache] Initialization complete:', {
95+ log.debug('[MessageCache] Initialization complete:', {
9396 roomCount: this.rooms.size,
9497 totalMessages: messages.length
9598 });
@@ -130,7 +133,7 @@ class MessageCache {
130133 this.rooms.set(message.chatRoomId, roomCache);
131134 this.ensureRoomCapacity();
132135
133- console.debug('[MessageCache] Added message to cache:', {
136+ log.debug('[MessageCache] Added message to cache:', {
134137 messageId: message.id,
135138 roomId: message.chatRoomId,
136139 roomMessageCount: roomCache.messages.length,
@@ -144,7 +147,7 @@ class MessageCache {
144147
145148 public clear(): void {
146149 this.rooms.clear();
147- console.debug('[MessageCache] Cache cleared');
150+ log.debug('[MessageCache] Cache cleared');
148151 }
149152
150153 public getCacheStats(): {
@@ -1,5 +1,8 @@
1 import type { Message } from '$lib/types/chat';1 import type { Message } from '$lib/types/chat';
2-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';2+import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('message-cache');
3 6
4 interface RoomCache {7 interface RoomCache {
5 messages: Message[];8 messages: Message[];
@@ -18,7 +21,7 @@ class MessageCache {
18 this.maxMessagesPerRoom = 100; // Keep last 100 messages per room21 this.maxMessagesPerRoom = 100; // Keep last 100 messages per room
19 this.maxRooms = 100; // Maximum number of rooms to cache22 this.maxRooms = 100; // Maximum number of rooms to cache
20 this.cacheExpiryMs = 30 * 60 * 1000; // 30 minutes cache expiry23 this.cacheExpiryMs = 30 * 60 * 1000; // 30 minutes cache expiry
21- console.debug('[MessageCache] Initialized with settings:', {24+ log.debug('[MessageCache] Initialized with settings:', {
22 maxMessagesPerRoom: this.maxMessagesPerRoom,25 maxMessagesPerRoom: this.maxMessagesPerRoom,
23 maxRooms: this.maxRooms,26 maxRooms: this.maxRooms,
24 cacheExpiryMs: this.cacheExpiryMs27 cacheExpiryMs: this.cacheExpiryMs
@@ -44,7 +47,7 @@ class MessageCache {
44 }47 }
45 48
46 if (expiredCount > 0) {49 if (expiredCount > 0) {
47- console.debug('[MessageCache] Cleaned expired rooms:', { expiredCount });50+ log.debug('[MessageCache] Cleaned expired rooms:', { expiredCount });
48 }51 }
49 }52 }
50 53
@@ -59,7 +62,7 @@ class MessageCache {
59 this.rooms.delete(roomId);62 this.rooms.delete(roomId);
60 }63 }
61 64
62- console.debug('[MessageCache] Removed LRU rooms:', { 65+ log.debug('[MessageCache] Removed LRU rooms:', {
63 removedCount: toRemove.length,66 removedCount: toRemove.length,
64 remainingRooms: this.rooms.size 67 remainingRooms: this.rooms.size
65 });68 });
@@ -67,7 +70,7 @@ class MessageCache {
67 }70 }
68 71
69 public async initialize(messages: Message[]): Promise<void> {72 public async initialize(messages: Message[]): Promise<void> {
70- console.debug('[MessageCache] Initializing with messages:', messages.length);73+ log.debug('[MessageCache] Initializing with messages:', { messageCount: messages.length });
71 // Group messages by chat room74 // Group messages by chat room
72 const messagesByRoom = new Map<string, Message[]>();75 const messagesByRoom = new Map<string, Message[]>();
73 76
@@ -89,7 +92,7 @@ class MessageCache {
89 }92 }
90 93
91 this.ensureRoomCapacity();94 this.ensureRoomCapacity();
92- console.debug('[MessageCache] Initialization complete:', { 95+ log.debug('[MessageCache] Initialization complete:', {
93 roomCount: this.rooms.size,96 roomCount: this.rooms.size,
94 totalMessages: messages.length97 totalMessages: messages.length
95 });98 });
@@ -130,7 +133,7 @@ class MessageCache {
130 this.rooms.set(message.chatRoomId, roomCache);133 this.rooms.set(message.chatRoomId, roomCache);
131 this.ensureRoomCapacity();134 this.ensureRoomCapacity();
132 135
133- console.debug('[MessageCache] Added message to cache:', { 136+ log.debug('[MessageCache] Added message to cache:', {
134 messageId: message.id,137 messageId: message.id,
135 roomId: message.chatRoomId,138 roomId: message.chatRoomId,
136 roomMessageCount: roomCache.messages.length,139 roomMessageCount: roomCache.messages.length,
@@ -144,7 +147,7 @@ class MessageCache {
144 147
145 public clear(): void {148 public clear(): void {
146 this.rooms.clear();149 this.rooms.clear();
147- console.debug('[MessageCache] Cache cleared');150+ log.debug('[MessageCache] Cache cleared');
148 }151 }
149 152
150 public getCacheStats(): {153 public getCacheStats(): {
M src/lib/components/chat-room.svelte +5 -3
@@ -8,7 +8,6 @@ import { resizable } from '$lib/actions/resizable';
88 import { maximizable } from '$lib/actions/maximizable';
99 import LoadingButton from './ui/button-loading.svelte';
1010 import Tooltip from './ui/tooltip.svelte';
11-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
1211 import { formatFrenchDateTime, formatFrenchRelativeTimeSafe } from '$lib/utils/date-format';
1312
1413 // Initialize with default values for SSR
@@ -65,7 +64,7 @@ let currentUser = $state<User | null>(null);
6564 let isLoadingMore = $state(false);
6665 let hasMoreMessages = $state(true);
6766 let oldestMessageTimestamp = $state<number | null>(null);
68-let currentRoomId = $state(DEFAULT_CHAT_ROOM_ID);
67+let currentRoomId = $state('');
6968
7069 // Compute visible messages based on login status
7170 let visibleMessages = $derived((() => {
@@ -131,9 +130,12 @@ $effect(() => {
131130 fetchedPublicRoom = true;
132131 fetchedPublicMessages = true;
133132
133+ // Get the default chat room id
134+ const defaultRoomId = chatState.getDefaultChatRoomId();
135+
134136 // Fetch both in parallel
135137 Promise.all([
136- fetch(`/api/rooms/${DEFAULT_CHAT_ROOM_ID}?public=true`).then(r => r.json()),
138+ fetch(`/api/rooms/${defaultRoomId}?public=true`).then(r => r.json()),
137139 fetch('/api/chat/messages?public=true').then(r => r.json())
138140 ])
139141 .then(([roomData, messagesData]) => {
@@ -8,7 +8,6 @@ import { resizable } from '$lib/actions/resizable';
8 import { maximizable } from '$lib/actions/maximizable';8 import { maximizable } from '$lib/actions/maximizable';
9 import LoadingButton from './ui/button-loading.svelte';9 import LoadingButton from './ui/button-loading.svelte';
10 import Tooltip from './ui/tooltip.svelte';10 import Tooltip from './ui/tooltip.svelte';
11-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
12 import { formatFrenchDateTime, formatFrenchRelativeTimeSafe } from '$lib/utils/date-format';11 import { formatFrenchDateTime, formatFrenchRelativeTimeSafe } from '$lib/utils/date-format';
13 12
14 // Initialize with default values for SSR13 // Initialize with default values for SSR
@@ -65,7 +64,7 @@ let currentUser = $state<User | null>(null);
65 let isLoadingMore = $state(false);64 let isLoadingMore = $state(false);
66 let hasMoreMessages = $state(true);65 let hasMoreMessages = $state(true);
67 let oldestMessageTimestamp = $state<number | null>(null);66 let oldestMessageTimestamp = $state<number | null>(null);
68-let currentRoomId = $state(DEFAULT_CHAT_ROOM_ID);67+let currentRoomId = $state('');
69 68
70 // Compute visible messages based on login status69 // Compute visible messages based on login status
71 let visibleMessages = $derived((() => {70 let visibleMessages = $derived((() => {
@@ -131,9 +130,12 @@ $effect(() => {
131 fetchedPublicRoom = true;130 fetchedPublicRoom = true;
132 fetchedPublicMessages = true;131 fetchedPublicMessages = true;
133 132
133+ // Get the default chat room id
134+ const defaultRoomId = chatState.getDefaultChatRoomId();
135+
134 // Fetch both in parallel136 // Fetch both in parallel
135 Promise.all([137 Promise.all([
136- fetch(`/api/rooms/${DEFAULT_CHAT_ROOM_ID}?public=true`).then(r => r.json()),138+ fetch(`/api/rooms/${defaultRoomId}?public=true`).then(r => r.json()),
137 fetch('/api/chat/messages?public=true').then(r => r.json())139 fetch('/api/chat/messages?public=true').then(r => r.json())
138 ])140 ])
139 .then(([roomData, messagesData]) => {141 .then(([roomData, messagesData]) => {
M src/lib/db/schema.ts +0 -50
@@ -3,23 +3,7 @@ import * as t from "drizzle-orm/sqlite-core";
33 import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "../types/chat";
44 import { v4 as uuidv4 } from "uuid";
55 import { eq } from "drizzle-orm";
6-import type { LibSQLDatabase } from "drizzle-orm/libsql";
7-import { createLogger } from "$lib/utils/logger.server";
86
9-const log = createLogger('schema');
10-
11-// Default chat room ID - using a UUID to avoid collisions
12-export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
13-
14-// Type guard for LibSQL errors
15-interface LibSQLError {
16- code: string;
17- message: string;
18-}
19-
20-function isLibSQLError(error: unknown): error is LibSQLError {
21- return typeof error === 'object' && error !== null && 'code' in error;
22-}
237
248 export const users = table(
259 "users",
@@ -121,40 +105,6 @@ const schema = {
121105 messageView
122106 };
123107
124-// Function to ensure default chat room exists
125-export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
126- log.debug('Ensuring default chat room exists...');
127- try {
128- // Use a transaction to handle concurrent creation attempts
129- await db.transaction(async (tx) => {
130- const defaultRoom = await tx.select()
131- .from(chatRooms)
132- .where(eq(chatRooms.id, DEFAULT_CHAT_ROOM_ID))
133- .get();
134-
135- if (!defaultRoom) {
136- log.debug('Creating default chat room...');
137- await tx.insert(chatRooms).values({
138- id: DEFAULT_CHAT_ROOM_ID,
139- name: 'General',
140- type: 'group',
141- createdAt: Date.now()
142- });
143- log.debug('Default chat room created successfully');
144- } else {
145- log.debug('Default chat room already exists');
146- }
147- });
148- } catch (error: unknown) {
149- // If the error is a unique constraint violation, another process probably created the room
150- if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
151- log.debug('Default chat room was created by another process');
152- return;
153- }
154- log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
155- throw error;
156- }
157-}
158108
159109 export default schema;
160110
@@ -3,23 +3,7 @@ import * as t from "drizzle-orm/sqlite-core";
3 import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "../types/chat";3 import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "../types/chat";
4 import { v4 as uuidv4 } from "uuid";4 import { v4 as uuidv4 } from "uuid";
5 import { eq } from "drizzle-orm";5 import { eq } from "drizzle-orm";
6-import type { LibSQLDatabase } from "drizzle-orm/libsql";
7-import { createLogger } from "$lib/utils/logger.server";
8 6
9-const log = createLogger('schema');
10-
11-// Default chat room ID - using a UUID to avoid collisions
12-export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
13-
14-// Type guard for LibSQL errors
15-interface LibSQLError {
16- code: string;
17- message: string;
18-}
19-
20-function isLibSQLError(error: unknown): error is LibSQLError {
21- return typeof error === 'object' && error !== null && 'code' in error;
22-}
23 7
24 export const users = table(8 export const users = table(
25 "users",9 "users",
@@ -121,40 +105,6 @@ const schema = {
121 messageView105 messageView
122 };106 };
123 107
124-// Function to ensure default chat room exists
125-export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
126- log.debug('Ensuring default chat room exists...');
127- try {
128- // Use a transaction to handle concurrent creation attempts
129- await db.transaction(async (tx) => {
130- const defaultRoom = await tx.select()
131- .from(chatRooms)
132- .where(eq(chatRooms.id, DEFAULT_CHAT_ROOM_ID))
133- .get();
134-
135- if (!defaultRoom) {
136- log.debug('Creating default chat room...');
137- await tx.insert(chatRooms).values({
138- id: DEFAULT_CHAT_ROOM_ID,
139- name: 'General',
140- type: 'group',
141- createdAt: Date.now()
142- });
143- log.debug('Default chat room created successfully');
144- } else {
145- log.debug('Default chat room already exists');
146- }
147- });
148- } catch (error: unknown) {
149- // If the error is a unique constraint violation, another process probably created the room
150- if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
151- log.debug('Default chat room was created by another process');
152- return;
153- }
154- log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
155- throw error;
156- }
157-}
158 108
159 export default schema;109 export default schema;
160 110
M src/lib/states/chat.svelte.ts +7 -2
@@ -5,8 +5,9 @@ import type {
55 GetMessagesResponse
66 } from '../types/payloads';
77 import { invalidate } from '$app/navigation';
8-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
8+import { env } from '$env/dynamic/public';
99
10+const DEFAULT_CHAT_ROOM_ID = env.PUBLIC_DEFAULT_CHAT_ROOM_ID;
1011
1112 class ChatState {
1213 private users = $state<User[]>([]);
@@ -17,7 +18,7 @@ class ChatState {
1718 private maxReconnectAttempts = 5;
1819 private reconnectDelay = 1000; // Start with 1 second
1920 private isInitializing = $state(false);
20- private currentRoomId = $state<string>(DEFAULT_CHAT_ROOM_ID);
21+ private currentRoomId = $state<string>();
2122 private userCache = $state<Record<string, User>>({});
2223 private sseError = $state<string | null>(null);
2324 private sseRetryAfter = $state<number | null>(null);
@@ -668,6 +669,10 @@ class ChatState {
668669 }
669670 }));
670671 }
672+
673+ getDefaultChatRoomId() {
674+ return DEFAULT_CHAT_ROOM_ID;
675+ }
671676 }
672677
673678 export const chatState = new ChatState();⏎̸
@@ -5,8 +5,9 @@ import type {
5 GetMessagesResponse 5 GetMessagesResponse
6 } from '../types/payloads';6 } from '../types/payloads';
7 import { invalidate } from '$app/navigation';7 import { invalidate } from '$app/navigation';
8-import { DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';8+import { env } from '$env/dynamic/public';
9 9
10+const DEFAULT_CHAT_ROOM_ID = env.PUBLIC_DEFAULT_CHAT_ROOM_ID;
10 11
11 class ChatState {12 class ChatState {
12 private users = $state<User[]>([]);13 private users = $state<User[]>([]);
@@ -17,7 +18,7 @@ class ChatState {
17 private maxReconnectAttempts = 5;18 private maxReconnectAttempts = 5;
18 private reconnectDelay = 1000; // Start with 1 second19 private reconnectDelay = 1000; // Start with 1 second
19 private isInitializing = $state(false);20 private isInitializing = $state(false);
20- private currentRoomId = $state<string>(DEFAULT_CHAT_ROOM_ID);21+ private currentRoomId = $state<string>();
21 private userCache = $state<Record<string, User>>({});22 private userCache = $state<Record<string, User>>({});
22 private sseError = $state<string | null>(null);23 private sseError = $state<string | null>(null);
23 private sseRetryAfter = $state<number | null>(null);24 private sseRetryAfter = $state<number | null>(null);
@@ -668,6 +669,10 @@ class ChatState {
668 }669 }
669 }));670 }));
670 }671 }
672+
673+ getDefaultChatRoomId() {
674+ return DEFAULT_CHAT_ROOM_ID;
675+ }
671 }676 }
672 677
673 export const chatState = new ChatState();⏎̸678 export const chatState = new ChatState();⏎̸
A src/lib/utils/chat.server.ts +55 -0
@@ -0,0 +1,55 @@
1+import type { LibSQLDatabase } from "drizzle-orm/libsql";
2+import { createLogger } from "$lib/utils/logger.server";
3+import schema, { chatRooms } from "$lib/db/schema";
4+import { eq } from "drizzle-orm";
5+import { env } from "$env/dynamic/public";
6+
7+const log = createLogger('chat-utils');
8+
9+// Default chat room ID from environment variable
10+export const DEFAULT_CHAT_ROOM_ID = env.PUBLIC_DEFAULT_CHAT_ROOM_ID || '00000000-0000-0000-0000-000000000001';
11+
12+// Type guard for LibSQL errors
13+interface LibSQLError {
14+ code: string;
15+ message: string;
16+}
17+
18+function isLibSQLError(error: unknown): error is LibSQLError {
19+ return typeof error === 'object' && error !== null && 'code' in error;
20+}
21+
22+// Function to ensure default chat room exists
23+export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
24+ log.debug('Ensuring default chat room exists...');
25+ try {
26+ // Use a transaction to handle concurrent creation attempts
27+ await db.transaction(async (tx) => {
28+ const defaultRoom = await tx.select()
29+ .from(chatRooms)
30+ .where(eq(chatRooms.id, DEFAULT_CHAT_ROOM_ID))
31+ .get();
32+
33+ if (!defaultRoom) {
34+ log.debug('Creating default chat room...');
35+ await tx.insert(chatRooms).values({
36+ id: DEFAULT_CHAT_ROOM_ID,
37+ name: 'General',
38+ type: 'group',
39+ createdAt: Date.now()
40+ });
41+ log.debug('Default chat room created successfully');
42+ } else {
43+ log.debug('Default chat room already exists');
44+ }
45+ });
46+ } catch (error: unknown) {
47+ // If the error is a unique constraint violation, another process probably created the room
48+ if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
49+ log.debug('Default chat room was created by another process');
50+ return;
51+ }
52+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
53+ throw error;
54+ }
55+} ⏎̸
@@ -0,0 +1,55 @@
1+import type { LibSQLDatabase } from "drizzle-orm/libsql";
2+import { createLogger } from "$lib/utils/logger.server";
3+import schema, { chatRooms } from "$lib/db/schema";
4+import { eq } from "drizzle-orm";
5+import { env } from "$env/dynamic/public";
6+
7+const log = createLogger('chat-utils');
8+
9+// Default chat room ID from environment variable
10+export const DEFAULT_CHAT_ROOM_ID = env.PUBLIC_DEFAULT_CHAT_ROOM_ID || '00000000-0000-0000-0000-000000000001';
11+
12+// Type guard for LibSQL errors
13+interface LibSQLError {
14+ code: string;
15+ message: string;
16+}
17+
18+function isLibSQLError(error: unknown): error is LibSQLError {
19+ return typeof error === 'object' && error !== null && 'code' in error;
20+}
21+
22+// Function to ensure default chat room exists
23+export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
24+ log.debug('Ensuring default chat room exists...');
25+ try {
26+ // Use a transaction to handle concurrent creation attempts
27+ await db.transaction(async (tx) => {
28+ const defaultRoom = await tx.select()
29+ .from(chatRooms)
30+ .where(eq(chatRooms.id, DEFAULT_CHAT_ROOM_ID))
31+ .get();
32+
33+ if (!defaultRoom) {
34+ log.debug('Creating default chat room...');
35+ await tx.insert(chatRooms).values({
36+ id: DEFAULT_CHAT_ROOM_ID,
37+ name: 'General',
38+ type: 'group',
39+ createdAt: Date.now()
40+ });
41+ log.debug('Default chat room created successfully');
42+ } else {
43+ log.debug('Default chat room already exists');
44+ }
45+ });
46+ } catch (error: unknown) {
47+ // If the error is a unique constraint violation, another process probably created the room
48+ if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
49+ log.debug('Default chat room was created by another process');
50+ return;
51+ }
52+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
53+ throw error;
54+ }
55+} ⏎̸
M src/routes/api/chat/messages/+server.ts +2 -8
@@ -11,9 +11,9 @@ import { desc } from 'drizzle-orm';
1111 import { error } from '@sveltejs/kit';
1212 import { sseEmitter } from '$lib/sseEmitter';
1313 import { eq, lt, and } from 'drizzle-orm';
14-import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
14+import { chatRooms } from '$lib/db/schema';
1515 import { users } from '$lib/db/schema';
16-import { ensureDefaultChatRoom } from '$lib/db/schema';
16+import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
1717 import { messageCache } from '$lib/cache/message-cache';
1818 import { createLogger } from '$lib/utils/logger.server';
1919
@@ -87,9 +87,6 @@ export async function GET({ request, locals }) {
8787 let fetchedMessages: Message[] = [];
8888
8989 try {
90- // Ensure default chat room exists
91- await ensureDefaultChatRoom(db);
92-
9390 // Initialize cache if needed
9491 if (!isCacheInitialized) {
9592 await initializeCache();
@@ -203,9 +200,6 @@ export async function POST({ request, locals }: { request: Request, locals: App.
203200 });
204201 }
205202
206- // Ensure default chat room exists
207- await ensureDefaultChatRoom(db);
208-
209203 const data = await request.json() as SendMessageRequest;
210204 if (!data.content || !data.userId) {
211205 log.warn('Invalid payload received', {
@@ -11,9 +11,9 @@ import { desc } from 'drizzle-orm';
11 import { error } from '@sveltejs/kit';11 import { error } from '@sveltejs/kit';
12 import { sseEmitter } from '$lib/sseEmitter';12 import { sseEmitter } from '$lib/sseEmitter';
13 import { eq, lt, and } from 'drizzle-orm';13 import { eq, lt, and } from 'drizzle-orm';
14-import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';14+import { chatRooms } from '$lib/db/schema';
15 import { users } from '$lib/db/schema';15 import { users } from '$lib/db/schema';
16-import { ensureDefaultChatRoom } from '$lib/db/schema';16+import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
17 import { messageCache } from '$lib/cache/message-cache';17 import { messageCache } from '$lib/cache/message-cache';
18 import { createLogger } from '$lib/utils/logger.server';18 import { createLogger } from '$lib/utils/logger.server';
19 19
@@ -87,9 +87,6 @@ export async function GET({ request, locals }) {
87 let fetchedMessages: Message[] = [];87 let fetchedMessages: Message[] = [];
88 88
89 try {89 try {
90- // Ensure default chat room exists
91- await ensureDefaultChatRoom(db);
92-
93 // Initialize cache if needed90 // Initialize cache if needed
94 if (!isCacheInitialized) {91 if (!isCacheInitialized) {
95 await initializeCache();92 await initializeCache();
@@ -203,9 +200,6 @@ export async function POST({ request, locals }: { request: Request, locals: App.
203 });200 });
204 }201 }
205 202
206- // Ensure default chat room exists
207- await ensureDefaultChatRoom(db);
208-
209 const data = await request.json() as SendMessageRequest;203 const data = await request.json() as SendMessageRequest;
210 if (!data.content || !data.userId) {204 if (!data.content || !data.userId) {
211 log.warn('Invalid payload received', { 205 log.warn('Invalid payload received', {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 9

↑↓ moveenter openesc close