Commiting to switch to a different orm
This commit is contained in:
38
.dockerignore
Normal file
38
.dockerignore
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# flyctl launch added from .gitignore
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
**/.pnp.js
|
||||||
|
**/.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# production
|
||||||
|
build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
**/.DS_Store
|
||||||
|
**/*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
**/npm-debug.log*
|
||||||
|
**/yarn-debug.log*
|
||||||
|
**/yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
**/.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
**/.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
**/*.tsbuildinfo
|
||||||
|
**/next-env.d.ts
|
||||||
|
fly.toml
|
||||||
@@ -2,35 +2,50 @@ name: Build and deploy
|
|||||||
run-name: Build and deploy
|
run-name: Build and deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# branches:
|
branches:
|
||||||
# - main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
github-server-url: 'https://gitea.home.joemonk.co.uk'
|
github-server-url: 'https://gitea.home.joemonk.co.uk'
|
||||||
-
|
|
||||||
name: Set up docker
|
- name: Set up docker
|
||||||
run: 'curl -fsSL https://get.docker.com | sh'
|
run: 'curl -fsSL https://get.docker.com | sh'
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
|
||||||
name: Login to private registry
|
- name: Login to gitea registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: 'gitea.home.joemonk.co.uk/${{ github.repository }}'
|
registry: 'gitea.home.joemonk.co.uk/${{ github.repository }}'
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
-
|
|
||||||
name: Build and push
|
- name: Login to flyio registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: 'registry.fly.io'
|
||||||
|
username: x
|
||||||
|
password: ${{ secrets.FLY_API_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: 'gitea.home.joemonk.co.uk/${{ gitea.repository }}:latest'
|
tags: |
|
||||||
|
'gitea.home.joemonk.co.uk/${{ gitea.repository }}:latest'
|
||||||
|
'gitea.home.joemonk.co.uk/${{ gitea.repository }}:${{ gitea.sha }}'
|
||||||
|
'registry.fly.io/${{ gitea.repository }}:${{ gitea.sha }}'
|
||||||
|
|
||||||
|
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||||
|
|
||||||
|
- run: flyctl deploy --remote-only -i registry.fly.io/${{ gitea.repository }}:${{ gitea.sha }}
|
||||||
|
env:
|
||||||
|
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||||
@@ -39,6 +39,7 @@ RUN chown nextjs:nodejs .next
|
|||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder /app/db.sql ./db.sql
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
|
|||||||
30
fly.toml
Normal file
30
fly.toml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# fly.toml app configuration file generated for joemonk on 2024-11-13T18:49:43Z
|
||||||
|
#
|
||||||
|
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||||
|
#
|
||||||
|
|
||||||
|
app = 'joemonk'
|
||||||
|
primary_region = 'lhr'
|
||||||
|
|
||||||
|
[build]
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 3000
|
||||||
|
force_https = true
|
||||||
|
auto_stop_machines = 'stop'
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 0
|
||||||
|
processes = ['app']
|
||||||
|
[[http_service.checks]]
|
||||||
|
grace_period = "15s"
|
||||||
|
interval = "120s"
|
||||||
|
method = "GET"
|
||||||
|
timeout = "5s"
|
||||||
|
path = "/api/status"
|
||||||
|
protocol = "http"
|
||||||
|
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
memory = '1gb'
|
||||||
|
cpu_kind = 'shared'
|
||||||
|
cpus = 1
|
||||||
@@ -6,19 +6,26 @@ const nextConfig = {
|
|||||||
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
|
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
|
||||||
experimental: {
|
experimental: {
|
||||||
reactCompiler: true,
|
reactCompiler: true,
|
||||||
ppr: true,
|
ppr: "incremental",
|
||||||
},
|
},
|
||||||
serverExternalPackages: ["typeorm"],
|
serverExternalPackages: ["typeorm", "better-sqlite3"],
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
hostname: "fly.storage.tigris.dev"
|
hostname: "fly.storage.tigris.dev",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
typescript: {
|
||||||
|
// !! WARN !!
|
||||||
|
// Dangerously allow production builds to successfully complete even if
|
||||||
|
// your project has type errors.
|
||||||
|
// !! WARN !!
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const millionConfig = {
|
const millionConfig = {
|
||||||
@@ -30,4 +37,5 @@ const withMDX = createMDX({
|
|||||||
// Add markdown plugins here, as desired
|
// Add markdown plugins here, as desired
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withMDX(million.next(nextConfig, millionConfig));
|
// export default withMDX(million.next(nextConfig, millionConfig));
|
||||||
|
export default withMDX(nextConfig);
|
||||||
|
|||||||
5331
package-lock.json
generated
5331
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -8,7 +8,7 @@
|
|||||||
"build:analyse": "ANALYZE=true npm run build",
|
"build:analyse": "ANALYZE=true npm run build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"lint:fix": "next lint -- --fix"
|
"lint:fix": "next lint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.663.0",
|
"@aws-sdk/client-s3": "^3.663.0",
|
||||||
@@ -32,12 +32,12 @@
|
|||||||
"framer-motion": "^11.5.6",
|
"framer-motion": "^11.5.6",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"million": "^3.1.11",
|
"million": "^3.1.11",
|
||||||
"next": "^15.0.0-rc.0",
|
"next": "15.0.4-canary.2",
|
||||||
"next-auth": "^5.0.0-beta",
|
"next-auth": "beta",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"radash": "^12.1.0",
|
"radash": "^12.1.0",
|
||||||
"react": "^19.0.0-rc-04bd67a4-20240924",
|
"react": "19.0.0-rc-5c56b873-20241107",
|
||||||
"react-dom": "^19.0.0-rc-04bd67a4-20240924",
|
"react-dom": "19.0.0-rc-5c56b873-20241107",
|
||||||
"react-zoom-pan-pinch": "^3.6.1",
|
"react-zoom-pan-pinch": "^3.6.1",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import { signIn } from "@/lib/auth"
|
import { signIn } from "@/lib/auth";
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
export default function Auth(props: {
|
export default function Auth(props: {
|
||||||
searchParams: { callbackUrl: string | undefined }
|
searchParams: Promise<{ callbackUrl: string | undefined }>
|
||||||
}) {
|
}): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="w-40 mx-auto"
|
className="w-40 mx-auto"
|
||||||
action={async () => {
|
action={async () => {
|
||||||
"use server"
|
"use server";
|
||||||
await signIn("authelia", {
|
await signIn("authelia", {
|
||||||
redirectTo: props.searchParams?.callbackUrl ?? "",
|
redirectTo: (await props.searchParams)?.callbackUrl ?? "",
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
className={`rounded-lg dark:bg-dracula-bg-light transition-colors duration-100 dark:text-white px-2 py-2 font-normal border-transparent`}
|
className={`rounded-lg dark:bg-dracula-bg-light transition-colors duration-100 dark:text-white px-2 py-2 font-normal border-transparent`}
|
||||||
>
|
>
|
||||||
<span>Sign in with Authelia</span>
|
<span>Sign in with Authelia</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import { SessionProvider } from "next-auth/react";
|
|
||||||
|
|
||||||
import NavBar from '@/components/navbar';
|
import NavBar from '@/components/navbar';
|
||||||
import Footer from '@/components/footer';
|
import Footer from '@/components/footer';
|
||||||
import LogIn from "@/components/auth/login";
|
|
||||||
|
|
||||||
import "../globals.css";
|
import "../globals.css";
|
||||||
|
|
||||||
|
|||||||
@@ -11,21 +11,23 @@ export default async function Photos(): Promise<React.JSX.Element> {
|
|||||||
const {data: imageData} = await getImageData();
|
const {data: imageData} = await getImageData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Lightbox imageData={imageData.images}>
|
<div className="mx-auto">
|
||||||
{imageData.images.map((image) => (
|
<Lightbox imageData={imageData.images}>
|
||||||
<Image
|
{imageData.images.map((image) => (
|
||||||
key={image.src}
|
<Image
|
||||||
alt={image.src}
|
key={image.src}
|
||||||
src={image.src}
|
alt={image.src}
|
||||||
className="object-contain h-60 w-80"
|
src={image.src}
|
||||||
sizes="100vw"
|
className="object-contain h-60 w-80"
|
||||||
loading="lazy"
|
sizes="100vw"
|
||||||
width={image.width}
|
loading="lazy"
|
||||||
height={image.height}
|
width={image.width}
|
||||||
blurDataURL={image.blur}
|
height={image.height}
|
||||||
placeholder="blur"
|
blurDataURL={image.blur}
|
||||||
/>
|
placeholder="blur"
|
||||||
))}
|
/>
|
||||||
</Lightbox>
|
))}
|
||||||
|
</Lightbox>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ export async function generateStaticParams(): Promise<{slug: string[]}[]> {
|
|||||||
return slugs;
|
return slugs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Post({params}: {params: { slug: string[] }}): Promise<React.JSX.Element> {
|
export default async function Post({params}: {params: Promise<{ slug: string[] }>}): Promise<React.JSX.Element> {
|
||||||
const mdxFile = await import(`../../../../markdown/posts/[...slug]/${params.slug.join('/')}.mdx`)
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const Post = dynamic(async () => mdxFile);
|
const mdxFile = await import(`../../../../markdown/posts/[...slug]/${(await params).slug.join('/')}.mdx`);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return
|
||||||
|
const Post = dynamic(() => mdxFile);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return (
|
return (
|
||||||
<Post/>
|
<Post/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ async function loadPostDetails(): Promise<postDetails[]> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const loadPostData = posts.map(async (post) => {
|
const loadPostData = posts.map(async (post) => {
|
||||||
const slug = [post.split('/').at(-1)!.slice(0, -4)]
|
const slug = [post.split('/').at(-1)!.slice(0, -4)];
|
||||||
const mdxFile = await import(`../../../../src/markdown/posts/[...slug]/${slug.join('/')}.mdx`)
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const mdxFile = await import(`../../../../src/markdown/posts/[...slug]/${slug.join('/')}.mdx`);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
return {
|
return {
|
||||||
|
link: getCurrentUrl() + '/posts/' + slug.join('/'),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||||
metadata: mdxFile.metadata,
|
metadata: mdxFile.metadata,
|
||||||
link: getCurrentUrl() + '/posts/' + slug.join('/')
|
};
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const postData = await Promise.all(loadPostData);
|
const postData = await Promise.all(loadPostData);
|
||||||
@@ -39,7 +42,7 @@ const getPosts = unstable_cache(
|
|||||||
{
|
{
|
||||||
revalidate: false
|
revalidate: false
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
export default async function Posts(): Promise<React.JSX.Element> {
|
export default async function Posts(): Promise<React.JSX.Element> {
|
||||||
const postDetails = await getPosts();
|
const postDetails = await getPosts();
|
||||||
@@ -58,7 +61,7 @@ export default async function Posts(): Promise<React.JSX.Element> {
|
|||||||
<div key={`${post.link}_${tag}`}>
|
<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>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
@@ -66,7 +69,7 @@ export default async function Posts(): Promise<React.JSX.Element> {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,21 +2,21 @@ import { NextRequest } from "next/server";
|
|||||||
import { handlers } from "@/lib/auth";
|
import { handlers } from "@/lib/auth";
|
||||||
|
|
||||||
const reqWithTrustedOrigin = (req: NextRequest): NextRequest => {
|
const reqWithTrustedOrigin = (req: NextRequest): NextRequest => {
|
||||||
const proto = req.headers.get('x-forwarded-proto')
|
const proto = req.headers.get('x-forwarded-proto');
|
||||||
const host = req.headers.get('x-forwarded-host')
|
const host = req.headers.get('x-forwarded-host');
|
||||||
if (!proto || !host) {
|
if (!proto || !host) {
|
||||||
console.warn("Missing x-forwarded-proto or x-forwarded-host headers.")
|
console.warn("Missing x-forwarded-proto or x-forwarded-host headers.");
|
||||||
return req
|
return req;
|
||||||
}
|
}
|
||||||
const envOrigin = `${proto}://${host}`
|
const envOrigin = `${proto}://${host}`;
|
||||||
const { href, origin } = req.nextUrl
|
const { href, origin } = req.nextUrl;
|
||||||
return new NextRequest(href.replace(origin, envOrigin), req)
|
return new NextRequest(href.replace(origin, envOrigin), req);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const GET = (req: NextRequest) => {
|
export const GET = (req: NextRequest): Promise<Response> => {
|
||||||
return handlers.GET(reqWithTrustedOrigin(req))
|
return handlers.GET(reqWithTrustedOrigin(req));
|
||||||
}
|
};
|
||||||
|
|
||||||
export const POST = (req: NextRequest) => {
|
export const POST = (req: NextRequest): Promise<Response> => {
|
||||||
return handlers.POST(reqWithTrustedOrigin(req))
|
return handlers.POST(reqWithTrustedOrigin(req));
|
||||||
}
|
};
|
||||||
@@ -49,8 +49,8 @@ export async function GET(): Promise<Response> {
|
|||||||
}),
|
}),
|
||||||
title: photo.title ?? undefined,
|
title: photo.title ?? undefined,
|
||||||
description: photo.description ?? undefined
|
description: photo.description ?? undefined
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
return NextResponse.json<GetPhotos>({ status: 200, data: { images } });
|
return NextResponse.json<GetPhotos>({ status: 200, data: { images } });
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ export type GetPhotosUpdate = {
|
|||||||
|
|
||||||
export const GET = auth(async function GET(req): Promise<Response> {
|
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 dataSource = await PhotoDataSource.dataSource;
|
const dataSource = await PhotoDataSource.dataSource;
|
||||||
@@ -35,7 +35,7 @@ export const GET = auth(async function GET(req): Promise<Response> {
|
|||||||
const s3Res = await s3Client.send(listObjCmd);
|
const s3Res = await s3Client.send(listObjCmd);
|
||||||
|
|
||||||
if (!s3Res.Contents) {
|
if (!s3Res.Contents) {
|
||||||
return NextResponse.json({ status: 500 })
|
return NextResponse.json({ status: 500 });
|
||||||
}
|
}
|
||||||
const s3Photos = sift(s3Res.Contents.map((obj) => {
|
const s3Photos = sift(s3Res.Contents.map((obj) => {
|
||||||
if (!obj.Key?.endsWith('/')) {
|
if (!obj.Key?.endsWith('/')) {
|
||||||
@@ -51,7 +51,7 @@ export const GET = auth(async function GET(req): Promise<Response> {
|
|||||||
const getImageCmd = new GetObjectCommand({
|
const getImageCmd = new GetObjectCommand({
|
||||||
Bucket: "joemonk-photos",
|
Bucket: "joemonk-photos",
|
||||||
Key: fileName.replace("https://fly.storage.tigris.dev/joemonk-photos/", "")
|
Key: fileName.replace("https://fly.storage.tigris.dev/joemonk-photos/", "")
|
||||||
})
|
});
|
||||||
const imgRes = await s3Client.send(getImageCmd);
|
const imgRes = await s3Client.send(getImageCmd);
|
||||||
const image = await imgRes.Body?.transformToByteArray();
|
const image = await imgRes.Body?.transformToByteArray();
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ export default async function LogIn(): Promise<React.JSX.Element | undefined> {
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
action={async () => {
|
action={async () => {
|
||||||
"use server"
|
"use server";
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
await signOut({
|
await signOut({
|
||||||
redirectTo: `${getCurrentUrl()}/`
|
redirectTo: `${getCurrentUrl()}/`
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
await signIn("authelia")
|
await signIn("authelia");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -63,21 +63,23 @@ function NextJsImage({ slide, offset, rect, unoptimized = false }: {slide: Image
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MyLightbox({imageData, children}: {imageData: ImageData[], children: React.JSX.Element[]}): React.JSX.Element {
|
export function MyLightbox({imageData, children}: {imageData: ImageData[], children: React.JSX.Element[]}): React.JSX.Element {
|
||||||
const [active, setActive] = useState<number | null>(null);
|
const [active, setActive] = useState<number | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
<div className="flex flex-row flex-wrap justify-center">
|
<div className="flex flex-row flex-wrap justify-center">
|
||||||
{children.map((image, index) => (
|
{children.map((image, index) => {
|
||||||
<button key={`lightbox_img_${index}`} onClick={(() => {
|
return (
|
||||||
setActive(index);
|
<button key={`lightbox_img_${index}`} onClick={(() => {
|
||||||
})}>
|
setActive(index);
|
||||||
<div className="relative">
|
})}>
|
||||||
{image}
|
<div className="relative">
|
||||||
</div>
|
{image}
|
||||||
</button>
|
</div>
|
||||||
))}
|
</button>
|
||||||
|
); }
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<YARL
|
<YARL
|
||||||
open={typeof active === 'number'}
|
open={typeof active === 'number'}
|
||||||
@@ -90,4 +92,41 @@ export default function MyLightbox({imageData, children}: {imageData: ImageData[
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface FormElements extends HTMLFormControlsCollection {
|
||||||
|
src: HTMLInputElement
|
||||||
|
}
|
||||||
|
interface UsernameFormElement extends HTMLFormElement {
|
||||||
|
readonly elements: FormElements
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Test(props: {imageData: ImageData[], children: React.JSX.Element[]}): React.JSX.Element {
|
||||||
|
const [imageData, setImageData] = useState(props.imageData);
|
||||||
|
|
||||||
|
function handleSubmit(event: React.FormEvent<UsernameFormElement>): void {
|
||||||
|
event.preventDefault();
|
||||||
|
const imageData = props.imageData;
|
||||||
|
setImageData(imageData.filter((data) => data.src === event.currentTarget.elements.src.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = imageData.map((data) => props.children.find((child) => {
|
||||||
|
return data.src === child.key ? child : null;
|
||||||
|
})).filter(((data) => !!data));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="src">Src:</label>
|
||||||
|
<input id="src" type="text" />
|
||||||
|
</div>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
<MyLightbox imageData={imageData}>
|
||||||
|
{...children}
|
||||||
|
</MyLightbox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export default function NavBarClient({LogIn, navigation}: NavBarClientProps): Re
|
|||||||
current.current = true;
|
current.current = true;
|
||||||
}
|
}
|
||||||
return nav;
|
return nav;
|
||||||
}, [pathname]);
|
}, [pathname, navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="dark:bg-dracula-bg-darker border-b-2 dark:border-dracula-purple">
|
<nav className="dark:bg-dracula-bg-darker border-b-2 dark:border-dracula-purple">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const defaultNavigation = [
|
|||||||
|
|
||||||
const authedNavigation = [
|
const authedNavigation = [
|
||||||
{ name: 'Manage', href: '/manage', current: false },
|
{ name: 'Manage', href: '/manage', current: false },
|
||||||
]
|
];
|
||||||
|
|
||||||
export default async function NavBar(): Promise<React.JSX.Element> {
|
export default async function NavBar(): Promise<React.JSX.Element> {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function PostHeader({metadata}: PostHeaderProps): React.JSX.Eleme
|
|||||||
<>
|
<>
|
||||||
<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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { Photo } from "./entity/photo";
|
|||||||
|
|
||||||
const dataSource = new DataSource({
|
const dataSource = new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
database: "db.sql",
|
database: `${process.cwd()}/db.sql`,
|
||||||
entities: [Photo],
|
entities: [Photo],
|
||||||
migrations: ["./migrations"],
|
migrations: ["./migrations"],
|
||||||
})
|
});
|
||||||
|
|
||||||
export default class PhotoDataSource {
|
export default class PhotoDataSource {
|
||||||
private static _dataSource: DataSource | null = null;
|
private static _dataSource: DataSource | null = null;
|
||||||
@@ -22,11 +22,11 @@ export default class PhotoDataSource {
|
|||||||
static async initDataSource(): Promise<DataSource> {
|
static async initDataSource(): Promise<DataSource> {
|
||||||
if (!PhotoDataSource._dataSource || !PhotoDataSource._dataSource.isInitialized) {
|
if (!PhotoDataSource._dataSource || !PhotoDataSource._dataSource.isInitialized) {
|
||||||
const ds = await dataSource.initialize();
|
const ds = await dataSource.initialize();
|
||||||
console.log('Photo data source initialized')
|
console.log('Photo data source initialized');
|
||||||
PhotoDataSource._dataSource = ds;
|
PhotoDataSource._dataSource = ds;
|
||||||
}
|
}
|
||||||
return PhotoDataSource._dataSource;
|
return PhotoDataSource._dataSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoDataSource.initDataSource();
|
await PhotoDataSource.initDataSource();
|
||||||
@@ -1,48 +1,48 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Photo {
|
export class Photo {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: number
|
id!: number;
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
@Column("text", { unique: true })
|
||||||
src!: string;
|
src!: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
width!: number
|
width!: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
height!: number
|
height!: number;
|
||||||
|
|
||||||
@Column("blob")
|
@Column("blob")
|
||||||
blur!: string
|
blur!: string;
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
camera: string | null = null;
|
camera: string | null = null;
|
||||||
|
|
||||||
// Manually input data
|
// Manually input data
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
title: string | null = null;
|
title: string | null = null;
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
description: string | null = null;
|
description: string | null = null;
|
||||||
|
|
||||||
// Exif data
|
// Exif data
|
||||||
@Column("int", { nullable: true })
|
@Column("int", { nullable: true })
|
||||||
exposureBiasValue: number | null = null
|
exposureBiasValue: number | null = null;
|
||||||
|
|
||||||
@Column("float", { nullable: true })
|
@Column("float", { nullable: true })
|
||||||
fNumber: number | null = null
|
fNumber: number | null = null;
|
||||||
|
|
||||||
@Column("int", { nullable: true })
|
@Column("int", { nullable: true })
|
||||||
isoSpeedRatings: number | null = null
|
isoSpeedRatings: number | null = null;
|
||||||
|
|
||||||
@Column("int", { nullable: true })
|
@Column("int", { nullable: true })
|
||||||
focalLength: number | null = null
|
focalLength: number | null = null;
|
||||||
|
|
||||||
@Column("date", { nullable: true })
|
@Column("date", { nullable: true })
|
||||||
dateTimeOriginal: Date | null = null
|
dateTimeOriginal: Date | null = null;
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
lensModel: string | null = null
|
lensModel: string | null = null;
|
||||||
}
|
}
|
||||||
@@ -13,4 +13,4 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
|||||||
}],
|
}],
|
||||||
trustHost: true,
|
trustHost: true,
|
||||||
redirectProxyUrl: `${getCurrentUrl()}/api/auth`,
|
redirectProxyUrl: `${getCurrentUrl()}/api/auth`,
|
||||||
})
|
});
|
||||||
Reference in New Issue
Block a user