initial commit
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
portfolio-data
|
||||||
1
.env.template
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PAYLOAD_SECRET=123
|
||||||
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
!.env.template
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
portfolio-data/
|
||||||
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM node:24-alpine AS base
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json pnpm-lock.yaml* .npmrc* ./
|
||||||
|
RUN corepack enable pnpm && pnpm i --frozen-lockfile;
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN corepack enable pnpm && pnpm build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
RUN corepack enable pnpm
|
||||||
|
RUN mkdir -p /app/portfolio-data && chown node:node /app/portfolio-data
|
||||||
|
COPY --chown=node --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --chown=node --from=builder /app/public ./public
|
||||||
|
COPY --chown=node --from=builder /app/next.config.ts ./next.config.ts
|
||||||
|
COPY --chown=node --from=builder /app/.next ./.next
|
||||||
|
COPY --chown=node --from=builder /app/package.json ./package.json
|
||||||
|
COPY --chown=node --from=builder /app/tsconfig.json ./tsconfig.json
|
||||||
|
COPY --chown=node --from=builder /app/src ./src
|
||||||
|
USER node
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["pnpm", "start"]
|
||||||
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Liam Pietralla Porfolio
|
||||||
|
|
||||||
|
The portfolio is built using Next.JS and Payload CMS. Payload is running directly in the Next app, and can be accessed by appending /admin to the route.
|
||||||
|
|
||||||
|
Next is currently using a sqlite database and local file storage. Both are output to a `portfolio-data` directory.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To develop the application use pnpm to install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Once downloaded you can use pnpm to run the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payload CMS
|
||||||
|
|
||||||
|
Payload will require rebuilding on the types file once any changes are made:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run payload:generate:types
|
||||||
|
```
|
||||||
|
|
||||||
|
In local mode payload will apply any changes to the config to the database automatically. To generate a migration once changes are made, you can use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run payload:migrate:create
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploying
|
||||||
|
|
||||||
|
Deploying the portfolio is done as a docker container. It can be built with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t liam-portfolio .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Container
|
||||||
|
|
||||||
|
Once the container is built, you can run it with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 3000:3000 -v liam-portfolio-data:/app/portfolio-data -e PAYLOAD_SECRET=your_secret liam-portfolio
|
||||||
|
```
|
||||||
25
eslint.config.mjs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
6
next.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { withPayload } from "@payloadcms/next/withPayload";
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = { };
|
||||||
|
|
||||||
|
export default withPayload(nextConfig);
|
||||||
40
package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "liam-portfolio",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "payload migrate && next start",
|
||||||
|
"lint": "eslint",
|
||||||
|
"payload:generate-types": "payload generate:types",
|
||||||
|
"payload:migrate:create": "payload migrate:create"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@payloadcms/db-sqlite": "^3.53.0",
|
||||||
|
"@payloadcms/next": "^3.53.0",
|
||||||
|
"@payloadcms/richtext-lexical": "^3.53.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"graphql": "^16.11.0",
|
||||||
|
"lucide-react": "^0.541.0",
|
||||||
|
"next": "15.5.0",
|
||||||
|
"payload": "^3.53.0",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"sharp": "^0.34.3",
|
||||||
|
"tailwind-merge": "^3.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.5.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b"
|
||||||
|
}
|
||||||
7373
pnpm-lock.yaml
generated
Normal file
5
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- '@tailwindcss/oxide'
|
||||||
|
- esbuild
|
||||||
|
- sharp
|
||||||
|
- unrs-resolver
|
||||||
5
postcss.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
1
public/file.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/liam_pietralla.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/images/portfolio_project.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
public/images/sole_invoice.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
34
src/app/(app)/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Liam Pietralla",
|
||||||
|
description: "Enthusiastic Software Developer & DevOps Engineer",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
src/app/(app)/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
import IndexLink from "@/components/home-page-link";
|
||||||
|
import Rule from "@/components/horizontal-rule";
|
||||||
|
import { getHome } from "@/services/home-service";
|
||||||
|
import { Mail } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
const IndexPage = async () => {
|
||||||
|
const home = await getHome();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 justify-center items-center h-screen">
|
||||||
|
<Image className="rounded-full" src="/images/liam_pietralla.jpg" width={200} height={200} alt="Liam Pietralla" />
|
||||||
|
<h1 className="text-5xl font-bold">Liam Pietralla</h1>
|
||||||
|
<h2 className="text-xl">Enthusiastic Software Developer & DevOps Engineer</h2>
|
||||||
|
<Rule className="w-[300px]" />
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
{home.mainLinks.map(link => (
|
||||||
|
<IndexLink key={link.id} {...link} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
{home.popoverLinks.map(link => (
|
||||||
|
<IndexLink key={link.id} {...link} isPopover={true} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Rule className="w-[300px]" />
|
||||||
|
<a href="mailto:me@liampietralla.com" className="group leading-relaxed">
|
||||||
|
<span className="flex flex-row gap-2">
|
||||||
|
<Mail />
|
||||||
|
me@liampietralla.com
|
||||||
|
</span>
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexPage;
|
||||||
35
src/app/(app)/projects/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
import ProjectCard from "@/components/project-card";
|
||||||
|
import Rule from "@/components/horizontal-rule";
|
||||||
|
import { getProjects } from "@/services/projects-service";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
const ProjectsPage = async () => {
|
||||||
|
const projects = await getProjects();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 justify-center items-center my-15">
|
||||||
|
<div className="flex flex-row items-center gap-2 my-2">
|
||||||
|
<Image src="/images/liam_pietralla.jpg" width={50} height={50} alt="Liam Pietralla" className="rounded-full" />
|
||||||
|
<Link href="/" className="group leading-relaxed font-semi-bold">
|
||||||
|
Liam Pietralla
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-5xl font-bold">Projects</h1>
|
||||||
|
<h2>A collection of interesting projects that I am working on currently or have worked on in the past.</h2>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{projects.docs.map((project, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<ProjectCard {...project} />
|
||||||
|
{index < projects.docs.length - 1 && <Rule />}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectsPage;
|
||||||
24
src/app/(payload)/admin/[[...segments]]/not-found.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
import config from '@payload-config'
|
||||||
|
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||||
|
import { importMap } from '../importMap'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
params: Promise<{
|
||||||
|
segments: string[]
|
||||||
|
}>
|
||||||
|
searchParams: Promise<{
|
||||||
|
[key: string]: string | string[]
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||||
|
generatePageMetadata({ config, params, searchParams })
|
||||||
|
|
||||||
|
const NotFound = ({ params, searchParams }: Args) =>
|
||||||
|
NotFoundPage({ config, params, searchParams, importMap })
|
||||||
|
|
||||||
|
export default NotFound
|
||||||
24
src/app/(payload)/admin/[[...segments]]/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
import config from '@payload-config'
|
||||||
|
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||||
|
import { importMap } from '../importMap'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
params: Promise<{
|
||||||
|
segments: string[]
|
||||||
|
}>
|
||||||
|
searchParams: Promise<{
|
||||||
|
[key: string]: string | string[]
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||||
|
generatePageMetadata({ config, params, searchParams })
|
||||||
|
|
||||||
|
const Page = ({ params, searchParams }: Args) =>
|
||||||
|
RootPage({ config, params, searchParams, importMap })
|
||||||
|
|
||||||
|
export default Page
|
||||||
5
src/app/(payload)/admin/importMap.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const importMap = {
|
||||||
|
|
||||||
|
}
|
||||||
43
src/app/(payload)/api/[...slug]/route.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import config from '@payload-config'
|
||||||
|
import '@payloadcms/next/css'
|
||||||
|
import {
|
||||||
|
REST_DELETE,
|
||||||
|
REST_GET,
|
||||||
|
REST_OPTIONS,
|
||||||
|
REST_PATCH,
|
||||||
|
REST_POST,
|
||||||
|
REST_PUT,
|
||||||
|
} from '@payloadcms/next/routes'
|
||||||
|
|
||||||
|
type NextCtx = { params: Promise<{ slug?: string[] }> }
|
||||||
|
type PayloadCtx = { params: Promise<{ slug: string[] }> }
|
||||||
|
|
||||||
|
const coerceCtx = (ctx: NextCtx): PayloadCtx => ({
|
||||||
|
params: ctx.params.then(p => ({ slug: p?.slug ?? [] })),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function GET(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_GET(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function POST(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_POST(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DELETE(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_DELETE(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PATCH(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_PATCH(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PUT(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_PUT(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_OPTIONS(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
9
src/app/(payload)/api/graphql-playground/route.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import config from '@payload-config'
|
||||||
|
import '@payloadcms/next/css'
|
||||||
|
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||||
|
|
||||||
|
export async function GET(req: Request): Promise<Response> {
|
||||||
|
return GRAPHQL_PLAYGROUND_GET(config)(req)
|
||||||
|
}
|
||||||
19
src/app/(payload)/api/graphql/route.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import config from '@payload-config'
|
||||||
|
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'
|
||||||
|
|
||||||
|
type NextCtx = { params: Promise<{ slug?: string[] }> }
|
||||||
|
type PayloadCtx = { params: Promise<{ slug: string[] }> }
|
||||||
|
|
||||||
|
const coerceCtx = (ctx: NextCtx): PayloadCtx => ({
|
||||||
|
params: ctx.params.then(p => ({ slug: p?.slug ?? [] })),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function POST(req: Request) {
|
||||||
|
return GRAPHQL_POST(config)(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OPTIONS(req: Request, ctx: NextCtx) {
|
||||||
|
return REST_OPTIONS(config)(req, coerceCtx(ctx))
|
||||||
|
}
|
||||||
0
src/app/(payload)/custom.scss
Normal file
31
src/app/(payload)/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||||
|
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||||
|
import config from '@payload-config'
|
||||||
|
import '@payloadcms/next/css'
|
||||||
|
import type { ServerFunctionClient } from 'payload'
|
||||||
|
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { importMap } from './admin/importMap.js'
|
||||||
|
import './custom.scss'
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverFunction: ServerFunctionClient = async function (args) {
|
||||||
|
'use server'
|
||||||
|
return handleServerFunctions({
|
||||||
|
...args,
|
||||||
|
config,
|
||||||
|
importMap,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const Layout = ({ children }: Args) => (
|
||||||
|
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
|
||||||
|
{children}
|
||||||
|
</RootLayout>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Layout
|
||||||
13
src/app/api/health/route.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export async function GET() {
|
||||||
|
const json = require("../../../../package.json");
|
||||||
|
const response = {
|
||||||
|
"status": "Healthy",
|
||||||
|
"version": json.version
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
BIN
src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
19
src/app/globals.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
27
src/collections/Media.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
export const Media: CollectionConfig = {
|
||||||
|
slug: 'media',
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'alt',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
upload: {
|
||||||
|
staticDir: 'portfolio-data/media',
|
||||||
|
imageSizes: [
|
||||||
|
{
|
||||||
|
name: "thumbnail",
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
position: 'centre',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
adminThumbnail: 'thumbnail',
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/collections/Projects.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { CollectionConfig } from "payload";
|
||||||
|
|
||||||
|
export const Projects: CollectionConfig = {
|
||||||
|
slug: "project",
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
type: "textarea",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "featuredImage",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "media",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tags",
|
||||||
|
type: "array",
|
||||||
|
required: false,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "tag",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "viewLink",
|
||||||
|
type: "text",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repositoryLink",
|
||||||
|
type: "text",
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
src/collections/Users.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
export const Users: CollectionConfig = {
|
||||||
|
slug: 'users',
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'email',
|
||||||
|
},
|
||||||
|
auth: true,
|
||||||
|
fields: [
|
||||||
|
// Email added by default
|
||||||
|
// Add more fields as needed
|
||||||
|
],
|
||||||
|
}
|
||||||
60
src/components/home-page-link.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { DynamicIcon, IconName } from 'lucide-react/dynamic';
|
||||||
|
|
||||||
|
interface HomePageLinkProps {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon: string;
|
||||||
|
id?: string | null;
|
||||||
|
isPopover?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
|
||||||
|
const isRelative = !url.startsWith("http");
|
||||||
|
const dynIcon = <DynamicIcon name={icon as IconName} />
|
||||||
|
|
||||||
|
if (isPopover) {
|
||||||
|
if (isRelative) {
|
||||||
|
return (
|
||||||
|
<div className="relative group">
|
||||||
|
<Link href={url} className="flex items-center space-x-2">
|
||||||
|
{dynIcon}
|
||||||
|
</Link>
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="relative group">
|
||||||
|
<a href={url} className="flex items-center space-x-2">
|
||||||
|
{dynIcon}
|
||||||
|
</a>
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRelative) {
|
||||||
|
return (
|
||||||
|
<Link href={url} className="group leading-relaxed">
|
||||||
|
<span className="flex flex-row gap-2">{dynIcon} {title}</span>
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<a href={url} className="group leading-relaxed">
|
||||||
|
<span className="flex flex-row gap-2">{dynIcon} {title}</span>
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default HomePageLink;
|
||||||
13
src/components/horizontal-rule.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface RuleProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HorizontalRule = ({ className }: RuleProps) => {
|
||||||
|
return (
|
||||||
|
<div className={cn("border-t-1 border-accent", className)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HorizontalRule;
|
||||||
48
src/components/project-card.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import HorizontalRule from "./horizontal-rule";
|
||||||
|
import { Code, ExternalLink } from "lucide-react";
|
||||||
|
import { Project } from "@/payload-types";
|
||||||
|
|
||||||
|
const ProjectCard = (project: Project) => {
|
||||||
|
return (
|
||||||
|
<div className="border border-white rounded-md p-4 flex flex-col gap-2 max-w-[700px] mx-auto">
|
||||||
|
<h2 className="text-2xl font-bold">{project.title}</h2>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{project.featuredImage && typeof project.featuredImage === "object" && (
|
||||||
|
<Image src={project.featuredImage.url!} alt={project.title} width={project.featuredImage.width!} height={project.featuredImage.height!} className="rounded-md border border-gray-600" />
|
||||||
|
)}
|
||||||
|
<p className="whitespace-pre-wrap">{project.description}</p>
|
||||||
|
<div className="flex flex-row gap-2 my-2">
|
||||||
|
{(project?.tags || []).map((tag) => (
|
||||||
|
<span key={tag.id} className="bg-white text-black rounded-md px-1 py-0.5 text-sm hover:bg-gray-200">
|
||||||
|
{tag.tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HorizontalRule />
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
{project.viewLink && (
|
||||||
|
<a href={project.viewLink} target="_blank" className="group">
|
||||||
|
<span className="flex flex-row gap-2">
|
||||||
|
<ExternalLink />
|
||||||
|
View Project
|
||||||
|
</span>
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.repositoryLink && (
|
||||||
|
<a href={project.repositoryLink} target="_blank" className="group">
|
||||||
|
<span className="flex flex-row gap-2">
|
||||||
|
<Code />
|
||||||
|
View Source
|
||||||
|
</span>
|
||||||
|
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectCard;
|
||||||
57
src/globals/home.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { lucideOptions } from "@/lib/lucid-options";
|
||||||
|
import { GlobalConfig } from "payload";
|
||||||
|
|
||||||
|
export const Home: GlobalConfig = {
|
||||||
|
slug: "home",
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "mainLinks",
|
||||||
|
type: "array",
|
||||||
|
required: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
type: "select",
|
||||||
|
options: lucideOptions,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "popoverLinks",
|
||||||
|
type: "array",
|
||||||
|
required: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
type: "select",
|
||||||
|
options: lucideOptions,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
6
src/lib/lucid-options.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const lucideOptions: { label: string, value: string }[] = [
|
||||||
|
{ label: "Code 2", value: "code-2" },
|
||||||
|
{ label: "Notebook", value: "notebook" },
|
||||||
|
{ label: "Github", value: "github" },
|
||||||
|
{ label: "Linkedin", value: "linkedin" },
|
||||||
|
]
|
||||||
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
1183
src/migrations/20250828_224637.json
Normal file
193
src/migrations/20250828_224637.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-sqlite'
|
||||||
|
|
||||||
|
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.run(sql`CREATE TABLE \`users_sessions\` (
|
||||||
|
\`_order\` integer NOT NULL,
|
||||||
|
\`_parent_id\` integer NOT NULL,
|
||||||
|
\`id\` text PRIMARY KEY NOT NULL,
|
||||||
|
\`created_at\` text,
|
||||||
|
\`expires_at\` text NOT NULL,
|
||||||
|
FOREIGN KEY (\`_parent_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`users_sessions_order_idx\` ON \`users_sessions\` (\`_order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`users_sessions_parent_id_idx\` ON \`users_sessions\` (\`_parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`users\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`email\` text NOT NULL,
|
||||||
|
\`reset_password_token\` text,
|
||||||
|
\`reset_password_expiration\` text,
|
||||||
|
\`salt\` text,
|
||||||
|
\`hash\` text,
|
||||||
|
\`login_attempts\` numeric DEFAULT 0,
|
||||||
|
\`lock_until\` text
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`users_updated_at_idx\` ON \`users\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`users_created_at_idx\` ON \`users\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE UNIQUE INDEX \`users_email_idx\` ON \`users\` (\`email\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`media\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`alt\` text NOT NULL,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`url\` text,
|
||||||
|
\`thumbnail_u_r_l\` text,
|
||||||
|
\`filename\` text,
|
||||||
|
\`mime_type\` text,
|
||||||
|
\`filesize\` numeric,
|
||||||
|
\`width\` numeric,
|
||||||
|
\`height\` numeric,
|
||||||
|
\`focal_x\` numeric,
|
||||||
|
\`focal_y\` numeric,
|
||||||
|
\`sizes_thumbnail_url\` text,
|
||||||
|
\`sizes_thumbnail_width\` numeric,
|
||||||
|
\`sizes_thumbnail_height\` numeric,
|
||||||
|
\`sizes_thumbnail_mime_type\` text,
|
||||||
|
\`sizes_thumbnail_filesize\` numeric,
|
||||||
|
\`sizes_thumbnail_filename\` text
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`media_updated_at_idx\` ON \`media\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`media_created_at_idx\` ON \`media\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE UNIQUE INDEX \`media_filename_idx\` ON \`media\` (\`filename\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`media_sizes_thumbnail_sizes_thumbnail_filename_idx\` ON \`media\` (\`sizes_thumbnail_filename\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`project_tags\` (
|
||||||
|
\`_order\` integer NOT NULL,
|
||||||
|
\`_parent_id\` integer NOT NULL,
|
||||||
|
\`id\` text PRIMARY KEY NOT NULL,
|
||||||
|
\`tag\` text NOT NULL,
|
||||||
|
FOREIGN KEY (\`_parent_id\`) REFERENCES \`project\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`project_tags_order_idx\` ON \`project_tags\` (\`_order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`project_tags_parent_id_idx\` ON \`project_tags\` (\`_parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`project\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`title\` text NOT NULL,
|
||||||
|
\`description\` text NOT NULL,
|
||||||
|
\`featured_image_id\` integer,
|
||||||
|
\`view_link\` text,
|
||||||
|
\`repository_link\` text,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
FOREIGN KEY (\`featured_image_id\`) REFERENCES \`media\`(\`id\`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`project_featured_image_idx\` ON \`project\` (\`featured_image_id\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`project_updated_at_idx\` ON \`project\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`project_created_at_idx\` ON \`project\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`payload_locked_documents\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`global_slug\` text,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_global_slug_idx\` ON \`payload_locked_documents\` (\`global_slug\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_updated_at_idx\` ON \`payload_locked_documents\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_created_at_idx\` ON \`payload_locked_documents\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`payload_locked_documents_rels\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`order\` integer,
|
||||||
|
\`parent_id\` integer NOT NULL,
|
||||||
|
\`path\` text NOT NULL,
|
||||||
|
\`users_id\` integer,
|
||||||
|
\`media_id\` integer,
|
||||||
|
\`project_id\` integer,
|
||||||
|
FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_locked_documents\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (\`media_id\`) REFERENCES \`media\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_order_idx\` ON \`payload_locked_documents_rels\` (\`order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_parent_idx\` ON \`payload_locked_documents_rels\` (\`parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_path_idx\` ON \`payload_locked_documents_rels\` (\`path\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_users_id_idx\` ON \`payload_locked_documents_rels\` (\`users_id\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_media_id_idx\` ON \`payload_locked_documents_rels\` (\`media_id\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_locked_documents_rels_project_id_idx\` ON \`payload_locked_documents_rels\` (\`project_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`payload_preferences\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`key\` text,
|
||||||
|
\`value\` text,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_key_idx\` ON \`payload_preferences\` (\`key\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_updated_at_idx\` ON \`payload_preferences\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_created_at_idx\` ON \`payload_preferences\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`payload_preferences_rels\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`order\` integer,
|
||||||
|
\`parent_id\` integer NOT NULL,
|
||||||
|
\`path\` text NOT NULL,
|
||||||
|
\`users_id\` integer,
|
||||||
|
FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_preferences\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_rels_order_idx\` ON \`payload_preferences_rels\` (\`order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_rels_parent_idx\` ON \`payload_preferences_rels\` (\`parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_rels_path_idx\` ON \`payload_preferences_rels\` (\`path\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_preferences_rels_users_id_idx\` ON \`payload_preferences_rels\` (\`users_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`payload_migrations\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`name\` text,
|
||||||
|
\`batch\` numeric,
|
||||||
|
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||||
|
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_migrations_updated_at_idx\` ON \`payload_migrations\` (\`updated_at\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`payload_migrations_created_at_idx\` ON \`payload_migrations\` (\`created_at\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`home_main_links\` (
|
||||||
|
\`_order\` integer NOT NULL,
|
||||||
|
\`_parent_id\` integer NOT NULL,
|
||||||
|
\`id\` text PRIMARY KEY NOT NULL,
|
||||||
|
\`title\` text NOT NULL,
|
||||||
|
\`url\` text NOT NULL,
|
||||||
|
\`icon\` text NOT NULL,
|
||||||
|
FOREIGN KEY (\`_parent_id\`) REFERENCES \`home\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`home_main_links_order_idx\` ON \`home_main_links\` (\`_order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`home_main_links_parent_id_idx\` ON \`home_main_links\` (\`_parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`home_popover_links\` (
|
||||||
|
\`_order\` integer NOT NULL,
|
||||||
|
\`_parent_id\` integer NOT NULL,
|
||||||
|
\`id\` text PRIMARY KEY NOT NULL,
|
||||||
|
\`title\` text NOT NULL,
|
||||||
|
\`url\` text NOT NULL,
|
||||||
|
\`icon\` text NOT NULL,
|
||||||
|
FOREIGN KEY (\`_parent_id\`) REFERENCES \`home\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
await db.run(sql`CREATE INDEX \`home_popover_links_order_idx\` ON \`home_popover_links\` (\`_order\`);`)
|
||||||
|
await db.run(sql`CREATE INDEX \`home_popover_links_parent_id_idx\` ON \`home_popover_links\` (\`_parent_id\`);`)
|
||||||
|
await db.run(sql`CREATE TABLE \`home\` (
|
||||||
|
\`id\` integer PRIMARY KEY NOT NULL,
|
||||||
|
\`updated_at\` text,
|
||||||
|
\`created_at\` text
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||||
|
await db.run(sql`DROP TABLE \`users_sessions\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`users\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`media\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`project_tags\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`project\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`payload_locked_documents\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`payload_locked_documents_rels\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`payload_preferences\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`payload_preferences_rels\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`payload_migrations\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`home_main_links\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`home_popover_links\`;`)
|
||||||
|
await db.run(sql`DROP TABLE \`home\`;`)
|
||||||
|
}
|
||||||
9
src/migrations/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import * as migration_20250828_224637 from './20250828_224637';
|
||||||
|
|
||||||
|
export const migrations = [
|
||||||
|
{
|
||||||
|
up: migration_20250828_224637.up,
|
||||||
|
down: migration_20250828_224637.down,
|
||||||
|
name: '20250828_224637'
|
||||||
|
},
|
||||||
|
];
|
||||||
418
src/payload-types.ts
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by Payload.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||||
|
* and re-run `payload generate:types` to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported timezones in IANA format.
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "supportedTimezones".
|
||||||
|
*/
|
||||||
|
export type SupportedTimezones =
|
||||||
|
| 'Pacific/Midway'
|
||||||
|
| 'Pacific/Niue'
|
||||||
|
| 'Pacific/Honolulu'
|
||||||
|
| 'Pacific/Rarotonga'
|
||||||
|
| 'America/Anchorage'
|
||||||
|
| 'Pacific/Gambier'
|
||||||
|
| 'America/Los_Angeles'
|
||||||
|
| 'America/Tijuana'
|
||||||
|
| 'America/Denver'
|
||||||
|
| 'America/Phoenix'
|
||||||
|
| 'America/Chicago'
|
||||||
|
| 'America/Guatemala'
|
||||||
|
| 'America/New_York'
|
||||||
|
| 'America/Bogota'
|
||||||
|
| 'America/Caracas'
|
||||||
|
| 'America/Santiago'
|
||||||
|
| 'America/Buenos_Aires'
|
||||||
|
| 'America/Sao_Paulo'
|
||||||
|
| 'Atlantic/South_Georgia'
|
||||||
|
| 'Atlantic/Azores'
|
||||||
|
| 'Atlantic/Cape_Verde'
|
||||||
|
| 'Europe/London'
|
||||||
|
| 'Europe/Berlin'
|
||||||
|
| 'Africa/Lagos'
|
||||||
|
| 'Europe/Athens'
|
||||||
|
| 'Africa/Cairo'
|
||||||
|
| 'Europe/Moscow'
|
||||||
|
| 'Asia/Riyadh'
|
||||||
|
| 'Asia/Dubai'
|
||||||
|
| 'Asia/Baku'
|
||||||
|
| 'Asia/Karachi'
|
||||||
|
| 'Asia/Tashkent'
|
||||||
|
| 'Asia/Calcutta'
|
||||||
|
| 'Asia/Dhaka'
|
||||||
|
| 'Asia/Almaty'
|
||||||
|
| 'Asia/Jakarta'
|
||||||
|
| 'Asia/Bangkok'
|
||||||
|
| 'Asia/Shanghai'
|
||||||
|
| 'Asia/Singapore'
|
||||||
|
| 'Asia/Tokyo'
|
||||||
|
| 'Asia/Seoul'
|
||||||
|
| 'Australia/Brisbane'
|
||||||
|
| 'Australia/Sydney'
|
||||||
|
| 'Pacific/Guam'
|
||||||
|
| 'Pacific/Noumea'
|
||||||
|
| 'Pacific/Auckland'
|
||||||
|
| 'Pacific/Fiji';
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
auth: {
|
||||||
|
users: UserAuthOperations;
|
||||||
|
};
|
||||||
|
blocks: {};
|
||||||
|
collections: {
|
||||||
|
users: User;
|
||||||
|
media: Media;
|
||||||
|
project: Project;
|
||||||
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
|
'payload-preferences': PayloadPreference;
|
||||||
|
'payload-migrations': PayloadMigration;
|
||||||
|
};
|
||||||
|
collectionsJoins: {};
|
||||||
|
collectionsSelect: {
|
||||||
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
|
media: MediaSelect<false> | MediaSelect<true>;
|
||||||
|
project: ProjectSelect<false> | ProjectSelect<true>;
|
||||||
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
|
};
|
||||||
|
db: {
|
||||||
|
defaultIDType: number;
|
||||||
|
};
|
||||||
|
globals: {
|
||||||
|
home: Home;
|
||||||
|
};
|
||||||
|
globalsSelect: {
|
||||||
|
home: HomeSelect<false> | HomeSelect<true>;
|
||||||
|
};
|
||||||
|
locale: null;
|
||||||
|
user: User & {
|
||||||
|
collection: 'users';
|
||||||
|
};
|
||||||
|
jobs: {
|
||||||
|
tasks: unknown;
|
||||||
|
workflows: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface UserAuthOperations {
|
||||||
|
forgotPassword: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
login: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
registerFirstUser: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
unlock: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "users".
|
||||||
|
*/
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
email: string;
|
||||||
|
resetPasswordToken?: string | null;
|
||||||
|
resetPasswordExpiration?: string | null;
|
||||||
|
salt?: string | null;
|
||||||
|
hash?: string | null;
|
||||||
|
loginAttempts?: number | null;
|
||||||
|
lockUntil?: string | null;
|
||||||
|
sessions?:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
createdAt?: string | null;
|
||||||
|
expiresAt: string;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
password?: string | null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "media".
|
||||||
|
*/
|
||||||
|
export interface Media {
|
||||||
|
id: number;
|
||||||
|
alt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
url?: string | null;
|
||||||
|
thumbnailURL?: string | null;
|
||||||
|
filename?: string | null;
|
||||||
|
mimeType?: string | null;
|
||||||
|
filesize?: number | null;
|
||||||
|
width?: number | null;
|
||||||
|
height?: number | null;
|
||||||
|
focalX?: number | null;
|
||||||
|
focalY?: number | null;
|
||||||
|
sizes?: {
|
||||||
|
thumbnail?: {
|
||||||
|
url?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
height?: number | null;
|
||||||
|
mimeType?: string | null;
|
||||||
|
filesize?: number | null;
|
||||||
|
filename?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "project".
|
||||||
|
*/
|
||||||
|
export interface Project {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
featuredImage?: (number | null) | Media;
|
||||||
|
tags?:
|
||||||
|
| {
|
||||||
|
tag: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
viewLink?: string | null;
|
||||||
|
repositoryLink?: string | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-locked-documents".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocument {
|
||||||
|
id: number;
|
||||||
|
document?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'users';
|
||||||
|
value: number | User;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'media';
|
||||||
|
value: number | Media;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'project';
|
||||||
|
value: number | Project;
|
||||||
|
} | null);
|
||||||
|
globalSlug?: string | null;
|
||||||
|
user: {
|
||||||
|
relationTo: 'users';
|
||||||
|
value: number | User;
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-preferences".
|
||||||
|
*/
|
||||||
|
export interface PayloadPreference {
|
||||||
|
id: number;
|
||||||
|
user: {
|
||||||
|
relationTo: 'users';
|
||||||
|
value: number | User;
|
||||||
|
};
|
||||||
|
key?: string | null;
|
||||||
|
value?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-migrations".
|
||||||
|
*/
|
||||||
|
export interface PayloadMigration {
|
||||||
|
id: number;
|
||||||
|
name?: string | null;
|
||||||
|
batch?: number | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "users_select".
|
||||||
|
*/
|
||||||
|
export interface UsersSelect<T extends boolean = true> {
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
email?: T;
|
||||||
|
resetPasswordToken?: T;
|
||||||
|
resetPasswordExpiration?: T;
|
||||||
|
salt?: T;
|
||||||
|
hash?: T;
|
||||||
|
loginAttempts?: T;
|
||||||
|
lockUntil?: T;
|
||||||
|
sessions?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
id?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
expiresAt?: T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "media_select".
|
||||||
|
*/
|
||||||
|
export interface MediaSelect<T extends boolean = true> {
|
||||||
|
alt?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
url?: T;
|
||||||
|
thumbnailURL?: T;
|
||||||
|
filename?: T;
|
||||||
|
mimeType?: T;
|
||||||
|
filesize?: T;
|
||||||
|
width?: T;
|
||||||
|
height?: T;
|
||||||
|
focalX?: T;
|
||||||
|
focalY?: T;
|
||||||
|
sizes?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
thumbnail?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
url?: T;
|
||||||
|
width?: T;
|
||||||
|
height?: T;
|
||||||
|
mimeType?: T;
|
||||||
|
filesize?: T;
|
||||||
|
filename?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "project_select".
|
||||||
|
*/
|
||||||
|
export interface ProjectSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
featuredImage?: T;
|
||||||
|
tags?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
tag?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
viewLink?: T;
|
||||||
|
repositoryLink?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-locked-documents_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||||
|
document?: T;
|
||||||
|
globalSlug?: T;
|
||||||
|
user?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-preferences_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||||
|
user?: T;
|
||||||
|
key?: T;
|
||||||
|
value?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-migrations_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||||
|
name?: T;
|
||||||
|
batch?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "home".
|
||||||
|
*/
|
||||||
|
export interface Home {
|
||||||
|
id: number;
|
||||||
|
mainLinks: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon: 'code-2' | 'notebook' | 'github' | 'linkedin';
|
||||||
|
id?: string | null;
|
||||||
|
}[];
|
||||||
|
popoverLinks: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon: 'code-2' | 'notebook' | 'github' | 'linkedin';
|
||||||
|
id?: string | null;
|
||||||
|
}[];
|
||||||
|
updatedAt?: string | null;
|
||||||
|
createdAt?: string | null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "home_select".
|
||||||
|
*/
|
||||||
|
export interface HomeSelect<T extends boolean = true> {
|
||||||
|
mainLinks?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
title?: T;
|
||||||
|
url?: T;
|
||||||
|
icon?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
popoverLinks?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
title?: T;
|
||||||
|
url?: T;
|
||||||
|
icon?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
globalType?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "auth".
|
||||||
|
*/
|
||||||
|
export interface Auth {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare module 'payload' {
|
||||||
|
export interface GeneratedTypes extends Config {}
|
||||||
|
}
|
||||||
39
src/payload.config.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||||
|
import path from 'path'
|
||||||
|
import { buildConfig } from 'payload'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import sharp from 'sharp'
|
||||||
|
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||||
|
|
||||||
|
import { Users } from './collections/Users'
|
||||||
|
import { Media } from './collections/Media'
|
||||||
|
import { Projects } from "./collections/Projects"
|
||||||
|
import { Home } from './globals/home'
|
||||||
|
|
||||||
|
const filename = fileURLToPath(import.meta.url)
|
||||||
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
admin: {
|
||||||
|
user: Users.slug,
|
||||||
|
importMap: {
|
||||||
|
baseDir: path.resolve(dirname),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
collections: [Users, Media, Projects],
|
||||||
|
globals: [Home],
|
||||||
|
editor: lexicalEditor(),
|
||||||
|
secret: process.env.PAYLOAD_SECRET || '',
|
||||||
|
typescript: {
|
||||||
|
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Both our media and db will reside in the 'portfolio-data' directory
|
||||||
|
* We can use a docker volume to persist this data
|
||||||
|
*/
|
||||||
|
db: sqliteAdapter({
|
||||||
|
client: { url: "file:./portfolio-data/data.db" }
|
||||||
|
}),
|
||||||
|
sharp,
|
||||||
|
plugins: [],
|
||||||
|
})
|
||||||
9
src/services/home-service.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import config from '@payload-config'
|
||||||
|
|
||||||
|
export const getHome = async () => {
|
||||||
|
const payload = await getPayload({ config })
|
||||||
|
return await payload.findGlobal({
|
||||||
|
slug: "home"
|
||||||
|
})
|
||||||
|
}
|
||||||
9
src/services/projects-service.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import config from '@payload-config'
|
||||||
|
|
||||||
|
export const getProjects = async () => {
|
||||||
|
const payload = await getPayload({ config })
|
||||||
|
return await payload.find({
|
||||||
|
collection: "project"
|
||||||
|
})
|
||||||
|
}
|
||||||
43
tsconfig.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
],
|
||||||
|
"@payload-config": [
|
||||||
|
"./src/payload.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||