mirror of
https://github.com/FranP-code/spooky-spotify-showcase.git
synced 2025-10-13 00:02:36 +00:00
Entry endpoints
This commit is contained in:
@@ -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",
|
||||
|
||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,11 +36,6 @@ export default async function Home({
|
||||
}
|
||||
void api.post.getLatest.prefetch();
|
||||
|
||||
console.log({
|
||||
access_token,
|
||||
refresh_token,
|
||||
});
|
||||
|
||||
return (
|
||||
<HydrateClient>
|
||||
<main className="justify-centerbg-gradient-to-r flex min-h-screen flex-col items-center bg-gradient-to-r from-slate-900 to-slate-700 text-slate-200">
|
||||
|
||||
@@ -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
|
||||
|
||||
106
src/server/api/routers/entry.ts
Normal file
106
src/server/api/routers/entry.ts
Normal file
@@ -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;
|
||||
}
|
||||
}),
|
||||
});
|
||||
37
src/server/utils/index.ts
Normal file
37
src/server/utils/index.ts
Normal file
@@ -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<string | unknown> => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user