feat(cache): single-owner caching across nginx, edge and content edits
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 28s
CI / tests-unit (push) Successful in 1m39s
CI / tests-integration (push) Successful in 1m41s
CI / tests-ui (push) Successful in 2m27s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m35s
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 28s
CI / tests-unit (push) Successful in 1m39s
CI / tests-integration (push) Successful in 1m41s
CI / tests-ui (push) Successful in 2m27s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m35s
Rebuild production nginx from the repo (deployment/proxy/*) with a single Cache-Control owner per route: the app stays the source, nginx only manages headers, and Cloudflare stores the public API allowlist at the edge. - deployment/proxy: nginx.conf, mime.types, nginx-cms.conf and the blue/green upstream snippet; config backed by scripts/nginx-sync.sh (idempotent install + reload, --check/--force). - nginx serves Cache-Tag headers on the public allowlist (cms-public), gamedata, client and camera responses so the edge and purge stay in sync. - src/lib/edge-cache.ts + tests: coalesced, fire-and-forget edge purges that no-op unless Cloudflare is configured; scripts/cf-purge.sh and cf-setup-cache.sh create and purge the cache rule. - src/lib/cloudflare-api.ts: purgeCacheByTags/purgeCacheByUrls. - Purge hooks after catalog exports (public + gamedata) and on shop, team, guild, photo and rare-values edits; ci-deploy purges after each release. - src/proxy.ts excludes the imaging/images docs from the middleware matcher.
This commit is contained in:
1 parent
30dcecd530
commit
7697728d07
18 files changed
+1001
-1
No files matched your search
@@ -165,6 +165,63 @@ export async function verifyCloudflareConnection(): Promise<CloudflareConnection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge specific URLs from the Cloudflare edge cache (POST purge_cache).
|
||||
* At most 30 URLs are allowed per call by the API; split larger batches.
|
||||
*/
|
||||
export async function purgeCacheByUrls(
|
||||
urls: string[],
|
||||
): Promise<{ purged: number }> {
|
||||
const config = getCloudflareApiConfig();
|
||||
if (!config.zoneId) {
|
||||
throw new CloudflareApiError("CLOUDFLARE_ZONE_ID is not configured");
|
||||
}
|
||||
const zoneId: string = config.zoneId;
|
||||
if (urls.length === 0) return { purged: 0 };
|
||||
const envelopes = await Promise.all(
|
||||
chunk(urls, 30).map((files) =>
|
||||
cloudflareRequest<{ id: string }>(
|
||||
`/zones/${encodeURIComponent(zoneId)}/purge_cache`,
|
||||
{ method: "POST", body: { files } },
|
||||
),
|
||||
),
|
||||
);
|
||||
return { purged: envelopes.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge every cached response that carried one of the given Cache-Tag values
|
||||
* (the tags nginx emits in the `Cache-Tag` header). This is the cheap, exact
|
||||
* way to drop the public API / gamedata / client edge cache after a CMS edit
|
||||
* or deploy, without touching unrelated cached objects.
|
||||
*/
|
||||
export async function purgeCacheByTags(
|
||||
tags: string[],
|
||||
): Promise<{ purged: number }> {
|
||||
const config = getCloudflareApiConfig();
|
||||
if (!config.zoneId) {
|
||||
throw new CloudflareApiError("CLOUDFLARE_ZONE_ID is not configured");
|
||||
}
|
||||
const zoneId: string = config.zoneId;
|
||||
if (tags.length === 0) return { purged: 0 };
|
||||
const envelopes = await Promise.all(
|
||||
chunk(tags, 30).map((tagGroup) =>
|
||||
cloudflareRequest<{ id: string }>(
|
||||
`/zones/${encodeURIComponent(zoneId)}/purge_cache`,
|
||||
{ method: "POST", body: { tags: tagGroup } },
|
||||
),
|
||||
),
|
||||
);
|
||||
return { purged: envelopes.length };
|
||||
}
|
||||
|
||||
function chunk<T>(items: T[], size: number): T[][] {
|
||||
const out: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += size)
|
||||
out.push(items.slice(i, i + size));
|
||||
return out;
|
||||
}
|
||||
|
||||
async function createIpRule(
|
||||
ip: string,
|
||||
ttlSeconds: number,
|
||||
|
||||
Reference in new issue
Block a user