One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links ๐Ÿ“… calendar.xyehr.cn
5
fork

Configure Feed

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

Merge pull request #225 from EvanTechDev/feature/fix-backup-data-refresh-bug

fix: prevent stale backup restore data from cache

authored by

Evan Huang and committed by
GitHub
faebf24c 8d4a8396

+16 -8
+15 -7
app/api/blob/route.ts
··· 5 5 import { deleteRecord, getRecord, putRecord } from "@/lib/atproto"; 6 6 7 7 export const runtime = "nodejs"; 8 + export const dynamic = "force-dynamic"; 8 9 9 10 const postgresUrl = process.env.POSTGRES_URL; 10 11 const useSsl = ··· 42 43 43 44 const ATPROTO_BACKUP_COLLECTION = "app.onecalendar.backup"; 44 45 const ATPROTO_BACKUP_RKEY = "latest"; 46 + 47 + function jsonNoStore(body: unknown, init?: ResponseInit) { 48 + const response = NextResponse.json(body, init); 49 + response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate"); 50 + response.headers.set("Pragma", "no-cache"); 51 + response.headers.set("Expires", "0"); 52 + return response; 53 + } 45 54 46 55 export async function POST(req: NextRequest) { 47 56 try { ··· 117 126 dpopPublicJwk: atproto.dpopPublicJwk, 118 127 }); 119 128 const value = record.value ?? {}; 120 - return NextResponse.json({ 129 + return jsonNoStore({ 121 130 ciphertext: value.ciphertext, 122 131 iv: value.iv, 123 132 timestamp: value.updatedAt, 124 133 backend: "atproto", 125 134 }); 126 135 } catch { 127 - return NextResponse.json({ error: "Not found" }, { status: 404 }); 136 + return jsonNoStore({ error: "Not found" }, { status: 404 }); 128 137 } 129 138 } 130 139 131 140 const user = await currentUser(); 132 - if (!user) 133 - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 141 + if (!user) return jsonNoStore({ error: "Unauthorized" }, { status: 401 }); 134 142 135 143 await initDB(); 136 144 ··· 141 149 [user.id], 142 150 ); 143 151 if (result.rowCount === 0) 144 - return NextResponse.json({ error: "Not found" }, { status: 404 }); 152 + return jsonNoStore({ error: "Not found" }, { status: 404 }); 145 153 146 - return NextResponse.json({ 154 + return jsonNoStore({ 147 155 ciphertext: result.rows[0].encrypted_data, 148 156 iv: result.rows[0].iv, 149 157 timestamp: result.rows[0].timestamp, ··· 154 162 } 155 163 } catch (e: unknown) { 156 164 const message = e instanceof Error ? e.message : "Internal error"; 157 - return NextResponse.json({ error: message }, { status: 500 }); 165 + return jsonNoStore({ error: message }, { status: 500 }); 158 166 } 159 167 } 160 168
+1 -1
components/app/profile/user-profile-button.tsx
··· 104 104 }; 105 105 106 106 async function apiGet() { 107 - const r = await fetch("/api/blob"); 107 + const r = await fetch("/api/blob", { cache: "no-store" }); 108 108 if (r.status === 404) return null; 109 109 if (!r.ok) throw new Error(); 110 110 return r.json();