main f6d8405

convex.server.ts RawHistoryFolder

1import { env as privateEnv } from '$env/dynamic/private';
2import { env as publicEnv } from '$env/dynamic/public';
3
4// Convert .cloud URL to .site URL for HTTP actions
5function getHttpUrl(): string {
6	const baseUrl = publicEnv.PUBLIC_CONVEX_URL ?? privateEnv.PUBLIC_CONVEX_URL;
7	if (!baseUrl) {
8		throw new Error('PUBLIC_CONVEX_URL is not set');
9	}
10	return baseUrl.replace('.convex.cloud', '.convex.site');
11}
12
13// Generic fetch helper for Convex HTTP endpoints (server-side only)
14async function convexFetch<T>(path: string, body: Record<string, unknown> = {}): Promise<T> {
15	const url = `${getHttpUrl()}${path}`;
16
17	const response = await fetch(url, {
18		method: 'POST',
19		headers: {
20			'Content-Type': 'application/json',
21			'X-Convex-Secret': privateEnv.CONVEX_API_SECRET || ''
22		},
23		body: JSON.stringify(body)
24	});
25
26	if (!response.ok) {
27		const error = await response.text();
28		throw new Error(`Convex HTTP error: ${response.status} - ${error}`);
29	}
30
31	return response.json();
32}
33
34// ============ USERS ============
35
36export const users = {
37	getByNickname: (nickname: string) =>
38		convexFetch<ConvexUser | null>('/api/users/getByNickname', { nickname }),
39
40	getById: (id: string) => convexFetch<ConvexUser | null>('/api/users/getById', { id }),
41
42	create: (nickname: string, password: string) =>
43		convexFetch<{ userId: string }>('/api/users/create', { nickname, password }),
44
45	updateStatus: (userId: string, status: string) =>
46		convexFetch<ConvexUser>('/api/users/updateStatus', { userId, status }),
47
48	list: () => convexFetch<ConvexUser[]>('/api/users/list', {}),
49
50	setAllOffline: () => convexFetch<{ updated: number }>('/api/users/setAllOffline', {}),
51
52	updateLastReadMentionTimestamp: (userId: string, timestamp: number) =>
53		convexFetch<{ success: boolean }>('/api/users/updateLastReadMentionTimestamp', {
54			userId,
55			timestamp
56		})
57};
58
59// ============ SESSIONS ============
60
61export const sessions = {
62	create: (tokenHash: string, userId: string, expiresAt: number) =>
63		convexFetch<{ id: string; tokenHash: string }>('/api/sessions/create', {
64			tokenHash,
65			userId,
66			expiresAt
67		}),
68
69	validate: (tokenHash: string) =>
70		convexFetch<{ session: ConvexSession | null; user: ConvexUser | null }>(
71			'/api/sessions/validate',
72			{ tokenHash }
73		),
74
75	remove: (tokenHash: string) =>
76		convexFetch<{ success: boolean }>('/api/sessions/remove', { tokenHash })
77};
78
79// ============ CHAT ROOMS ============
80
81export const chatRooms = {
82	getOrCreateDefault: () => convexFetch<ConvexChatRoom>('/api/chatRooms/getOrCreateDefault', {}),
83
84	getById: (id: string) => convexFetch<ConvexChatRoom | null>('/api/chatRooms/getById', { id })
85};
86
87// ============ MESSAGES ============
88
89export const messages = {
90	getByRoom: (roomId: string, limit?: number, beforeTimestamp?: number) =>
91		convexFetch<{ messages: ConvexMessage[]; hasMore: boolean }>('/api/messages/getByRoom', {
92			roomId,
93			limit,
94			beforeTimestamp
95		}),
96
97	send: (
98		chatRoomId: string,
99		senderId: string,
100		content: string,
101		type?: string,
102		styleData?: string,
103		hasFormatting?: boolean
104	) =>
105		convexFetch<ConvexMessage>('/api/messages/send', {
106			chatRoomId,
107			senderId,
108			content,
109			type,
110			styleData,
111			hasFormatting
112		})
113};
114
115// ============ TYPES ============
116
117export interface ConvexUser {
118	_id: string;
119	_creationTime: number;
120	password: string;
121	nickname: string;
122	status: string;
123	avatarUrl?: string;
124	createdAt: number;
125	lastSeen?: number;
126	lastReadMentionTimestamp?: number;
127}
128
129export interface ConvexSession {
130	_id: string;
131	_creationTime: number;
132	tokenHash: string;
133	userId: string;
134	expiresAt: number;
135	createdAt: number;
136}
137
138export interface ConvexMessage {
139	_id: string;
140	_creationTime: number;
141	chatRoomId: string;
142	senderId: string;
143	content: string;
144	type: string;
145	timestamp: number;
146	styleData?: string;
147	hasFormatting?: boolean;
148}
149
150export interface ConvexChatRoom {
151	_id: string;
152	_creationTime: number;
153	name?: string;
154	type: string;
155	createdAt: number;
156	topic?: string;
157	memberIds?: string[];
158	createdBy?: string;
159}
160
161export interface ConvexTextPreferences {
162	_id: string;
163	_creationTime: number;
164	userId: string;
165	defaultStyle: {
166		fontFamily: string;
167		fontSize: number;
168		color?: string;
169		gradient?: string[];
170		bold: boolean;
171		italic: boolean;
172		underline: boolean;
173		strikethrough: boolean;
174	};
175	allowFormatting: boolean;
176	maxMessageLength: number;
177	createdAt: number;
178	updatedAt: number;
179}
180
181// ============ TEXT PREFERENCES ============
182
183export const textPreferences = {
184	get: (userId: string) =>
185		convexFetch<ConvexTextPreferences | null>('/api/textPreferences/get', { userId }),
186
187	save: (
188		userId: string,
189		defaultStyle: {
190			fontFamily: string;
191			fontSize: number;
192			color?: string;
193			gradient?: string[];
194			bold: boolean;
195			italic: boolean;
196			underline: boolean;
197			strikethrough: boolean;
198		},
199		allowFormatting: boolean,
200		maxMessageLength: number
201	) =>
202		convexFetch<{ success: boolean; id: string }>('/api/textPreferences/save', {
203			userId,
204			defaultStyle,
205			allowFormatting,
206			maxMessageLength
207		})
208};
209
210export default { users, sessions, messages, chatRooms, textPreferences };

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 8

↑↓ moveenter openesc close