Compare commits

6 Commits

Author SHA1 Message Date
e514d05fd6 Set up infinite scroll n stuff 2025-02-22 13:37:03 +00:00
6eaf1d6b9f user an dchown
All checks were successful
Build and deploy / deploy (push) Successful in 2m38s
2024-12-18 00:22:12 +00:00
ce19237fd2 Try different build
All checks were successful
Build and deploy / deploy (push) Successful in 2m17s
2024-12-17 23:57:10 +00:00
06d75f9d29 Update dockerfile and fly
All checks were successful
Build and deploy / deploy (push) Successful in 2m15s
2024-12-17 20:12:24 +00:00
dc195aecc3 Update registry name
All checks were successful
Build and deploy / deploy (push) Successful in 3m39s
2024-12-17 18:43:32 +00:00
a591fe4157 Fix tag formatting
Some checks failed
Build and deploy / deploy (push) Failing after 1m42s
2024-12-17 18:38:21 +00:00
12 changed files with 501 additions and 463 deletions

View File

@@ -40,12 +40,12 @@ jobs:
context: . context: .
push: true push: true
tags: | tags: |
'gitea.home.joemonk.co.uk/${{ gitea.repository }}:latest' gitea.home.joemonk.co.uk/${{ gitea.repository }}:latest
'gitea.home.joemonk.co.uk/${{ gitea.repository }}:${{ gitea.sha }}' gitea.home.joemonk.co.uk/${{ gitea.repository }}:${{ gitea.sha }}
'registry.fly.io/${{ gitea.repository }}:${{ gitea.sha }}' registry.fly.io/joemonk:${{ gitea.sha }}
- uses: superfly/flyctl-actions/setup-flyctl@master - uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only -i registry.fly.io/${{ gitea.repository }}:${{ gitea.sha }} - run: flyctl deploy --remote-only -i registry.fly.io/joemonk:${{ gitea.sha }}
env: env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

View File

@@ -15,7 +15,7 @@ WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build RUN npm run build
@@ -23,8 +23,8 @@ RUN npm run build
FROM base AS runner FROM base AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV production ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
@@ -39,13 +39,13 @@ 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 COPY --from=builder --chown=nextjs:nodejs /app/db.sql ./db.sql
USER nextjs USER nextjs
EXPOSE 3000 EXPOSE 3000
ENV PORT 3000 ENV PORT=3000
# server.js is created by next build from the standalone output # server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output # https://nextjs.org/docs/pages/api-reference/next-config-js/output

View File

@@ -6,17 +6,15 @@
app = 'joemonk' app = 'joemonk'
primary_region = 'lhr' primary_region = 'lhr'
[build]
[http_service] [http_service]
internal_port = 3000 internal_port = 3000
force_https = true force_https = true
auto_stop_machines = 'stop' auto_stop_machines = 'stop'
auto_start_machines = true auto_start_machines = true
min_machines_running = 0 min_machines_running = 1
processes = ['app'] processes = ["app"]
[[http_service.checks]] [[http_service.checks]]
grace_period = "15s" grace_period = "30s"
interval = "120s" interval = "120s"
method = "GET" method = "GET"
timeout = "5s" timeout = "5s"

View File

@@ -8,7 +8,7 @@ const nextConfig = {
reactCompiler: true, reactCompiler: true,
ppr: "incremental", ppr: "incremental",
}, },
serverExternalPackages: ["typeorm", "better-sqlite3"], serverExternalPackages: ["better-sqlite3"],
reactStrictMode: true, reactStrictMode: true,
output: "standalone", output: "standalone",
images: { images: {

822
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
import { TRPCProvider } from "@/trpc/client";
import RefreshImageDb from "@/components/refresh-image-db";
import { trpc } from "@/trpc/server";
export default async function Photos(): Promise<React.JSX.Element> {
const imageCount = await trpc.photos.count();
return (
<div className="mx-auto">
<TRPCProvider>
<RefreshImageDb count={imageCount}/>
</TRPCProvider>
</div>
);
}

View File

@@ -9,22 +9,7 @@ export default async function Photos(): Promise<React.JSX.Element> {
return ( return (
<div className="mx-auto"> <div className="mx-auto">
<TRPCProvider> <TRPCProvider>
<FilteredLightbox imageData={images}> <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> </TRPCProvider>
</div> </div>
); );

View File

@@ -3,7 +3,7 @@ export default function NavBar(): React.JSX.Element {
<footer className="dark:bg-dracula-bg-darker border-t-2 dark:border-dracula-purple"> <footer className="dark:bg-dracula-bg-darker border-t-2 dark:border-dracula-purple">
<div className="mx-auto max-w-7xl px-4"> <div className="mx-auto max-w-7xl px-4">
<div className="relative flex flex-row-reverse h-12 items-center justify-between"> <div className="relative flex flex-row-reverse h-12 items-center justify-between">
<span className='dark:text-white select-none'>© Joe Monk 2024</span> <span className='dark:text-white select-none'>© Joe Monk 2025</span>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -129,15 +129,15 @@ interface UsernameFormElement extends HTMLFormElement {
export default function FilteredLightbox(props: { export default function FilteredLightbox(props: {
imageData: ImageData[]; imageData: ImageData[];
children: React.JSX.Element[];
}): React.JSX.Element { }): React.JSX.Element {
//const [imageData, setImageData] = useState(props.imageData);
const [imageData] = useState(props.imageData);
const photoQuery = trpc.photos.list.useInfiniteQuery( const photoQuery = trpc.photos.list.useInfiniteQuery(
{ {
limit: 1, limit: 1,
}, },
{ {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
initialData: { initialData: {
pages: [ pages: [
{ {
@@ -151,24 +151,30 @@ export default function FilteredLightbox(props: {
} }
); );
const refreshQuery = trpc.photos.update.useQuery(undefined, { // Call from observable
enabled: false, function loadNextPage(): void {
retry: false, console.log(photoQuery.isFetchingNextPage);
}); console.log(photoQuery.hasNextPage);
if (!photoQuery.isFetchingNextPage) {
void photoQuery.fetchNextPage();
}
}
function handleSubmit(event: React.FormEvent<UsernameFormElement>): void { function handleSubmit(event: React.FormEvent<UsernameFormElement>): void {
event.preventDefault(); event.preventDefault();
// const imageData = props.imageData; // Set filters, then action in the re-render on the data, or pass to photo query?
// setImageData( // setFilters(
// imageData.filter( // imageData.filter(
// (data) => data.src === event.currentTarget.elements.src.value // (data) => data.src === event.currentTarget.elements.src.value
// ) // )
// ); // );
void photoQuery.fetchNextPage();
} }
const children = photoQuery.data.pages const imageData = photoQuery.data.pages
.flatMap((data) => data.data) .flatMap((data) => data.data)
.filter((data) => !!data);
const images = imageData
.map((data) => ( .map((data) => (
<Image <Image
key={data.src} key={data.src}
@@ -182,8 +188,7 @@ export default function FilteredLightbox(props: {
blurDataURL={data.blur} blurDataURL={data.blur}
placeholder="blur" placeholder="blur"
/> />
)) ));
.filter((data) => !!data);
return ( return (
<> <>
@@ -194,16 +199,7 @@ export default function FilteredLightbox(props: {
</div> </div>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
<button <Lightbox imageData={imageData}>{...images}</Lightbox>
onClick={() => {
void refreshQuery.refetch();
}}
>
Refresh
</button>
{refreshQuery.data ? JSON.stringify(refreshQuery.data) : "\nNot"}
{refreshQuery.error ? JSON.stringify(refreshQuery.error) : "\nNo Error"}
<Lightbox imageData={imageData}>{...children}</Lightbox>
</> </>
); );
} }

View File

@@ -0,0 +1,30 @@
"use client";
import React from "react";
import { trpc } from "@/trpc/client";
export default function RefreshImageDb({count}: {count: number}): React.JSX.Element {
const countQuery = trpc.photos.count.useQuery(undefined, {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
initialData: count
});
const refreshQuery = trpc.photos.update.useQuery(undefined, {
enabled: false,
retry: false,
});
return (
<>
{countQuery.data}
<button onClick={() => void refreshQuery.refetch()}>
Refresh image db
</button>
{refreshQuery.data ? JSON.stringify(refreshQuery.data) : "\nNot"}
{refreshQuery.error ? JSON.stringify(refreshQuery.error) : "\nNo Error"}
</>
);
}

View File

@@ -3,6 +3,7 @@ import { createTRPCRouter, privateProcedure, publicProcedure } from '../init';
import { list } from './photos/list'; import { list } from './photos/list';
import { update } from './photos/update'; import { update } from './photos/update';
import { count } from './photos/count';
export const photosRouter = createTRPCRouter({ export const photosRouter = createTRPCRouter({
list: publicProcedure list: publicProcedure
@@ -31,5 +32,6 @@ export const photosRouter = createTRPCRouter({
next next
}; };
}), }),
count: publicProcedure.query(count),
update: privateProcedure.query(update) update: privateProcedure.query(update)
}); });

View File

@@ -0,0 +1,7 @@
import db from "@/db/db";
import { photosTable } from "@/db/schema/photo";
export async function count(): Promise<number> {
const count = await db.$count(photosTable);
return count;
}