The recipes.blue monorepo recipes.blue
recipes appview atproto
2
fork

Configure Feed

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

feat: update getRecipes endpoint

hayden.moe 1fda649a b3b4fff0

verified
+37 -57
+33 -54
apps/api/src/xrpc/blue.recipes.feed.getRecipes.ts
··· 1 - import { db, desc, eq } from '@cookware/database'; 1 + import { db, eq } from '@cookware/database'; 2 2 import { recipeTable } from '@cookware/database/schema'; 3 - import { BlueRecipesFeedDefs, BlueRecipesFeedGetRecipes } from '@cookware/lexicons'; 3 + import { BlueRecipesActorDefs, BlueRecipesFeedGetRecipes, BlueRecipesFeedRecipe } from '@cookware/lexicons'; 4 4 import { json, XRPCRouter } from '@atcute/xrpc-server'; 5 - import { simpleFetchHandler, Client } from '@atcute/client'; 6 - import { getAuthorInfo, parseDid } from '../util/api.js'; 7 - import { AtprotoDid } from '@atcute/lexicons/syntax'; 5 + import { parseDid } from '../util/api.js'; 6 + import { AtprotoDid, ResourceUri } from '@atcute/lexicons/syntax'; 8 7 import { Logger } from 'pino'; 8 + import { isLegacyBlob } from '@atcute/lexicons/interfaces'; 9 9 10 10 export const registerGetRecipes = (router: XRPCRouter, _logger: Logger) => { 11 11 router.addQuery(BlueRecipesFeedGetRecipes.mainSchema, { 12 - async handler({ params: { did } }) { 12 + async handler({ params: { author, limit } }) { 13 13 let foundDid: AtprotoDid | null = null; 14 - if (did) foundDid = await parseDid(did); 14 + if (author) foundDid = await parseDid(author); 15 15 16 16 const recipes = await db.query.recipeTable.findMany({ 17 - columns: { 18 - rkey: true, 19 - title: true, 20 - description: true, 21 - time: true, 22 - serves: true, 23 - ingredientsCount: true, 24 - stepsCount: true, 25 - createdAt: true, 26 - authorDid: true, 27 - imageRef: true, 28 - uri: true, 29 - }, 30 - orderBy: [desc(recipeTable.createdAt)], 31 - where: foundDid ? eq(recipeTable.authorDid, foundDid) : undefined, 32 - }); 33 - 34 - const rpc = new Client({ 35 - handler: simpleFetchHandler({ 36 - service: 'https://public.api.bsky.app', 37 - }), 17 + orderBy: recipeTable.createdAt, 18 + limit: limit, 19 + where: foundDid ? eq(recipeTable.did, foundDid) : undefined, 38 20 }); 39 21 40 - let authorInfo: BlueRecipesFeedDefs.AuthorInfo | null = null; 41 - if (foundDid) { 42 - authorInfo = await getAuthorInfo(foundDid, rpc); 43 - }; 44 - 45 - const results = []; 46 - const eachRecipe = async (r: typeof recipes[0]) => ({ 47 - author: authorInfo || await getAuthorInfo(r.authorDid, rpc), 48 - rkey: r.rkey, 49 - title: r.title, 50 - time: r.time, 51 - serves: r.serves ?? 1, 52 - description: r.description || undefined, 53 - ingredients: r.ingredientsCount as number, 54 - steps: r.stepsCount as number, 55 - imageUrl: r.imageRef 56 - ? `https://cdn.bsky.app/img/feed_thumbnail/plain/${r.authorDid}/${r.imageRef}@jpeg` 57 - : undefined, 58 - }); 59 - 60 - for (const result of recipes) { 61 - results.push(await eachRecipe(result)); 62 - } 63 - 64 22 return json({ 65 - author: authorInfo || undefined, 66 - recipes: results, 23 + nextCursor: '', 24 + recipes: recipes.map((recipe) => ({ 25 + author: { 26 + $type: BlueRecipesActorDefs.profileViewBasicSchema.shape.$type.wrapped.expected, 27 + did: recipe.did, 28 + handle: 'hayden.moe', 29 + createdAt: new Date().toISOString(), 30 + }, 31 + cid: '', 32 + indexedAt: new Date().toISOString(), 33 + record: { 34 + $type: BlueRecipesFeedRecipe.mainSchema.object.shape.$type.expected, 35 + title: recipe.title, 36 + description: recipe.description ?? undefined, 37 + time: recipe.time ?? undefined, 38 + serves: recipe.serves ?? undefined, 39 + ingredients: recipe.ingredients as BlueRecipesFeedRecipe.Ingredient[], 40 + steps: recipe.steps as BlueRecipesFeedRecipe.Step[], 41 + image: isLegacyBlob(recipe.imageRef) ? undefined : recipe.imageRef ?? undefined, 42 + createdAt: recipe.createdAt.toISOString(), 43 + }, 44 + uri: recipe.uri as ResourceUri, 45 + })), 67 46 }); 68 47 } 69 48 });
+2 -1
libs/lexicons/lexicons/feed/getRecipes.tsp
··· 1 1 import "@typelex/emitter"; 2 + import "./defs.tsp"; 2 3 3 4 namespace blue.recipes.feed.getRecipes { 4 5 /** Response model for fetching multiple recipes. */ ··· 8 9 @minValue(1) @maxValue(100) limit?: integer = 50, 9 10 cursor?: string, 10 11 ): { 11 - @required recipes: blue.recipes.feed.recipe.Main[]; 12 + @required recipes: blue.recipes.feed.defs.RecipeView[]; 12 13 @required nextCursor: string; 13 14 }; 14 15 }
+2 -2
libs/lexicons/lib/types/blue/recipes/feed/getRecipes.ts
··· 1 1 import type {} from "@atcute/lexicons"; 2 2 import * as v from "@atcute/lexicons/validations"; 3 3 import type {} from "@atcute/lexicons/ambient"; 4 - import * as BlueRecipesFeedRecipe from "./recipe.js"; 4 + import * as BlueRecipesFeedDefs from "./defs.js"; 5 5 6 6 const _mainSchema = /*#__PURE__*/ v.query("blue.recipes.feed.getRecipes", { 7 7 params: /*#__PURE__*/ v.object({ ··· 24 24 schema: /*#__PURE__*/ v.object({ 25 25 nextCursor: /*#__PURE__*/ v.string(), 26 26 get recipes() { 27 - return /*#__PURE__*/ v.array(BlueRecipesFeedRecipe.mainSchema); 27 + return /*#__PURE__*/ v.array(BlueRecipesFeedDefs.recipeViewSchema); 28 28 }, 29 29 }), 30 30 },