Lint and TRPC
This commit is contained in:
@@ -1,33 +1,31 @@
|
||||
import Image from "next/image";
|
||||
import FilteredLightbox from "@/components/lightbox";
|
||||
import { type GetPhotos } from "@/app/api/photos/route";
|
||||
|
||||
async function getImageData(): Promise<GetPhotos> {
|
||||
const res = await fetch(`http://localhost:3000/api/photos`, { next: { revalidate: false, tags: ['photos'] } });
|
||||
return res.json() as Promise<GetPhotos>;
|
||||
}
|
||||
import { trpc } from "@/trpc/server";
|
||||
import { TRPCProvider } from "@/trpc/client";
|
||||
|
||||
export default async function Photos(): Promise<React.JSX.Element> {
|
||||
const {data: imageData} = await getImageData();
|
||||
|
||||
const { data: images } = await trpc.photos.list();
|
||||
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<FilteredLightbox imageData={imageData.images}>
|
||||
{imageData.images.map((image) => (
|
||||
<Image
|
||||
key={image.src}
|
||||
alt={image.src}
|
||||
src={image.src}
|
||||
className="object-contain h-60 w-80"
|
||||
sizes="100vw"
|
||||
loading="lazy"
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
blurDataURL={image.blur}
|
||||
placeholder="blur"
|
||||
/>
|
||||
))}
|
||||
</FilteredLightbox>
|
||||
<TRPCProvider>
|
||||
<FilteredLightbox imageData={images}>
|
||||
{images.map((image) => (
|
||||
<Image
|
||||
key={image.src}
|
||||
alt={image.src}
|
||||
src={image.src}
|
||||
className="object-contain h-60 w-80"
|
||||
sizes="100vw"
|
||||
loading="lazy"
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
blurDataURL={image.blur}
|
||||
placeholder="blur"
|
||||
/>
|
||||
))}
|
||||
</FilteredLightbox>
|
||||
</TRPCProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
import { glob } from "glob";
|
||||
import dynamic from "next/dynamic";
|
||||
import dynamic, { LoaderComponent } from "next/dynamic";
|
||||
import React from "react";
|
||||
|
||||
export const dynamicParams = false;
|
||||
|
||||
export async function generateStaticParams(): Promise<{slug: string[]}[]> {
|
||||
const posts = await glob(`${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`, {
|
||||
nodir: true,
|
||||
});
|
||||
export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
|
||||
const posts = await glob(
|
||||
`${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`,
|
||||
{
|
||||
nodir: true,
|
||||
}
|
||||
);
|
||||
|
||||
const slugs = posts.map((post) => ({
|
||||
slug: [post.split('/').at(-1)!.slice(0, -4)]
|
||||
slug: [post.split("/").at(-1)!.slice(0, -4)],
|
||||
}));
|
||||
|
||||
return slugs;
|
||||
}
|
||||
|
||||
export default async function Post({params}: {params: Promise<{ slug: string[] }>}): Promise<React.JSX.Element> {
|
||||
const mdxFile = await import(`../../../../markdown/posts/[...slug]/${(await params).slug.join('/')}.mdx`);
|
||||
export default async function Post({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>;
|
||||
}): Promise<React.JSX.Element> {
|
||||
const mdxFile = await import(
|
||||
`../../../../markdown/posts/[...slug]/${(await params).slug.join("/")}.mdx`
|
||||
) as LoaderComponent<unknown>;
|
||||
const Post = dynamic(() => mdxFile);
|
||||
return (
|
||||
<Post/>
|
||||
);
|
||||
return <Post />;
|
||||
}
|
||||
|
||||
@@ -4,27 +4,32 @@ import { unstable_cache } from "next/cache";
|
||||
import Link from "next/link";
|
||||
|
||||
type postDetails = {
|
||||
link: string,
|
||||
link: string;
|
||||
metadata: {
|
||||
title: string,
|
||||
date: string,
|
||||
coverImage: string,
|
||||
blurb: string,
|
||||
shortBlurb: string,
|
||||
tags: string[]
|
||||
}
|
||||
}
|
||||
title: string;
|
||||
date: string;
|
||||
coverImage: string;
|
||||
blurb: string;
|
||||
shortBlurb: string;
|
||||
tags: string[];
|
||||
};
|
||||
};
|
||||
|
||||
async function loadPostDetails(): Promise<postDetails[]> {
|
||||
const posts = await glob(`${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`, {
|
||||
nodir: true,
|
||||
});
|
||||
const posts = await glob(
|
||||
`${process.cwd()}/src/markdown/posts/[[]...slug[]]/**/*.mdx`,
|
||||
{
|
||||
nodir: true,
|
||||
}
|
||||
);
|
||||
|
||||
const loadPostData = posts.map(async (post) => {
|
||||
const slug = [post.split('/').at(-1)!.slice(0, -4)];
|
||||
const mdxFile = await import(`../../../../src/markdown/posts/[...slug]/${slug.join('/')}.mdx`);
|
||||
const slug = [post.split("/").at(-1)!.slice(0, -4)];
|
||||
const mdxFile = await import(
|
||||
`../../../../src/markdown/posts/[...slug]/${slug.join("/")}.mdx`
|
||||
) as postDetails;
|
||||
return {
|
||||
link: getCurrentUrl() + '/posts/' + slug.join('/'),
|
||||
link: getCurrentUrl() + "/posts/" + slug.join("/"),
|
||||
metadata: mdxFile.metadata,
|
||||
};
|
||||
});
|
||||
@@ -33,13 +38,9 @@ async function loadPostDetails(): Promise<postDetails[]> {
|
||||
return postData;
|
||||
}
|
||||
|
||||
const getPosts = unstable_cache(
|
||||
loadPostDetails,
|
||||
['posts'],
|
||||
{
|
||||
revalidate: false
|
||||
}
|
||||
);
|
||||
const getPosts = unstable_cache(loadPostDetails, ["posts"], {
|
||||
revalidate: false,
|
||||
});
|
||||
|
||||
export default async function Posts(): Promise<React.JSX.Element> {
|
||||
const postDetails = await getPosts();
|
||||
@@ -56,14 +57,14 @@ export default async function Posts(): Promise<React.JSX.Element> {
|
||||
{post.metadata.tags.map((tag) => {
|
||||
return (
|
||||
<div key={`${post.link}_${tag}`}>
|
||||
<span className="select-none text-sm me-2 px-2.5 py-1 rounded border border-dracula-pink dark:bg-dracula-bg-darker dark:text-dracula-pink">{tag}</span>
|
||||
<span className="select-none text-sm me-2 px-2.5 py-1 rounded border border-dracula-pink dark:bg-dracula-bg-darker dark:text-dracula-pink">
|
||||
{tag}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p>
|
||||
{post.metadata.blurb}
|
||||
</p>
|
||||
<p>{post.metadata.blurb}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { shake } from "radash";
|
||||
import db from "@/db/db";
|
||||
import { photosTable } from "@/db/schema/photo";
|
||||
|
||||
export type ImageData = {
|
||||
width: number,
|
||||
height: number,
|
||||
blur: `data:image/${string}`,
|
||||
src: string,
|
||||
camera?: string,
|
||||
exif: Partial<{
|
||||
ExposureBiasValue: number,
|
||||
FNumber: number,
|
||||
ISOSpeedRatings: number,
|
||||
FocalLength: number,
|
||||
DateTimeOriginal: Date,
|
||||
LensModel: string
|
||||
}>,
|
||||
title?: string,
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type GetPhotos = {
|
||||
status: number,
|
||||
data: {
|
||||
images: ImageData[]
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(): Promise<Response> {
|
||||
const currentSources = await db.select().from(photosTable);
|
||||
const images = currentSources.map((photo) => {
|
||||
return {
|
||||
width: photo.width,
|
||||
height: photo.height,
|
||||
blur: photo.blur as `data:image/${string}`,
|
||||
src: photo.src,
|
||||
camera: photo.camera ?? undefined,
|
||||
exif: shake({
|
||||
ExposureBiasValue: photo.exposureBiasValue,
|
||||
FNumber: photo.fNumber,
|
||||
ISOSpeedRatings: photo.isoSpeedRatings,
|
||||
FocalLength: photo.focalLength,
|
||||
DateTimeOriginal: photo.dateTimeOriginal,
|
||||
LensModel: photo.lensModel
|
||||
}),
|
||||
title: photo.title ?? undefined,
|
||||
description: photo.description ?? undefined
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json<GetPhotos>({ status: 200, data: { images } });
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { S3Client, ListObjectsV2Command, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||
import exifReader from "exif-reader";
|
||||
import { NextResponse } from "next/server";
|
||||
import { diff, sift } from "radash";
|
||||
import sharp from "sharp";
|
||||
|
||||
import db from "@/db/db";
|
||||
import { photosTable } from "@/db/schema/photo";
|
||||
import { auth } from "@/lib/auth";
|
||||
|
||||
export type GetPhotosUpdate = {
|
||||
status: number,
|
||||
s3Photos: string[]
|
||||
}
|
||||
|
||||
export const GET = auth(async function GET(req): Promise<Response> {
|
||||
if (!req.auth) {
|
||||
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
|
||||
}
|
||||
const photos = await db.select().from(photosTable);
|
||||
const currentSources = photos.map((photo) => photo.src);
|
||||
|
||||
const s3Client = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: `https://fly.storage.tigris.dev`,
|
||||
});
|
||||
|
||||
const listObjCmd = new ListObjectsV2Command({
|
||||
Bucket: "joemonk-photos"
|
||||
});
|
||||
|
||||
const s3Res = await s3Client.send(listObjCmd);
|
||||
|
||||
if (!s3Res.Contents) {
|
||||
return NextResponse.json({ status: 500 });
|
||||
}
|
||||
const s3Photos = sift(s3Res.Contents.map((obj) => {
|
||||
if (!obj.Key?.endsWith('/')) {
|
||||
return `https://fly.storage.tigris.dev/joemonk-photos/${obj.Key}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
|
||||
const newPhotos = diff(s3Photos, currentSources);
|
||||
|
||||
if (newPhotos.length === 0) {
|
||||
return NextResponse.json<GetPhotosUpdate>({ status: 200, s3Photos: newPhotos });
|
||||
}
|
||||
|
||||
const imageData = newPhotos.map(async (fileName: string) => {
|
||||
const getImageCmd = new GetObjectCommand({
|
||||
Bucket: "joemonk-photos",
|
||||
Key: fileName.replace("https://fly.storage.tigris.dev/joemonk-photos/", "")
|
||||
});
|
||||
const imgRes = await s3Client.send(getImageCmd);
|
||||
const image = await imgRes.Body?.transformToByteArray();
|
||||
|
||||
const { width, height, exif } = await sharp(image).metadata();
|
||||
const blur = await sharp(image)
|
||||
.resize({ width: 12, height: 12, fit: 'inside' })
|
||||
.toBuffer();
|
||||
const exifData = exif ? exifReader(exif) : undefined;
|
||||
|
||||
const photo: typeof photosTable.$inferInsert = {
|
||||
src: fileName,
|
||||
width: width ?? 10,
|
||||
height: height ?? 10,
|
||||
blur: `data:image/jpeg;base64,${blur.toString('base64')}` as `data:image/${string}`,
|
||||
camera: exifData?.Image?.Model ?? null,
|
||||
|
||||
exposureBiasValue: exifData?.Photo?.ExposureBiasValue ?? null,
|
||||
fNumber: exifData?.Photo?.FNumber ?? null,
|
||||
isoSpeedRatings: exifData?.Photo?.ISOSpeedRatings ?? null,
|
||||
focalLength: exifData?.Photo?.FocalLength ?? null,
|
||||
dateTimeOriginal: exifData?.Photo?.DateTimeOriginal ?? null,
|
||||
lensModel: exifData?.Photo?.LensModel ?? null,
|
||||
};
|
||||
|
||||
return photo;
|
||||
});
|
||||
|
||||
const images = await Promise.all(imageData);
|
||||
|
||||
await db.insert(photosTable).values(images);
|
||||
|
||||
return NextResponse.json<GetPhotosUpdate>({ status: 200, s3Photos: newPhotos });
|
||||
});
|
||||
11
src/app/api/trpc/[trpc]/route.ts
Normal file
11
src/app/api/trpc/[trpc]/route.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
||||
import { createTRPCContext } from '@/trpc/init';
|
||||
import { appRouter } from '@/trpc/routers/_app';
|
||||
const handler = (req: Request): Promise<Response> =>
|
||||
fetchRequestHandler({
|
||||
endpoint: '/api/trpc',
|
||||
req,
|
||||
router: appRouter,
|
||||
createContext: createTRPCContext,
|
||||
});
|
||||
export { handler as GET, handler as POST };
|
||||
Reference in New Issue
Block a user