Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
87
fork

Configure Feed

Select the types of activity you want to include in your feed.

check against db

+80 -20
+55 -2
apps/hosting-service/src/lib/file-serving.integration.test.ts
··· 3 3 // Fake storage shared across tests; reset in beforeEach 4 4 type FakeEntry = { data: Uint8Array; mimeType?: string; encoding?: string; checksum?: string } 5 5 const storageData = new Map<string, FakeEntry>() 6 + const storageGetWithMetadataKeys: string[] = [] 7 + let siteFileCids: Record<string, string> | null = null 6 8 7 9 const fakeStorage = { 8 10 async get(key: string) { ··· 10 12 return entry?.data ?? null 11 13 }, 12 14 async getWithMetadata(key: string) { 15 + storageGetWithMetadataKeys.push(key) 13 16 const entry = storageData.get(key) 14 17 if (!entry) return null 15 18 return { ··· 60 63 getStorageConfig: () => ({}), 61 64 })) 62 65 mock.module('./db', () => ({ 63 - getSiteCache: async () => null, 66 + getSiteCache: async () => 67 + siteFileCids 68 + ? { 69 + did: DID, 70 + rkey: RKEY, 71 + record_cid: 'record-cid', 72 + file_cids: siteFileCids, 73 + cached_at: 0, 74 + updated_at: 0, 75 + } 76 + : null, 64 77 getSiteSettingsCache: async () => null, 65 78 CACHE_ONLY: true, 66 79 })) ··· 71 84 fetchAndCacheSite: async () => false, 72 85 })) 73 86 74 - const { serveFileInternal } = await import('./file-serving') 87 + const { cache } = await import('./cache-manager') 88 + const { resetHtmlHotCacheWarmupForTests } = await import('./html-prewarm') 89 + const { serveFileInternal, serveFromCache } = await import('./file-serving') 75 90 76 91 const DID = 'did:plc:test' 77 92 const RKEY = 'hydrant-docs' ··· 86 101 describe('serveFileInternal directory-index fallback for extensioned paths', () => { 87 102 beforeEach(() => { 88 103 storageData.clear() 104 + storageGetWithMetadataKeys.length = 0 105 + siteFileCids = null 106 + cache.clear('redirectRules') 107 + resetHtmlHotCacheWarmupForTests() 89 108 }) 90 109 91 110 test('serves index.html when requested path with .md extension is actually a directory', async () => { ··· 113 132 const response = await serveFileInternal(DID, RKEY, 'concepts/missing.md') 114 133 115 134 expect(response.status).toBe(404) 135 + }) 136 + 137 + test('skips storage miss before redirect when manifest says extensioned direct file is absent', async () => { 138 + storeFile('_redirects', '/getting-started.md /docs/getting-started 301', 'text/plain') 139 + siteFileCids = { 140 + _redirects: 'redirects-cid', 141 + 'getting-started.md/index.html': 'index-cid', 142 + } 143 + 144 + const response = await serveFromCache( 145 + DID, 146 + RKEY, 147 + 'getting-started.md', 148 + 'https://hydrant.klbr.net/getting-started.md', 149 + ) 150 + 151 + expect(response.status).toBe(301) 152 + expect(response.headers.get('Location')).toBe('/docs/getting-started') 153 + expect(storageGetWithMetadataKeys).not.toContain(`${DID}/${RKEY}/getting-started.md`) 154 + }) 155 + 156 + test('serves a direct file once before non-forced redirect when manifest says it exists', async () => { 157 + storeFile('_redirects', '/direct.md /elsewhere 301', 'text/plain') 158 + storeFile('direct.md', 'direct markdown', 'text/markdown') 159 + siteFileCids = { 160 + _redirects: 'redirects-cid', 161 + 'direct.md': 'direct-cid', 162 + } 163 + 164 + const response = await serveFromCache(DID, RKEY, 'direct.md', 'https://hydrant.klbr.net/direct.md') 165 + 166 + expect(response.status).toBe(200) 167 + expect(await response.text()).toBe('direct markdown') 168 + expect(storageGetWithMetadataKeys.filter((key) => key === `${DID}/${RKEY}/direct.md`)).toHaveLength(1) 116 169 }) 117 170 })
+25 -18
apps/hosting-service/src/lib/file-serving.ts
··· 178 178 return Array.from(entries.entries()).map(([name, isDirectory]) => ({ name, isDirectory })) 179 179 } 180 180 181 + async function hasFileForNonForcedRedirect( 182 + did: string, 183 + rkey: string, 184 + filePath: string, 185 + indexFiles: string[], 186 + trace?: RequestTrace | null, 187 + ): Promise<boolean> { 188 + let checkPath: string = filePath || indexFiles[0] || 'index.html' 189 + if (checkPath.endsWith('/')) { 190 + checkPath += indexFiles[0] || 'index.html' 191 + } 192 + 193 + const normalizedCheckPath = checkPath.startsWith('/') ? checkPath.slice(1) : checkPath 194 + const siteCache = await span(trace, 'db:siteCache:redirectCheck', () => getSiteCache(did, rkey)) 195 + if (siteCache) { 196 + const fileCids = normalizeFileCids(siteCache.file_cids).value 197 + return fileCids[normalizedCheckPath] !== undefined 198 + } 199 + 200 + const fileInStorage = await span(trace, `storage:${checkPath}`, () => getFileWithMetadata(did, rkey, checkPath)) 201 + return fileInStorage !== null 202 + } 203 + 181 204 async function getFileForRequest( 182 205 did: string, 183 206 rkey: string, ··· 361 384 362 385 // If not forced, check if the requested file exists before redirecting 363 386 if (!rule.force) { 364 - // Build the expected file path 365 - let checkPath: string = filePath || indexFiles[0] || 'index.html' 366 - if (checkPath.endsWith('/')) { 367 - checkPath += indexFiles[0] || 'index.html' 368 - } 369 - 370 - const fileInStorage = await span(trace, `storage:${checkPath}`, () => getFileWithMetadata(did, rkey, checkPath)) 371 - 372 387 // If file exists and redirect is not forced, serve the file normally 373 - if (fileInStorage) { 388 + if (await hasFileForNonForcedRedirect(did, rkey, filePath, indexFiles, trace)) { 374 389 const response = await serveFileInternal(did, rkey, filePath, settings, headers, trace) 375 390 logTrace(trace, filePath || '/', logger) 376 391 return response ··· 679 694 680 695 // If not forced, check if the requested file exists before redirecting 681 696 if (!rule.force) { 682 - // Build the expected file path 683 - let checkPath: string = filePath || indexFiles[0] || 'index.html' 684 - if (checkPath.endsWith('/')) { 685 - checkPath += indexFiles[0] || 'index.html' 686 - } 687 - 688 - const fileInStorage = await span(trace, `storage:${checkPath}`, () => getFileWithMetadata(did, rkey, checkPath)) 689 - 690 697 // If file exists and redirect is not forced, serve the file normally 691 - if (fileInStorage) { 698 + if (await hasFileForNonForcedRedirect(did, rkey, filePath, indexFiles, trace)) { 692 699 const response = await serveFileInternalWithRewrite(did, rkey, filePath, basePath, settings, headers, trace) 693 700 logTrace(trace, filePath || '/', logger) 694 701 return response