v0.1.2 944de91

hooks.server.ts RawHistoryFolder

1import type { Handle } from "@sveltejs/kit";
2import db from "$lib/db/db.server";
3import { handleRateLimit } from "$lib/api/rate-limiter";
4import { users } from "$lib/db/schema";
5import { createLogger } from "$lib/utils/logger.server";
6import { ensureDefaultChatRoom } from "$lib/utils/chat.server";
7import { validateSessionToken } from "$lib/api/session.server";
8import { setSessionTokenCookie, deleteSessionTokenCookie } from "$lib/api/session.cookie";
9
10const log = createLogger('hooks-server');
11
12// Set all users to offline on server start
13async function setAllUsersOffline() {
14    try {
15        const now = Date.now();
16        await db.update(users)
17            .set({ status: 'offline', lastSeen: now });
18        log.info('All users set to offline on server start');
19    } catch (error: unknown) {
20        log.error('Failed to set users offline on server start:', { error });
21    }
22}
23
24// Initialize server
25Promise.resolve()
26    .then(async () => {
27        log.info('Initializing server...');
28        await ensureDefaultChatRoom(db);
29        await setAllUsersOffline();
30        log.info('Server initialized successfully');
31    })
32    .catch((error: unknown) => {
33        log.error('Failed to initialize server:', { error });
34        process.exit(1);
35    });
36
37export const handle: Handle = async ({ event, resolve }) => {
38    log.debug("Handling request", { path: event.url.pathname });
39    
40    // Get session token from cookies
41    const token = event.cookies.get("session") ?? null;
42
43    // Validate session token if token exists
44    if (token) {
45        const { session, user } = await validateSessionToken(token);
46        if (session) {
47            log.debug("Valid session found", { user: user?.nickname });
48            setSessionTokenCookie(event, token, session.expiresAt);
49            event.locals.session = session;
50            event.locals.user = user;
51        } else {
52            log.debug("Invalid session token");
53            deleteSessionTokenCookie(event);
54            event.locals.session = null;
55            event.locals.user = null;
56        }
57    }
58
59    // Identify public chat and room requests if there is no valid session
60    const isPublicChatRequest = event.url.pathname.startsWith('/api/chat/messages') && event.request.method === 'GET';
61    const isPublicRoomRequest = event.url.pathname.startsWith('/api/rooms/') && event.request.method === 'GET';
62
63    if ((isPublicChatRequest || isPublicRoomRequest) && !event.locals.session) {
64        log.debug("Public request accessed", { path: event.url.pathname });
65        const response = await resolve(event);
66        return response;
67    }
68    
69    // Public routes that don't require authentication
70    const publicRoutes = [
71        "/",
72        "/api/session/login",
73        "/api/session/validate",
74        "/api/register",
75        "/login",
76        "/register"
77    ];
78
79    // Handle public routes
80    if (publicRoutes.includes(event.url.pathname)) {
81        log.debug("Public route accessed", { path: event.url.pathname });
82        const response = await resolve(event);
83        return response;
84    }
85
86    // Skip rate limiting for SSE endpoint when authenticated
87    if (event.url.pathname.startsWith('/api/sse')) {
88        return await resolve(event);
89    }
90
91    // Apply rate limiting for non-public endpoints
92    if (!isPublicChatRequest && !isPublicRoomRequest) {
93        const rateLimitResult = await handleRateLimit(event);
94        if (rateLimitResult?.status === 429) {
95            return new Response('Too Many Requests', {
96                status: 429,
97                headers: {
98                    'Retry-After': '60'
99                }
100            });
101        }
102    }
103
104    const response = await resolve(event);
105    return response;
106}; 

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