| 1 | // Paths that need a friend's password, by prefix. Comparisons fold case to |
| 2 | // match the file store, so a prefix gates every spelling of a path under it. |
| 3 | const db = getDatabase("cache.sqlite"); |
| 4 | |
| 5 | const table = sqliteTable("permissions", { |
| 6 | prefix: text().primaryKey(), |
| 7 | /** 1 needs a password. a path under no prefix is public */ |
| 8 | allow: integer().notNull(), |
| 9 | }); |
| 10 | db.migrate(table, 1); |
| 11 | |
| 12 | /** the permission of the longest prefix of `filePath`, or 0 */ |
| 13 | export function getByPrefix(filePath: string): number { |
| 14 | return db.get<{ allow: number } | undefined>(sql` |
| 15 | select allow from permissions where lower(${filePath}) glob lower(prefix) || '*' |
| 16 | order by length(prefix) desc limit 1 |
| 17 | `)?.allow ?? 0; |
| 18 | } |
| 19 | |
| 20 | /** the permission of exactly `filePath`, or 0 */ |
| 21 | export function getExact(filePath: string): number { |
| 22 | return db.select({ allow: table.allow }).from(table) |
| 23 | .where(sql`${table.prefix} = ${filePath} collate nocase`).get()?.allow ?? 0; |
| 24 | } |
| 25 | |
| 26 | export function set(prefix: string, allow: number) { |
| 27 | if (allow) { |
| 28 | db.insert(table).values({ prefix, allow }).onConflictDoUpdate({ target: table.prefix, set: { allow } }).run(); |
| 29 | } else { |
| 30 | db.delete(table).where(eq(table.prefix, prefix)).run(); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | import { getDatabase } from "#sitegen/drizzle"; |
| 35 | import { eq, sql } from "drizzle-orm"; |
| 36 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |