From ce6cf91c77a8001eefa85cb14b6ae7d812432381 Mon Sep 17 00:00:00 2001 From: Francisco Pessano Date: Fri, 11 Oct 2024 01:30:52 -0300 Subject: [PATCH] Entry endpoints --- package.json | 1 + pnpm-lock.yaml | 33 +++++++++- prisma/schema.prisma | 10 ++- src/app/page.tsx | 5 -- src/server/api/root.ts | 7 +++ src/server/api/routers/entry.ts | 106 ++++++++++++++++++++++++++++++++ src/server/utils/index.ts | 37 +++++++++++ 7 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/server/api/routers/entry.ts create mode 100644 src/server/utils/index.ts diff --git a/package.json b/package.json index 2fef18b..1359c6e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@trpc/client": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", + "cloudinary": "^2.5.1", "geist": "^1.3.0", "next": "^14.2.4", "react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9061c5c..9ddf715 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@trpc/server': specifier: ^11.0.0-rc.446 version: 11.0.0-rc.566 + cloudinary: + specifier: ^2.5.1 + version: 2.5.1 geist: specifier: ^1.3.0 version: 1.3.1(next@14.2.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) @@ -547,6 +550,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cloudinary@2.5.1: + resolution: {integrity: sha512-CNg6uU53Hl4FEVynkTGpt5bQEAQWDHi3H+Sm62FzKf5uQHipSN2v7qVqS8GRVqeb0T1WNV+22+75DOJeRXYeSQ==} + engines: {node: '>=9'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1182,6 +1189,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1485,6 +1495,14 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -2293,6 +2311,11 @@ snapshots: client-only@0.0.1: {} + cloudinary@2.5.1: + dependencies: + lodash: 4.17.21 + q: 1.5.1 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2553,7 +2576,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -2566,7 +2589,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -2588,7 +2611,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -3094,6 +3117,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3328,6 +3353,8 @@ snapshots: punycode@2.3.1: {} + q@1.5.1: {} + qs@6.13.0: dependencies: side-channel: 1.0.6 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9472013..33ded7c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,7 +19,15 @@ model Post { @@index([name]) } -model KeyValue { +model SpookyImage { + id Int @id @default(autoincrement()) // Auto-incrementing ID + key String @unique // The key must be unique + value String // Value associated with the key + createdAt DateTime @default(now()) // Timestamp for when the entry was created + updatedAt DateTime @updatedAt // Auto-updating timestamp for when the entry was last updated +} + +model GeneratedImage { id Int @id @default(autoincrement()) // Auto-incrementing ID key String @unique // The key must be unique value String // Value associated with the key diff --git a/src/app/page.tsx b/src/app/page.tsx index e50c72d..d5cf986 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -36,11 +36,6 @@ export default async function Home({ } void api.post.getLatest.prefetch(); - console.log({ - access_token, - refresh_token, - }); - return (
diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 59dac6a..64cf98f 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -1,5 +1,11 @@ import { postRouter } from "@/server/api/routers/post"; import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc"; +import * as cloudinary from "cloudinary"; +import { entryRouter } from "./routers/entry"; + +cloudinary.v2.config({ + secure: true, +}); /** * This is the primary router for your server. @@ -8,6 +14,7 @@ import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc"; */ export const appRouter = createTRPCRouter({ post: postRouter, + entry: entryRouter, }); // export type definition of API diff --git a/src/server/api/routers/entry.ts b/src/server/api/routers/entry.ts new file mode 100644 index 0000000..45768ed --- /dev/null +++ b/src/server/api/routers/entry.ts @@ -0,0 +1,106 @@ +import { z } from "zod"; + +import { createTRPCRouter, publicProcedure } from "@/server/api/trpc"; +import { + generateEntryUniqueKey, + makeImageSpooky, + uploadImage, +} from "@/server/utils"; + +export type Entry = { + type: "artist" | "album"; + name: string; + image: string; +}; + +export const entryRouter = createTRPCRouter({ + get: publicProcedure + .input( + z.object({ + type: z.enum(["artist", "album"]), + name: z.string().min(1), + image: z.string().min(1), + }), + ) + .query(async ({ ctx, input }) => { + const key = generateEntryUniqueKey(input); + const existingEntry = await ctx.db.spookyImage.findFirst({ + where: { + key, + }, + }); + return existingEntry; + }), + + generate: publicProcedure + .input( + z.object({ + entry: z.object({ + type: z.enum(["artist", "album"]), + name: z.string().min(1), + image: z.string().min(1), + }), + }), + ) + .mutation(async ({ ctx, input }) => { + const key = generateEntryUniqueKey(input.entry); + try { + const uploadedImage = await uploadImage(input.entry.image); + if (!uploadedImage || typeof uploadedImage !== "string") { + throw new Error("Failed to upload image"); + } + const spookyImage = makeImageSpooky(uploadedImage); + if (!spookyImage || typeof spookyImage !== "string") { + throw new Error("Failed to make image spooky"); + } + await ctx.db.generatedImage.upsert({ + where: { + key, + }, + update: {}, + create: { + key, + value: spookyImage, + }, + }); + return spookyImage; + } catch (error) { + console.error(error); + return error; + } + }), + + save: publicProcedure + .input( + z.object({ + entry: z.object({ + type: z.enum(["artist", "album"]), + name: z.string().min(1), + image: z.string().min(1), + }), + }), + ) + .mutation(async ({ ctx, input }) => { + const key = generateEntryUniqueKey(input.entry); + try { + const generatedImage = await ctx.db.generatedImage.findFirst({ + where: { + key, + }, + }); + if (!generatedImage) { + throw new Error("No generated image found"); + } + const entry = await ctx.db.spookyImage.create({ + data: { + key, + value: generatedImage.value, + }, + }); + return entry; + } catch (error) { + console.error(error); + return error; + } + }), +}); diff --git a/src/server/utils/index.ts b/src/server/utils/index.ts new file mode 100644 index 0000000..9e65611 --- /dev/null +++ b/src/server/utils/index.ts @@ -0,0 +1,37 @@ +import * as cloudinary from "cloudinary"; +import { Entry } from "../api/routers/entry"; + +export const generateEntryUniqueKey = (entry: Entry): string => { + return `${entry.type}:${entry.name.trim().toLowerCase().replace(/\s/g, "-")}`; +}; + +export const uploadImage = async ( + imagePath: string, +): Promise => { + const options = { + use_filename: true, + unique_filename: false, + overwrite: true, + }; + + try { + const result = await cloudinary.v2.uploader.upload(imagePath, options); + console.log(result); + return result.public_id; + } catch (error) { + console.error(error); + return error; + } +}; + +export const makeImageSpooky = (publicId: string) => { + const options = { effect: "gen_background_replace" }; + + try { + const result = cloudinary.v2.image(publicId, { ...options }); + console.log(result); + return result; + } catch (error) { + console.error(error); + } +};