The recipes.blue monorepo recipes.blue
recipes appview atproto

feat: api improvements

hayden.moe 45bd2167 0efd62d9

verified
Changed files
+66 -30
apps
libs
lexicons
lexicons
profiles
lib
types
blue
recipes
actor
+18
apps/api/src/util/api.ts
··· 4 4 import { ActorIdentifier, AtprotoDid, Handle, isHandle } from '@atcute/lexicons/syntax'; 5 5 import { isAtprotoDid } from '@atcute/identity'; 6 6 import { RedisClient } from 'bun'; 7 + import { ProfileViewBasic } from '../../../../libs/lexicons/dist/types/blue/recipes/actor/defs.js'; 8 + import { Blob, LegacyBlob } from '@atcute/lexicons'; 9 + import { buildCdnUrl } from './cdn.js'; 7 10 8 11 const handleResolver = new CompositeHandleResolver({ 9 12 strategy: 'race', ··· 43 46 44 47 return handle; 45 48 } 49 + 50 + export const buildProfileViewBasic = async (author: { 51 + did: AtprotoDid; 52 + displayName: string; 53 + pronouns: string | null; 54 + avatarRef: Blob | LegacyBlob | null; 55 + createdAt: Date; 56 + }, redis: RedisClient): Promise<ProfileViewBasic> => ({ 57 + did: author.did, 58 + handle: await getHandle(author.did, redis), 59 + displayName: author.displayName, 60 + pronouns: author.pronouns ?? undefined, 61 + avatar: author.avatarRef ? buildCdnUrl('avatar', author.did, author.avatarRef) : undefined, 62 + createdAt: author.createdAt.toISOString(), 63 + });
+34 -25
apps/api/src/xrpc/blue.recipes.feed.getRecipe.ts
··· 1 1 import { json, XRPCRouter, XRPCError } from '@atcute/xrpc-server'; 2 - import { BlueRecipesFeedGetRecipe, BlueRecipesFeedRecipe, BlueRecipesActorDefs } from '@cookware/lexicons'; 2 + import { BlueRecipesFeedGetRecipe, BlueRecipesFeedRecipe } from '@cookware/lexicons'; 3 3 import { db, and, or, eq } from '@cookware/database'; 4 - import { parseDid } from '../util/api.js'; 4 + import { buildProfileViewBasic, parseDid } from '../util/api.js'; 5 5 import { Logger } from 'pino'; 6 6 import { parseResourceUri, ResourceUri } from '@atcute/lexicons'; 7 7 import { recipeTable } from '@cookware/database/schema'; 8 8 import { isLegacyBlob } from '@atcute/lexicons/interfaces'; 9 9 import { RedisClient } from 'bun'; 10 + import { buildCdnUrl } from '../util/cdn.js'; 10 11 11 12 const invalidUriError = (uri: string) => new XRPCError({ 12 13 status: 400, ··· 14 15 description: `The provided URI is invalid: ${uri}`, 15 16 }); 16 17 17 - export const registerGetRecipe = (router: XRPCRouter, _logger: Logger, _redis: RedisClient) => { 18 + export const registerGetRecipe = (router: XRPCRouter, _logger: Logger, redis: RedisClient) => { 18 19 router.addQuery(BlueRecipesFeedGetRecipe.mainSchema, { 19 20 async handler({ params: { uris } }) { 20 21 const whereClauses = []; ··· 36 37 const recipes = await db.query.recipeTable.findMany({ 37 38 orderBy: recipeTable.createdAt, 38 39 where: or(...whereClauses), 40 + with: { 41 + author: { 42 + columns: { 43 + did: true, 44 + displayName: true, 45 + pronouns: true, 46 + avatarRef: true, 47 + createdAt: true, 48 + }, 49 + }, 50 + }, 39 51 }); 40 52 41 53 return json({ 42 - recipes: recipes.map((recipe) => ({ 43 - author: { 44 - $type: BlueRecipesActorDefs.profileViewBasicSchema.shape.$type.wrapped.expected, 45 - did: recipe.did, 46 - handle: 'hayden.moe', 47 - createdAt: new Date().toISOString(), 48 - }, 49 - cid: '', 50 - indexedAt: new Date().toISOString(), 51 - record: { 52 - $type: BlueRecipesFeedRecipe.mainSchema.object.shape.$type.expected, 53 - title: recipe.title, 54 - description: recipe.description ?? undefined, 55 - time: recipe.time ?? undefined, 56 - serves: recipe.serves ?? undefined, 57 - ingredients: recipe.ingredients as BlueRecipesFeedRecipe.Ingredient[], 58 - steps: recipe.steps as BlueRecipesFeedRecipe.Step[], 59 - image: isLegacyBlob(recipe.imageRef) ? undefined : recipe.imageRef ?? undefined, 60 - createdAt: recipe.createdAt.toISOString(), 61 - }, 62 - uri: recipe.uri as ResourceUri, 63 - })), 54 + recipes: await Promise.all( 55 + recipes.map(async ({ author, ...recipe }) => ({ 56 + uri: recipe.uri as ResourceUri, 57 + author: await buildProfileViewBasic(author, redis), 58 + cid: recipe.cid, 59 + indexedAt: recipe.ingestedAt.toISOString(), 60 + record: { 61 + $type: BlueRecipesFeedRecipe.mainSchema.object.shape.$type.expected, 62 + title: recipe.title, 63 + description: recipe.description ?? undefined, 64 + time: recipe.time ?? undefined, 65 + serves: recipe.serves ?? undefined, 66 + ingredients: recipe.ingredients as BlueRecipesFeedRecipe.Ingredient[], 67 + steps: recipe.steps as BlueRecipesFeedRecipe.Step[], 68 + image: isLegacyBlob(recipe.imageRef) ? undefined : recipe.imageRef ?? undefined, 69 + createdAt: recipe.createdAt.toISOString(), 70 + }, 71 + })) 72 + ), 64 73 }); 65 74 }, 66 75 });
+12 -3
apps/api/src/xrpc/blue.recipes.feed.getRecipes.ts
··· 7 7 import { Logger } from 'pino'; 8 8 import { isLegacyBlob } from '@atcute/lexicons/interfaces'; 9 9 import { RedisClient } from 'bun'; 10 + import { buildCdnUrl } from '../util/cdn.js'; 10 11 11 12 export const registerGetRecipes = (router: XRPCRouter, _logger: Logger, redis: RedisClient) => { 12 13 router.addQuery(BlueRecipesFeedGetRecipes.mainSchema, { ··· 27 28 limit: limit, 28 29 where: whereClauses ? and(...whereClauses) : undefined, 29 30 with: { 30 - author: true, 31 + author: { 32 + columns: { 33 + did: true, 34 + displayName: true, 35 + pronouns: true, 36 + avatarRef: true, 37 + createdAt: true, 38 + }, 39 + }, 31 40 }, 32 41 }); 33 42 ··· 44 53 did: recipe.author.did, 45 54 handle: await getHandle(recipe.author.did, redis), 46 55 displayName: recipe.author.displayName, 47 - avatar: isLegacyBlob(recipe.author.avatarRef) ? undefined : recipe.author.avatarRef ?? undefined, 56 + avatar: recipe.author.avatarRef ? buildCdnUrl('avatar', recipe.author.did, recipe.author.avatarRef) : undefined, 48 57 pronouns: recipe.author.pronouns ?? undefined, 49 58 createdAt: recipe.author.createdAt.toISOString(), 50 59 }, 51 - cid: '', 60 + cid: recipe.cid, 52 61 indexedAt: recipe.ingestedAt.toISOString(), 53 62 record: { 54 63 $type: BlueRecipesFeedRecipe.mainSchema.object.shape.$type.expected,
+1 -1
libs/lexicons/lexicons/profiles/defs.tsp
··· 10 10 displayName?: string; 11 11 12 12 pronouns?: string; 13 - avatar?: uri; 13 + avatar?: url; 14 14 15 15 @format("datetime") 16 16 createdAt?: string;
+1 -1
libs/lexicons/lib/types/blue/recipes/actor/defs.ts
··· 5 5 $type: /*#__PURE__*/ v.optional( 6 6 /*#__PURE__*/ v.literal("blue.recipes.actor.defs#profileViewBasic"), 7 7 ), 8 - avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 8 + avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 9 9 createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 10 10 did: /*#__PURE__*/ v.didString(), 11 11 /**