Move to drizzle
This commit is contained in:
10
drizzle.config.ts
Normal file
10
drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
out: './drizzle',
|
||||||
|
schema: './src/db/schema',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
dbCredentials: {
|
||||||
|
url: `${process.cwd()}/db.sql`,
|
||||||
|
},
|
||||||
|
});
|
||||||
2106
package-lock.json
generated
2106
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -11,30 +11,33 @@
|
|||||||
"lint:fix": "next lint --fix"
|
"lint:fix": "next lint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.663.0",
|
"@aws-sdk/client-s3": "^3.691.0",
|
||||||
"@heroicons/react": "^2.1.5",
|
"@heroicons/react": "^2.1.5",
|
||||||
"@mdx-js/loader": "^3.0.1",
|
"@mdx-js/loader": "^3.0.1",
|
||||||
"@mdx-js/react": "^3.0.1",
|
"@mdx-js/react": "^3.0.1",
|
||||||
"@next/bundle-analyzer": "^14.2.13",
|
"@next/bundle-analyzer": "^14.2.18",
|
||||||
"@next/mdx": "^14.2.13",
|
"@next/mdx": "^14.2.18",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@types/better-sqlite3": "^7.6.11",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "^22.6.1",
|
"@types/node": "^22.6.1",
|
||||||
"@types/react": "^18.3.9",
|
"@types/react": "^18.3.9",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"babel-plugin-react-compiler": "^0.0.0-experimental-6067d4e-20240924",
|
"babel-plugin-react-compiler": "^0.0.0-experimental-6067d4e-20240924",
|
||||||
"better-sqlite3": "^9.6.0",
|
"better-sqlite3": "^11.5.0",
|
||||||
|
"drizzle-kit": "^0.28.1",
|
||||||
|
"drizzle-orm": "^0.36.1",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
"eslint-config-next": "^15.0.0-rc.0",
|
"eslint-config-next": "^15.0.0-rc.0",
|
||||||
"exif-reader": "^2.0.1",
|
"exif-reader": "^2.0.1",
|
||||||
"framer-motion": "^11.5.6",
|
"framer-motion": "^11.11.15",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"million": "^3.1.11",
|
"million": "^3.1.11",
|
||||||
"next": "15.0.4-canary.2",
|
"next": "15.0.4-canary.2",
|
||||||
"next-auth": "beta",
|
"next-auth": "beta",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.49",
|
||||||
"radash": "^12.1.0",
|
"radash": "^12.1.0",
|
||||||
"react": "19.0.0-rc-5c56b873-20241107",
|
"react": "19.0.0-rc-5c56b873-20241107",
|
||||||
"react-dom": "19.0.0-rc-5c56b873-20241107",
|
"react-dom": "19.0.0-rc-5c56b873-20241107",
|
||||||
@@ -44,7 +47,6 @@
|
|||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"typeorm": "^0.3.20",
|
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"yet-another-react-lightbox": "^3.21.6"
|
"yet-another-react-lightbox": "^3.21.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { shake } from "radash";
|
import { shake } from "radash";
|
||||||
import PhotoDataSource from "@/data-source";
|
import db from "@/db/db";
|
||||||
import { Photo } from "@/entity/photo";
|
import { photosTable } from "@/db/schema/photo";
|
||||||
|
|
||||||
export type ImageData = {
|
export type ImageData = {
|
||||||
width: number,
|
width: number,
|
||||||
@@ -29,9 +29,7 @@ export type GetPhotos = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(): Promise<Response> {
|
export async function GET(): Promise<Response> {
|
||||||
const dataSource = await PhotoDataSource.dataSource;
|
const currentSources = await db.select().from(photosTable);
|
||||||
const photoRepository = dataSource.getRepository(Photo);
|
|
||||||
const currentSources = await photoRepository.find();
|
|
||||||
const images = currentSources.map((photo) => {
|
const images = currentSources.map((photo) => {
|
||||||
return {
|
return {
|
||||||
width: photo.width,
|
width: photo.width,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { NextResponse } from "next/server";
|
|||||||
import { diff, sift } from "radash";
|
import { diff, sift } from "radash";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
|
|
||||||
import PhotoDataSource from "@/data-source";
|
import db from "@/db/db";
|
||||||
import { Photo } from "@/entity/photo";
|
import { photosTable } from "@/db/schema/photo";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
export type GetPhotosUpdate = {
|
export type GetPhotosUpdate = {
|
||||||
@@ -17,16 +17,13 @@ export const GET = auth(async function GET(req): Promise<Response> {
|
|||||||
if (!req.auth) {
|
if (!req.auth) {
|
||||||
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
|
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
const photos = await db.select().from(photosTable);
|
||||||
|
const currentSources = photos.map((photo) => photo.src);
|
||||||
|
|
||||||
const dataSource = await PhotoDataSource.dataSource;
|
const s3Client = new S3Client({
|
||||||
const photoRepository = dataSource.getRepository(Photo);
|
region: "auto",
|
||||||
const currentSources = (await photoRepository.find({
|
endpoint: `https://fly.storage.tigris.dev`,
|
||||||
select: {
|
});
|
||||||
src: true
|
|
||||||
}
|
|
||||||
})).map((photo) => photo.src);
|
|
||||||
|
|
||||||
const s3Client = new S3Client();
|
|
||||||
|
|
||||||
const listObjCmd = new ListObjectsV2Command({
|
const listObjCmd = new ListObjectsV2Command({
|
||||||
Bucket: "joemonk-photos"
|
Bucket: "joemonk-photos"
|
||||||
@@ -47,6 +44,10 @@ export const GET = auth(async function GET(req): Promise<Response> {
|
|||||||
|
|
||||||
const newPhotos = diff(s3Photos, currentSources);
|
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 imageData = newPhotos.map(async (fileName: string) => {
|
||||||
const getImageCmd = new GetObjectCommand({
|
const getImageCmd = new GetObjectCommand({
|
||||||
Bucket: "joemonk-photos",
|
Bucket: "joemonk-photos",
|
||||||
@@ -61,26 +62,27 @@ export const GET = auth(async function GET(req): Promise<Response> {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
const exifData = exif ? exifReader(exif) : undefined;
|
const exifData = exif ? exifReader(exif) : undefined;
|
||||||
|
|
||||||
const photo = new Photo();
|
const photo: typeof photosTable.$inferInsert = {
|
||||||
photo.src = fileName;
|
src: fileName,
|
||||||
photo.width = width ?? 10;
|
width: width ?? 10,
|
||||||
photo.height = height ?? 10;
|
height: height ?? 10,
|
||||||
photo.blur = `data:image/jpeg;base64,${blur.toString('base64')}` as `data:image/${string}`;
|
blur: `data:image/jpeg;base64,${blur.toString('base64')}` as `data:image/${string}`,
|
||||||
photo.camera = exifData?.Image?.Model ?? null;
|
camera: exifData?.Image?.Model ?? null,
|
||||||
|
|
||||||
photo.exposureBiasValue = exifData?.Photo?.ExposureBiasValue ?? null;
|
exposureBiasValue: exifData?.Photo?.ExposureBiasValue ?? null,
|
||||||
photo.fNumber = exifData?.Photo?.FNumber ?? null;
|
fNumber: exifData?.Photo?.FNumber ?? null,
|
||||||
photo.isoSpeedRatings = exifData?.Photo?.ISOSpeedRatings ?? null;
|
isoSpeedRatings: exifData?.Photo?.ISOSpeedRatings ?? null,
|
||||||
photo.focalLength = exifData?.Photo?.FocalLength ?? null;
|
focalLength: exifData?.Photo?.FocalLength ?? null,
|
||||||
photo.dateTimeOriginal = exifData?.Photo?.DateTimeOriginal ?? null;
|
dateTimeOriginal: exifData?.Photo?.DateTimeOriginal ?? null,
|
||||||
photo.lensModel = exifData?.Photo?.LensModel ?? null;
|
lensModel: exifData?.Photo?.LensModel ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
return photo;
|
return photo;
|
||||||
});
|
});
|
||||||
|
|
||||||
const images = await Promise.all(imageData);
|
const images = await Promise.all(imageData);
|
||||||
|
|
||||||
await photoRepository.save(images);
|
await db.insert(photosTable).values(images);
|
||||||
|
|
||||||
return NextResponse.json<GetPhotosUpdate>({ status: 200, s3Photos: newPhotos });
|
return NextResponse.json<GetPhotosUpdate>({ status: 200, s3Photos: newPhotos });
|
||||||
});
|
});
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { DataSource } from "typeorm";
|
|
||||||
import { Photo } from "./entity/photo";
|
|
||||||
|
|
||||||
const dataSource = new DataSource({
|
|
||||||
type: "better-sqlite3",
|
|
||||||
database: `${process.cwd()}/db.sql`,
|
|
||||||
entities: [Photo],
|
|
||||||
migrations: ["./migrations"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class PhotoDataSource {
|
|
||||||
private static _dataSource: DataSource | null = null;
|
|
||||||
|
|
||||||
static get dataSource(): Promise<DataSource> {
|
|
||||||
if (PhotoDataSource._dataSource === null) {
|
|
||||||
return PhotoDataSource.initDataSource();
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(PhotoDataSource._dataSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async initDataSource(): Promise<DataSource> {
|
|
||||||
if (!PhotoDataSource._dataSource || !PhotoDataSource._dataSource.isInitialized) {
|
|
||||||
const ds = await dataSource.initialize();
|
|
||||||
console.log('Photo data source initialized');
|
|
||||||
PhotoDataSource._dataSource = ds;
|
|
||||||
}
|
|
||||||
return PhotoDataSource._dataSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await PhotoDataSource.initDataSource();
|
|
||||||
3
src/db/db.ts
Normal file
3
src/db/db.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
|
||||||
|
export default drizzle(`${process.cwd()}/db.sql`);
|
||||||
22
src/db/schema/photo.ts
Normal file
22
src/db/schema/photo.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { int, sqliteTable, text, blob, real } from "drizzle-orm/sqlite-core";
|
||||||
|
|
||||||
|
export const photosTable = sqliteTable(
|
||||||
|
"photo",
|
||||||
|
{
|
||||||
|
id: int().primaryKey({ autoIncrement: true }),
|
||||||
|
src: text().notNull().unique(),
|
||||||
|
width: int().notNull(),
|
||||||
|
height: int().notNull(),
|
||||||
|
blur: blob().notNull(),
|
||||||
|
|
||||||
|
camera: text(),
|
||||||
|
title: text(),
|
||||||
|
description: text(),
|
||||||
|
exposureBiasValue: int(),
|
||||||
|
fNumber: real(),
|
||||||
|
isoSpeedRatings: int(),
|
||||||
|
focalLength: int(),
|
||||||
|
dateTimeOriginal: int({ mode: 'timestamp' }),
|
||||||
|
lensModel: text(),
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class Photo {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id!: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
src!: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
width!: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
height!: number;
|
|
||||||
|
|
||||||
@Column("blob")
|
|
||||||
blur!: string;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
camera: string | null = null;
|
|
||||||
|
|
||||||
// Manually input data
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
title: string | null = null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
description: string | null = null;
|
|
||||||
|
|
||||||
// Exif data
|
|
||||||
@Column("int", { nullable: true })
|
|
||||||
exposureBiasValue: number | null = null;
|
|
||||||
|
|
||||||
@Column("float", { nullable: true })
|
|
||||||
fNumber: number | null = null;
|
|
||||||
|
|
||||||
@Column("int", { nullable: true })
|
|
||||||
isoSpeedRatings: number | null = null;
|
|
||||||
|
|
||||||
@Column("int", { nullable: true })
|
|
||||||
focalLength: number | null = null;
|
|
||||||
|
|
||||||
@Column("date", { nullable: true })
|
|
||||||
dateTimeOriginal: Date | null = null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
lensModel: string | null = null;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user