Refactor code
This commit is contained in:
@ -54,12 +54,12 @@ function centroidFromPaletteHexes(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fallbackHex =
|
const fallbackHex =
|
||||||
hexByType["Vibrant"] ||
|
hexByType.Vibrant ||
|
||||||
hexByType["Muted"] ||
|
hexByType.Muted ||
|
||||||
hexByType["DarkVibrant"] ||
|
hexByType.DarkVibrant ||
|
||||||
hexByType["DarkMuted"] ||
|
hexByType.DarkMuted ||
|
||||||
hexByType["LightVibrant"] ||
|
hexByType.LightVibrant ||
|
||||||
hexByType["LightMuted"];
|
hexByType.LightMuted;
|
||||||
|
|
||||||
let L = 0,
|
let L = 0,
|
||||||
A = 0,
|
A = 0,
|
||||||
@ -123,7 +123,9 @@ export async function generateArtworkColorsForArtwork(artworkId: string) {
|
|||||||
for (const { type, hex } of vibrantHexes) {
|
for (const { type, hex } of vibrantHexes) {
|
||||||
if (!hex) continue;
|
if (!hex) continue;
|
||||||
|
|
||||||
const [r, g, b] = hex.match(/\w\w/g)!.map((h) => parseInt(h, 16));
|
const match = hex.match(/\w\w/g);
|
||||||
|
if (!match) continue;
|
||||||
|
const [r, g, b] = match.map((h) => parseInt(h, 16));
|
||||||
const name = generateColorName(hex);
|
const name = generateColorName(hex);
|
||||||
|
|
||||||
const color = await prisma.color.upsert({
|
const color = await prisma.color.upsert({
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { artworkSchema } from "@/schemas/artworks/imageSchema";
|
import { artworkSchema } from "@/schemas/artworks/imageSchema";
|
||||||
import { normalizeNames, slugify } from "@/utils/artworkHelpers";
|
import { normalizeNames, slugify } from "@/utils/artworkHelpers";
|
||||||
import { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
// Updates an artwork and its tag/category relationships.
|
// Updates an artwork and its tag/category relationships.
|
||||||
export async function updateArtwork(
|
export async function updateArtwork(
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import type { SignUpResponse } from "@/types/auth";
|
|
||||||
import { registerFirstUserSchema } from "@/schemas/auth";
|
|
||||||
import type { RegisterFirstUserInput } from "@/schemas/auth";
|
import type { RegisterFirstUserInput } from "@/schemas/auth";
|
||||||
|
import { registerFirstUserSchema } from "@/schemas/auth";
|
||||||
|
import type { SignUpResponse } from "@/types/auth";
|
||||||
|
|
||||||
// Registers the very first user and upgrades them to admin.
|
// Registers the very first user and upgrades them to admin.
|
||||||
export async function registerFirstUser(input: RegisterFirstUserInput) {
|
export async function registerFirstUser(input: RegisterFirstUserInput) {
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { headers } from "next/headers";
|
|
||||||
import type { SessionWithRole } from "@/types/auth";
|
import type { SessionWithRole } from "@/types/auth";
|
||||||
import type { UsersListRow } from "@/types/users";
|
import type { UserRole, UsersListRow } from "@/types/users";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
// Returns all users for the admin users table.
|
// Returns all users for the admin users table.
|
||||||
export async function getUsers(): Promise<UsersListRow[]> {
|
export async function getUsers(): Promise<UsersListRow[]> {
|
||||||
@ -28,9 +28,16 @@ export async function getUsers(): Promise<UsersListRow[]> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows.map((r) => ({
|
return rows.map((r) => {
|
||||||
|
if (r.role !== "admin" && r.role !== "user") {
|
||||||
|
throw new Error(`Unexpected user role: ${r.role}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
...r,
|
...r,
|
||||||
|
role: r.role as UserRole,
|
||||||
createdAt: r.createdAt.toISOString(),
|
createdAt: r.createdAt.toISOString(),
|
||||||
updatedAt: r.updatedAt.toISOString(),
|
updatedAt: r.updatedAt.toISOString(),
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { s3 } from "@/lib/s3";
|
|||||||
import type { S3Body } from "@/types/s3";
|
import type { S3Body } from "@/types/s3";
|
||||||
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "node:stream";
|
||||||
|
|
||||||
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
||||||
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
|
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { S3Body } from "@/types/s3";
|
|||||||
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import archiver from "archiver";
|
import archiver from "archiver";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "node:stream";
|
||||||
|
|
||||||
// Streams commission request files (single or zip) from S3.
|
// Streams commission request files (single or zip) from S3.
|
||||||
type Mode = "display" | "download" | "bulk";
|
type Mode = "display" | "download" | "bulk";
|
||||||
@ -17,13 +17,30 @@ function contentDisposition(filename: string, mode: Mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeZipEntryName(name: string) {
|
function sanitizeZipEntryName(name: string) {
|
||||||
return name.replace(/[^\w.\- ()\[\]]+/g, "_").slice(0, 180);
|
return name.replace(/[^\w.\- ()[\]]+/g, "_").slice(0, 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
function isWebReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
||||||
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
|
return !!value && typeof (value as ReadableStream<Uint8Array>).getReader === "function";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function webStreamToAsyncIterable(stream: ReadableStream<Uint8Array>) {
|
||||||
|
const reader = stream.getReader();
|
||||||
|
return {
|
||||||
|
async *[Symbol.asyncIterator]() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
if (value) yield value;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function toBodyInit(body: S3Body): BodyInit {
|
function toBodyInit(body: S3Body): BodyInit {
|
||||||
if (body instanceof Readable) {
|
if (body instanceof Readable) {
|
||||||
return Readable.toWeb(body) as ReadableStream<Uint8Array>;
|
return Readable.toWeb(body) as ReadableStream<Uint8Array>;
|
||||||
@ -141,9 +158,16 @@ export async function GET(req: NextRequest) {
|
|||||||
if (body instanceof Readable) {
|
if (body instanceof Readable) {
|
||||||
archive.append(body, { name: entryName });
|
archive.append(body, { name: entryName });
|
||||||
} else if (isWebReadableStream(body)) {
|
} else if (isWebReadableStream(body)) {
|
||||||
archive.append(Readable.from(body as AsyncIterable<Uint8Array>), { name: entryName });
|
archive.append(Readable.from(webStreamToAsyncIterable(body)), { name: entryName });
|
||||||
|
} else if (body instanceof Blob) {
|
||||||
|
const stream = body.stream();
|
||||||
|
archive.append(Readable.from(webStreamToAsyncIterable(stream)), { name: entryName });
|
||||||
|
} else if (Buffer.isBuffer(body)) {
|
||||||
|
archive.append(body, { name: entryName });
|
||||||
|
} else if (body instanceof Uint8Array) {
|
||||||
|
archive.append(Buffer.from(body), { name: entryName });
|
||||||
} else {
|
} else {
|
||||||
archive.append(body as Buffer, { name: entryName });
|
throw new Error("Unsupported S3 body type for zip entry");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
// Global error UI for the app router segment.
|
// Global error UI for the app router segment.
|
||||||
export default function Error({
|
export default function ErrorPage({
|
||||||
error,
|
error,
|
||||||
reset,
|
reset,
|
||||||
}: {
|
}: {
|
||||||
@ -27,6 +27,7 @@ export default function Error({
|
|||||||
|
|
||||||
<div className="flex justify-center gap-4 pt-2">
|
<div className="flex justify-center gap-4 pt-2">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export default function GlobalError({
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { getArtworkColorStats } from "@/actions/colors/getArtworkColorStats";
|
import { getArtworkColorStats } from "@/actions/colors/getArtworkColorStats";
|
||||||
import { processPendingArtworkColors } from "@/actions/colors/processPendingArtworkColors";
|
import { processPendingArtworkColors } from "@/actions/colors/processPendingArtworkColors";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
// Admin tool for processing pending artwork color extraction.
|
// Admin tool for processing pending artwork color extraction.
|
||||||
export function ArtworkColorProcessor() {
|
export function ArtworkColorProcessor() {
|
||||||
@ -11,14 +12,14 @@ export function ArtworkColorProcessor() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [msg, setMsg] = useState<string | null>(null);
|
const [msg, setMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
const refreshStats = async () => {
|
const refreshStats = useCallback(async () => {
|
||||||
const s = await getArtworkColorStats();
|
const s = await getArtworkColorStats();
|
||||||
setStats(s);
|
setStats(s);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void refreshStats();
|
void refreshStats();
|
||||||
}, []);
|
}, [refreshStats]);
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -48,7 +49,7 @@ export function ArtworkColorProcessor() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button onClick={run} disabled={loading || done}>
|
<Button type="button" onClick={run} disabled={loading || done}>
|
||||||
{done ? "All colors processed" : loading ? "Processing…" : "Process pending colors"}
|
{done ? "All colors processed" : loading ? "Processing…" : "Process pending colors"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|||||||
@ -157,6 +157,7 @@ function FilterButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`px-3 py-1 rounded text-sm border transition ${active
|
className={`px-3 py-1 rounded text-sm border transition ${active
|
||||||
? "bg-primary text-white border-primary"
|
? "bg-primary text-white border-primary"
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function MultiSelectFilter(props: {
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<PopoverContent className="w-[280px] p-0" align="start">
|
<PopoverContent className="w-70 p-0" align="start">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search…" />
|
<CommandInput placeholder="Search…" />
|
||||||
<CommandEmpty>No results.</CommandEmpty>
|
<CommandEmpty>No results.</CommandEmpty>
|
||||||
|
|||||||
@ -162,7 +162,9 @@ export default function ArtworkTimelapse({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{src ? (
|
{src ? (
|
||||||
<video className="w-full rounded-md border" controls preload="metadata" src={src} />
|
<video className="w-full rounded-md border" controls preload="metadata" src={src}>
|
||||||
|
<track kind="captions" label="Captions" srcLang="en" src="" />
|
||||||
|
</video>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
// Form for editing an existing artwork category.
|
// Form for editing an existing artwork category.
|
||||||
export default function EditCategoryForm({ category }: { category: ArtCategory }) {
|
export default function EditCategoryForm({ category }: { category: ArtCategory }) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
// Form for creating a new artwork category.
|
// Form for creating a new artwork category.
|
||||||
export default function NewCategoryForm() {
|
export default function NewCategoryForm() {
|
||||||
|
|||||||
@ -33,7 +33,11 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
// Admin editor for a single commission request.
|
// Admin editor for a single commission request.
|
||||||
type RequestShape = NonNullable<Awaited<ReturnType<typeof getCommissionRequestById>>>;
|
type RequestShape = NonNullable<Awaited<ReturnType<typeof getCommissionRequestById>>>;
|
||||||
type RequestShapeSerializable = Omit<RequestShape, "createdAt" | "updatedAt" | "files"> & {
|
type RequestShapeSerializable = Omit<
|
||||||
|
RequestShape,
|
||||||
|
"createdAt" | "updatedAt" | "files" | "status"
|
||||||
|
> & {
|
||||||
|
status: CommissionStatus;
|
||||||
createdAt: string | Date;
|
createdAt: string | Date;
|
||||||
updatedAt: string | Date;
|
updatedAt: string | Date;
|
||||||
files: Array<Omit<RequestShape["files"][number], "createdAt"> & { createdAt: string | Date }>;
|
files: Array<Omit<RequestShape["files"][number], "createdAt"> & { createdAt: string | Date }>;
|
||||||
@ -79,7 +83,9 @@ function bulkUrl(requestId: string) {
|
|||||||
export function CommissionRequestEditor({ request }: { request: RequestShapeSerializable }) {
|
export function CommissionRequestEditor({ request }: { request: RequestShapeSerializable }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [status, setStatus] = useState<CommissionStatus>(request.status);
|
const [status, setStatus] = useState<CommissionStatus>(() =>
|
||||||
|
isCommissionStatus(request.status) ? request.status : "NEW"
|
||||||
|
);
|
||||||
const [customerName, setCustomerName] = useState(request.customerName);
|
const [customerName, setCustomerName] = useState(request.customerName);
|
||||||
const [customerEmail, setCustomerEmail] = useState(request.customerEmail);
|
const [customerEmail, setCustomerEmail] = useState(request.customerEmail);
|
||||||
const [customerSocials, setCustomerSocials] = useState(request.customerSocials ?? "");
|
const [customerSocials, setCustomerSocials] = useState(request.customerSocials ?? "");
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { type ChangeEvent, useEffect, useRef, useState } from "react";
|
import { type ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import * as z from "zod/v4";
|
import type * as z from "zod/v4";
|
||||||
|
|
||||||
type UploadStatus = "empty" | "queued" | "uploading" | "done" | "error";
|
type UploadStatus = "empty" | "queued" | "uploading" | "done" | "error";
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import { useCallback, useEffect, useState, useTransition } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { deleteUser } from "@/actions/users/deleteUser";
|
import { deleteUser } from "@/actions/users/deleteUser";
|
||||||
import { getUsers, type UsersListRow } from "@/actions/users/getUsers";
|
import { getUsers } from "@/actions/users/getUsers";
|
||||||
|
import type { UsersListRow } from "@/types/users";
|
||||||
// import { resendVerification } from "@/actions/users/resendVerification";
|
// import { resendVerification } from "@/actions/users/resendVerification";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -10,6 +10,10 @@ export const COMMISSION_STATUSES = [
|
|||||||
|
|
||||||
export type CommissionStatus = (typeof COMMISSION_STATUSES)[number];
|
export type CommissionStatus = (typeof COMMISSION_STATUSES)[number];
|
||||||
|
|
||||||
|
function isCommissionStatus(value: string): value is CommissionStatus {
|
||||||
|
return (COMMISSION_STATUSES as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
export const BOARD_COLUMNS = {
|
export const BOARD_COLUMNS = {
|
||||||
intake: {
|
intake: {
|
||||||
title: "Intake",
|
title: "Intake",
|
||||||
@ -30,15 +34,20 @@ export const BOARD_COLUMNS = {
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const INTAKE_STATUSES = new Set<CommissionStatus>(BOARD_COLUMNS.intake.statuses);
|
||||||
|
const IN_PROGRESS_STATUSES = new Set<CommissionStatus>(BOARD_COLUMNS.inProgress.statuses);
|
||||||
|
const COMPLETED_STATUSES = new Set<CommissionStatus>(BOARD_COLUMNS.completed.statuses);
|
||||||
|
|
||||||
export type BoardColumnId = keyof typeof BOARD_COLUMNS;
|
export type BoardColumnId = keyof typeof BOARD_COLUMNS;
|
||||||
|
|
||||||
export function columnIdForStatus(status: string): BoardColumnId | null {
|
export function columnIdForStatus(status: string): BoardColumnId | null {
|
||||||
if (BOARD_COLUMNS.intake.statuses.includes(status as any)) return "intake";
|
if (!isCommissionStatus(status)) return null;
|
||||||
if (BOARD_COLUMNS.inProgress.statuses.includes(status as any)) return "inProgress";
|
if (INTAKE_STATUSES.has(status)) return "intake";
|
||||||
if (BOARD_COLUMNS.completed.statuses.includes(status as any)) return "completed";
|
if (IN_PROGRESS_STATUSES.has(status)) return "inProgress";
|
||||||
|
if (COMPLETED_STATUSES.has(status)) return "completed";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canonicalStatusForColumn(col: BoardColumnId): CommissionStatus {
|
export function canonicalStatusForColumn(col: BoardColumnId): CommissionStatus {
|
||||||
return BOARD_COLUMNS[col].canonicalStatus as CommissionStatus;
|
return BOARD_COLUMNS[col].canonicalStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// src/lib/artworks/query.ts
|
// src/lib/artworks/query.ts
|
||||||
import { Prisma } from "@/generated/prisma/client";
|
import type { Prisma } from "@/generated/prisma/client";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export type ArtworkListParams = {
|
export type ArtworkListParams = {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
async function isFirstRun() {
|
async function isFirstRun() {
|
||||||
const count = await prisma.user.count();
|
const count = await prisma.user.count();
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export type SessionWithRole =
|
|||||||
| { user?: { role?: "admin" | "user"; id?: string } }
|
| { user?: { role?: "admin" | "user"; id?: string } }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export type SignUpResponse =
|
export type SignUpResponse = {
|
||||||
| { user?: { id?: string } }
|
user?: { id?: string };
|
||||||
| { data?: { user?: { id?: string }; id?: string } };
|
data?: { user?: { id?: string }; id?: string };
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
export type UserRole = "admin" | "user";
|
||||||
|
|
||||||
export type UsersListRow = {
|
export type UsersListRow = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
email: string;
|
email: string;
|
||||||
role: "admin" | "user";
|
role: UserRole;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { s3 } from "@/lib/s3";
|
import { s3 } from "@/lib/s3";
|
||||||
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import { Readable } from "stream";
|
import type { Readable } from "stream";
|
||||||
|
|
||||||
export async function getImageBufferFromS3Key(s3Key: string): Promise<Buffer> {
|
export async function getImageBufferFromS3Key(s3Key: string): Promise<Buffer> {
|
||||||
const command = new GetObjectCommand({
|
const command = new GetObjectCommand({
|
||||||
Bucket: process.env.BUCKET_NAME!,
|
Bucket: process.env.BUCKET_NAME,
|
||||||
Key: s3Key,
|
Key: s3Key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user