Skip to content

Commit

Permalink
feat: 캐시 타입 safety 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Tekiter committed Jul 29, 2023
1 parent ad36233 commit 9e09da1
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 24 deletions.
3 changes: 0 additions & 3 deletions apps/ssr-gateway/src/cache/index.ts

This file was deleted.

53 changes: 32 additions & 21 deletions apps/ssr-gateway/src/router/recruit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ import { Block } from 'notion-types';
import { parsePageId } from 'notion-utils';
import { z } from 'zod';

import { storageFactory } from '../storage/kv';
import { Context } from '../trpc/context';
import { internalProcedure, publicProcedure, router } from '../trpc/stub';

export const recruitRouter = router({
page: internalProcedure.input(z.object({ id: z.string().optional() })).query(async ({ ctx, input }) => {
const pageStorage = getPageStorage(ctx.kv);
const allowedPagesStorage = getAllowedPagesStorage(ctx.kv);

const pageId = parsePageId(input.id ?? ctx.recruit.rootPageId);
if (!pageId) {
return { status: 'NOT_FOUND' } as const;
}

const allowedPages = (await ctx.kv.get(kvKeys.allowedPages(), 'json')) as string[] | null;
const allowedPages = await allowedPagesStorage.get('');
if (!allowedPages) {
return { status: 'NEED_REFRESH' } as const;
}
Expand All @@ -21,14 +25,17 @@ export const recruitRouter = router({
return { status: 'NOT_FOUND' } as const;
}

const validated = pageRecordValidator.safeParse(await ctx.kv.get(kvKeys.page(pageId), 'json'));
if (!validated.success) {
const data = await pageStorage.get(pageId);

if (!data) {
return { status: 'NEED_REFRESH' } as const;
}

const { blockMap, ...pageData } = validated.data;
const { blockMap, ...pageData } = data;

const blockMapSigned = await ctx.recruit.notionClient.SignFileUrls(blockMap);
const blockMapSigned = await (async () => {
return await ctx.recruit.notionClient.SignFileUrls(blockMap);
})();

return { status: 'SUCCESS', ...pageData, blockMap: blockMapSigned } as const;
}),
Expand All @@ -45,21 +52,27 @@ type PathFragment = {
title: string;
};

const pageRecordValidator = z.object({
version: z.literal(1),
id: z.string(),
title: z.string().nullable(),
path: z.array(z.custom<PathFragment>()),
blockMap: z.custom<Record<string, Block>>(),
const getPageStorage = storageFactory({
version: 1,
prefix: 'recruit:page:',
type: z.object({
id: z.string(),
title: z.string().nullable(),
path: z.array(z.custom<PathFragment>()),
blockMap: z.custom<Record<string, Block>>(),
}),
});
type PageRecord = z.infer<typeof pageRecordValidator>;

const kvKeys = {
page: (pageId: string) => `recruit:page:${pageId}`,
allowedPages: () => 'recruit:allowdPages',
};
const getAllowedPagesStorage = storageFactory({
version: 1,
prefix: 'recruit:allowedPages',
type: z.array(z.string()),
});

async function refetchPages(ctx: Context) {
const pageStorage = getPageStorage(ctx.kv);
const allowedPagesStorage = getAllowedPagesStorage(ctx.kv);

const rootPageId = parsePageId(ctx.recruit.rootPageId);
const allowedPages: string[] = [];

Expand Down Expand Up @@ -89,18 +102,16 @@ async function refetchPages(ctx: Context) {
const childTraversePromises = childPageIds.map(async (id) => traverse(id, newPath));
await Promise.all(childTraversePromises);

const record: PageRecord = {
version: 1,
await pageStorage.put(pageId, {
id: pageId,
title,
path: newPath,
blockMap,
};
await ctx.kv.put(kvKeys.page(pageId), JSON.stringify(record));
});

allowedPages.push(pageId);
}

await traverse(rootPageId);
await ctx.kv.put(kvKeys.allowedPages(), JSON.stringify(allowedPages));
await allowedPagesStorage.put('', allowedPages);
}
69 changes: 69 additions & 0 deletions apps/ssr-gateway/src/storage/kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { KVNamespace } from '@cloudflare/workers-types';
import { z } from 'zod';

interface StorageConfig<T, V extends number> {
version: V;
prefix: string;
type: z.ZodType<T>;
}

export function createStorageClient<T, V extends number>(kv: KVNamespace, config: StorageConfig<T, V>) {
const { version, type, prefix } = config;

const recordSchema = z.object({
version: z.literal(version),
data: type,
});

async function get(key?: string) {
const record = await kv.get(prefix + (key ?? ''), 'json');
if (!record) {
return null;
}
const parsed = recordSchema.safeParse(record);

if (!parsed.success) {
return null;
}
return parsed.data.data;
}

async function put(key: string, value: T) {
const newData = {
version,
data: value,
};
await kv.put(prefix + key, JSON.stringify(newData));
}

async function remove(key: string) {
await kv.delete(prefix + key);
}

async function list() {
const { keys } = await kv.list({
prefix,
});
return keys;
}

async function deleteAll() {
const keys = await list();

await Promise.all(keys.map((key) => remove(key.name)));
}

return {
get,
put,
delete: remove,
list,
deleteAll,
};
}

export function storageFactory<T, V extends number>(config: StorageConfig<T, V>) {
return (kv: KVNamespace) => {
return createStorageClient(kv, config);
};
}

0 comments on commit 9e09da1

Please sign in to comment.