refactor: large refactor around monorepo
Just a commit point while I'm testing stuff. Already decided at this point to simplify and revert away from PayloadCMS.
@@ -0,0 +1,38 @@
|
||||
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'),
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
ignoreRestSiblings: false,
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^(_|ignore)',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['.next/'],
|
||||
},
|
||||
]
|
||||
|
||||
export default eslintConfig
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,31 @@
|
||||
import { withPayload } from "@payloadcms/next/withPayload";
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
output: "standalone",
|
||||
basePath: "/admin",
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "img.walters.to",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "img.xevion.dev",
|
||||
},
|
||||
],
|
||||
},
|
||||
webpack: (webpackConfig) => {
|
||||
webpackConfig.resolve.extensionAlias = {
|
||||
".cjs": [".cts", ".cjs"],
|
||||
".js": [".ts", ".tsx", ".js", ".jsx"],
|
||||
".mjs": [".mts", ".mjs"],
|
||||
};
|
||||
|
||||
return webpackConfig;
|
||||
},
|
||||
};
|
||||
|
||||
export default withPayload(config, { devBundleServerPackages: false });
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@xevion/payload",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "PayloadCMS admin for xevion.dev",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_OPTIONS=\"--no-deprecation --max-old-space-size=8000\" next build",
|
||||
"deploy": "echo 'Use Docker deployment'",
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev --port 5001",
|
||||
"devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
|
||||
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
|
||||
"generate:schema": "pnpm run generate:schema:payload && pnpm run generate:schema:sync",
|
||||
"generate:schema:payload": "cross-env NODE_OPTIONS=--no-deprecation payload generate:db-schema",
|
||||
"generate:schema:sync": "cp src/payload-generated-schema.ts ../../packages/db/src/schema.ts && pnpm --filter @xevion/db build",
|
||||
"ii": "pnpm install --ignore-workspace",
|
||||
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"preview": "cross-env NODE_OPTIONS=--no-deprecation next start --port 5001",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start --port 5001",
|
||||
"test": "pnpm run test:int && pnpm run test:e2e",
|
||||
"test:e2e": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" pnpm exec playwright test",
|
||||
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts",
|
||||
"clean": "rm -rf .next .turbo node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-postgres": "3.69.0",
|
||||
"@payloadcms/next": "3.69.0",
|
||||
"@payloadcms/richtext-lexical": "3.69.0",
|
||||
"@payloadcms/storage-s3": "3.69.0",
|
||||
"@payloadcms/ui": "3.69.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "16.4.7",
|
||||
"graphql": "^16.8.1",
|
||||
"next": "15.4.10",
|
||||
"payload": "3.69.0",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.56.1",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/react": "19.2.1",
|
||||
"@types/react-dom": "19.2.1",
|
||||
"@vitejs/plugin-react": "4.5.2",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "15.4.7",
|
||||
"jsdom": "26.1.0",
|
||||
"playwright": "1.56.1",
|
||||
"playwright-core": "1.56.1",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
"pnpm": "^9 || ^10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
import 'dotenv/config'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chromium' },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm dev',
|
||||
reuseExistingServer: true,
|
||||
url: 'http://localhost:3000',
|
||||
},
|
||||
})
|
||||
@@ -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 '@payloadcms/next/css'
|
||||
import {
|
||||
REST_DELETE,
|
||||
REST_GET,
|
||||
REST_OPTIONS,
|
||||
REST_PATCH,
|
||||
REST_POST,
|
||||
REST_PUT,
|
||||
} from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const PUT = REST_PUT(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
@@ -0,0 +1,8 @@
|
||||
/* 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'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
@@ -0,0 +1,47 @@
|
||||
import configPromise from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
// Force this route to be dynamic
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(_request: NextRequest) {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise });
|
||||
|
||||
// Fetch statistics about the content
|
||||
const [projectsResult, technologiesResult, linksResult] = await Promise.all(
|
||||
[
|
||||
payload.count({ collection: "projects" }),
|
||||
payload.count({ collection: "technologies" }),
|
||||
payload.count({ collection: "links" }),
|
||||
],
|
||||
);
|
||||
|
||||
// Get featured projects count
|
||||
const featuredProjects = await payload.count({
|
||||
collection: "projects",
|
||||
where: { featured: { equals: true } },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
stats: {
|
||||
projects: {
|
||||
total: projectsResult.totalDocs,
|
||||
featured: featuredProjects.totalDocs,
|
||||
},
|
||||
technologies: technologiesResult.totalDocs,
|
||||
links: linksResult.totalDocs,
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Failed to fetch stats",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { CollectionCards as CollectionCards_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc'
|
||||
|
||||
export const importMap = {
|
||||
'@payloadcms/ui/rsc#CollectionCards': CollectionCards_ab83ff7e88da8d3530831f296ec4756a,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* 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 "./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;
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 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;
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 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;
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Links: CollectionConfig = {
|
||||
slug: "links",
|
||||
admin: {
|
||||
useAsTitle: "url",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "url",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "URL",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
type: "text",
|
||||
label: "Icon (FontAwesome class)",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "project",
|
||||
type: "relationship",
|
||||
relationTo: "projects",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: "media",
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "alt",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
upload: true,
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Projects: CollectionConfig = {
|
||||
slug: "projects",
|
||||
admin: {
|
||||
useAsTitle: "name",
|
||||
defaultColumns: ["name", "featured", "status", "updatedAt"],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "shortDescription",
|
||||
type: "text",
|
||||
label: "Short Description",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
type: "text",
|
||||
label: "Icon (FontAwesome class)",
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Draft", value: "draft" },
|
||||
{ label: "Published", value: "published" },
|
||||
{ label: "Archived", value: "archived" },
|
||||
],
|
||||
defaultValue: "draft",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "featured",
|
||||
type: "checkbox",
|
||||
label: "Featured Project",
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: "autocheckUpdated",
|
||||
type: "checkbox",
|
||||
label: "Auto-check for GitHub updates",
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description:
|
||||
"Automatically check GitHub for latest commits and update lastUpdated field",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lastUpdated",
|
||||
type: "date",
|
||||
label: "Last Updated",
|
||||
admin: {
|
||||
description:
|
||||
"Automatically updated by cron job based on GitHub commits",
|
||||
date: {
|
||||
displayFormat: "yyyy-MM-dd HH:mm:ss",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wakatimeOffset",
|
||||
type: "number",
|
||||
label: "WakaTime Offset",
|
||||
admin: {
|
||||
description: "Offset for WakaTime fetched data (optional)",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bannerImage",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
label: "Banner Image",
|
||||
},
|
||||
{
|
||||
name: "technologies",
|
||||
type: "relationship",
|
||||
relationTo: "technologies",
|
||||
hasMany: true,
|
||||
label: "Technologies Used",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Technologies: CollectionConfig = {
|
||||
slug: "technologies",
|
||||
admin: {
|
||||
useAsTitle: "name",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
type: "text",
|
||||
label: "URL",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { GlobalConfig } from "payload";
|
||||
|
||||
export const Metadata: GlobalConfig = {
|
||||
slug: "metadata",
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "tagline",
|
||||
type: "textarea",
|
||||
required: true,
|
||||
label: "Site Tagline",
|
||||
},
|
||||
{
|
||||
name: "resume",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
required: true,
|
||||
label: "Resume File",
|
||||
},
|
||||
{
|
||||
name: "resumeFilename",
|
||||
type: "text",
|
||||
label: "Resume Filename Override",
|
||||
admin: {
|
||||
description:
|
||||
'Optional: Override the filename for the resume (e.g., "resume.pdf")',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TYPE "public"."enum_projects_status" AS ENUM('draft', 'published', 'archived');
|
||||
CREATE TABLE "users_sessions" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp(3) with time zone,
|
||||
"expires_at" timestamp(3) with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"email" varchar NOT NULL,
|
||||
"reset_password_token" varchar,
|
||||
"reset_password_expiration" timestamp(3) with time zone,
|
||||
"salt" varchar,
|
||||
"hash" varchar,
|
||||
"login_attempts" numeric DEFAULT 0,
|
||||
"lock_until" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE "media" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"alt" varchar NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"url" varchar,
|
||||
"thumbnail_u_r_l" varchar,
|
||||
"filename" varchar,
|
||||
"mime_type" varchar,
|
||||
"filesize" numeric,
|
||||
"width" numeric,
|
||||
"height" numeric,
|
||||
"focal_x" numeric,
|
||||
"focal_y" numeric
|
||||
);
|
||||
|
||||
CREATE TABLE "projects" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"short_description" varchar NOT NULL,
|
||||
"icon" varchar,
|
||||
"status" "enum_projects_status" DEFAULT 'draft' NOT NULL,
|
||||
"featured" boolean DEFAULT false,
|
||||
"autocheck_updated" boolean DEFAULT false,
|
||||
"last_updated" timestamp(3) with time zone,
|
||||
"wakatime_offset" numeric,
|
||||
"banner_image_id" integer,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "projects_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"technologies_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "technologies" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"url" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "links" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"url" varchar NOT NULL,
|
||||
"icon" varchar,
|
||||
"description" varchar,
|
||||
"project_id" integer NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_kv" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar NOT NULL,
|
||||
"data" jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_locked_documents" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"global_slug" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_locked_documents_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer,
|
||||
"media_id" integer,
|
||||
"projects_id" integer,
|
||||
"technologies_id" integer,
|
||||
"links_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_preferences" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar,
|
||||
"value" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_preferences_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE "payload_migrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"batch" numeric,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "metadata" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tagline" varchar NOT NULL,
|
||||
"resume_id" integer NOT NULL,
|
||||
"resume_filename" varchar,
|
||||
"updated_at" timestamp(3) with time zone,
|
||||
"created_at" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
ALTER TABLE "users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_banner_image_id_media_id_fk" FOREIGN KEY ("banner_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "projects_rels" ADD CONSTRAINT "projects_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "projects_rels" ADD CONSTRAINT "projects_rels_technologies_fk" FOREIGN KEY ("technologies_id") REFERENCES "public"."technologies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "links" ADD CONSTRAINT "links_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_locked_documents"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_projects_fk" FOREIGN KEY ("projects_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_technologies_fk" FOREIGN KEY ("technologies_id") REFERENCES "public"."technologies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_links_fk" FOREIGN KEY ("links_id") REFERENCES "public"."links"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "metadata" ADD CONSTRAINT "metadata_resume_id_media_id_fk" FOREIGN KEY ("resume_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
CREATE INDEX "users_sessions_order_idx" ON "users_sessions" USING btree ("_order");
|
||||
CREATE INDEX "users_sessions_parent_id_idx" ON "users_sessions" USING btree ("_parent_id");
|
||||
CREATE INDEX "users_updated_at_idx" ON "users" USING btree ("updated_at");
|
||||
CREATE INDEX "users_created_at_idx" ON "users" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");
|
||||
CREATE INDEX "media_updated_at_idx" ON "media" USING btree ("updated_at");
|
||||
CREATE INDEX "media_created_at_idx" ON "media" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "media_filename_idx" ON "media" USING btree ("filename");
|
||||
CREATE INDEX "projects_banner_image_idx" ON "projects" USING btree ("banner_image_id");
|
||||
CREATE INDEX "projects_updated_at_idx" ON "projects" USING btree ("updated_at");
|
||||
CREATE INDEX "projects_created_at_idx" ON "projects" USING btree ("created_at");
|
||||
CREATE INDEX "projects_rels_order_idx" ON "projects_rels" USING btree ("order");
|
||||
CREATE INDEX "projects_rels_parent_idx" ON "projects_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "projects_rels_path_idx" ON "projects_rels" USING btree ("path");
|
||||
CREATE INDEX "projects_rels_technologies_id_idx" ON "projects_rels" USING btree ("technologies_id");
|
||||
CREATE INDEX "technologies_updated_at_idx" ON "technologies" USING btree ("updated_at");
|
||||
CREATE INDEX "technologies_created_at_idx" ON "technologies" USING btree ("created_at");
|
||||
CREATE INDEX "links_project_idx" ON "links" USING btree ("project_id");
|
||||
CREATE INDEX "links_updated_at_idx" ON "links" USING btree ("updated_at");
|
||||
CREATE INDEX "links_created_at_idx" ON "links" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "payload_kv_key_idx" ON "payload_kv" USING btree ("key");
|
||||
CREATE INDEX "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug");
|
||||
CREATE INDEX "payload_locked_documents_updated_at_idx" ON "payload_locked_documents" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_locked_documents_created_at_idx" ON "payload_locked_documents" USING btree ("created_at");
|
||||
CREATE INDEX "payload_locked_documents_rels_order_idx" ON "payload_locked_documents_rels" USING btree ("order");
|
||||
CREATE INDEX "payload_locked_documents_rels_parent_idx" ON "payload_locked_documents_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_path_idx" ON "payload_locked_documents_rels" USING btree ("path");
|
||||
CREATE INDEX "payload_locked_documents_rels_users_id_idx" ON "payload_locked_documents_rels" USING btree ("users_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_media_id_idx" ON "payload_locked_documents_rels" USING btree ("media_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_projects_id_idx" ON "payload_locked_documents_rels" USING btree ("projects_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_technologies_id_idx" ON "payload_locked_documents_rels" USING btree ("technologies_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_links_id_idx" ON "payload_locked_documents_rels" USING btree ("links_id");
|
||||
CREATE INDEX "payload_preferences_key_idx" ON "payload_preferences" USING btree ("key");
|
||||
CREATE INDEX "payload_preferences_updated_at_idx" ON "payload_preferences" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_preferences_created_at_idx" ON "payload_preferences" USING btree ("created_at");
|
||||
CREATE INDEX "payload_preferences_rels_order_idx" ON "payload_preferences_rels" USING btree ("order");
|
||||
CREATE INDEX "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "payload_preferences_rels_path_idx" ON "payload_preferences_rels" USING btree ("path");
|
||||
CREATE INDEX "payload_preferences_rels_users_id_idx" ON "payload_preferences_rels" USING btree ("users_id");
|
||||
CREATE INDEX "payload_migrations_updated_at_idx" ON "payload_migrations" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_migrations_created_at_idx" ON "payload_migrations" USING btree ("created_at");
|
||||
CREATE INDEX "metadata_resume_idx" ON "metadata" USING btree ("resume_id");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP TABLE "users_sessions" CASCADE;
|
||||
DROP TABLE "users" CASCADE;
|
||||
DROP TABLE "media" CASCADE;
|
||||
DROP TABLE "projects" CASCADE;
|
||||
DROP TABLE "projects_rels" CASCADE;
|
||||
DROP TABLE "technologies" CASCADE;
|
||||
DROP TABLE "links" CASCADE;
|
||||
DROP TABLE "payload_kv" CASCADE;
|
||||
DROP TABLE "payload_locked_documents" CASCADE;
|
||||
DROP TABLE "payload_locked_documents_rels" CASCADE;
|
||||
DROP TABLE "payload_preferences" CASCADE;
|
||||
DROP TABLE "payload_preferences_rels" CASCADE;
|
||||
DROP TABLE "payload_migrations" CASCADE;
|
||||
DROP TABLE "metadata" CASCADE;
|
||||
DROP TYPE "public"."enum_projects_status";`)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as migration_20260104_100459 from './20260104_100459';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20260104_100459.up,
|
||||
down: migration_20260104_100459.down,
|
||||
name: '20260104_100459'
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,630 @@
|
||||
/* 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:db-schema` to regenerate this file.
|
||||
*/
|
||||
|
||||
import type {} from "@payloadcms/db-postgres";
|
||||
import {
|
||||
pgTable,
|
||||
index,
|
||||
uniqueIndex,
|
||||
foreignKey,
|
||||
integer,
|
||||
varchar,
|
||||
timestamp,
|
||||
serial,
|
||||
numeric,
|
||||
boolean,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
} from "@payloadcms/db-postgres/drizzle/pg-core";
|
||||
import { sql, relations } from "@payloadcms/db-postgres/drizzle";
|
||||
export const enum_projects_status = pgEnum("enum_projects_status", [
|
||||
"draft",
|
||||
"published",
|
||||
"archived",
|
||||
]);
|
||||
|
||||
export const users_sessions = pgTable(
|
||||
"users_sessions",
|
||||
{
|
||||
_order: integer("_order").notNull(),
|
||||
_parentID: integer("_parent_id").notNull(),
|
||||
id: varchar("id").primaryKey(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
expiresAt: timestamp("expires_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}).notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("users_sessions_order_idx").on(columns._order),
|
||||
index("users_sessions_parent_id_idx").on(columns._parentID),
|
||||
foreignKey({
|
||||
columns: [columns["_parentID"]],
|
||||
foreignColumns: [users.id],
|
||||
name: "users_sessions_parent_id_fk",
|
||||
}).onDelete("cascade"),
|
||||
],
|
||||
);
|
||||
|
||||
export const users = pgTable(
|
||||
"users",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
email: varchar("email").notNull(),
|
||||
resetPasswordToken: varchar("reset_password_token"),
|
||||
resetPasswordExpiration: timestamp("reset_password_expiration", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
salt: varchar("salt"),
|
||||
hash: varchar("hash"),
|
||||
loginAttempts: numeric("login_attempts", { mode: "number" }).default(0),
|
||||
lockUntil: timestamp("lock_until", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
},
|
||||
(columns) => [
|
||||
index("users_updated_at_idx").on(columns.updatedAt),
|
||||
index("users_created_at_idx").on(columns.createdAt),
|
||||
uniqueIndex("users_email_idx").on(columns.email),
|
||||
],
|
||||
);
|
||||
|
||||
export const media = pgTable(
|
||||
"media",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
alt: varchar("alt").notNull(),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
url: varchar("url"),
|
||||
thumbnailURL: varchar("thumbnail_u_r_l"),
|
||||
filename: varchar("filename"),
|
||||
mimeType: varchar("mime_type"),
|
||||
filesize: numeric("filesize", { mode: "number" }),
|
||||
width: numeric("width", { mode: "number" }),
|
||||
height: numeric("height", { mode: "number" }),
|
||||
focalX: numeric("focal_x", { mode: "number" }),
|
||||
focalY: numeric("focal_y", { mode: "number" }),
|
||||
},
|
||||
(columns) => [
|
||||
index("media_updated_at_idx").on(columns.updatedAt),
|
||||
index("media_created_at_idx").on(columns.createdAt),
|
||||
uniqueIndex("media_filename_idx").on(columns.filename),
|
||||
],
|
||||
);
|
||||
|
||||
export const projects = pgTable(
|
||||
"projects",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
name: varchar("name").notNull(),
|
||||
description: varchar("description").notNull(),
|
||||
shortDescription: varchar("short_description").notNull(),
|
||||
icon: varchar("icon"),
|
||||
status: enum_projects_status("status").notNull().default("draft"),
|
||||
featured: boolean("featured").default(false),
|
||||
autocheckUpdated: boolean("autocheck_updated").default(false),
|
||||
lastUpdated: timestamp("last_updated", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
wakatimeOffset: numeric("wakatime_offset", { mode: "number" }),
|
||||
bannerImage: integer("banner_image_id").references(() => media.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("projects_banner_image_idx").on(columns.bannerImage),
|
||||
index("projects_updated_at_idx").on(columns.updatedAt),
|
||||
index("projects_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const projects_rels = pgTable(
|
||||
"projects_rels",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
order: integer("order"),
|
||||
parent: integer("parent_id").notNull(),
|
||||
path: varchar("path").notNull(),
|
||||
technologiesID: integer("technologies_id"),
|
||||
},
|
||||
(columns) => [
|
||||
index("projects_rels_order_idx").on(columns.order),
|
||||
index("projects_rels_parent_idx").on(columns.parent),
|
||||
index("projects_rels_path_idx").on(columns.path),
|
||||
index("projects_rels_technologies_id_idx").on(columns.technologiesID),
|
||||
foreignKey({
|
||||
columns: [columns["parent"]],
|
||||
foreignColumns: [projects.id],
|
||||
name: "projects_rels_parent_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["technologiesID"]],
|
||||
foreignColumns: [technologies.id],
|
||||
name: "projects_rels_technologies_fk",
|
||||
}).onDelete("cascade"),
|
||||
],
|
||||
);
|
||||
|
||||
export const technologies = pgTable(
|
||||
"technologies",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
name: varchar("name").notNull(),
|
||||
url: varchar("url"),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("technologies_updated_at_idx").on(columns.updatedAt),
|
||||
index("technologies_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const links = pgTable(
|
||||
"links",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
url: varchar("url").notNull(),
|
||||
icon: varchar("icon"),
|
||||
description: varchar("description"),
|
||||
project: integer("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("links_project_idx").on(columns.project),
|
||||
index("links_updated_at_idx").on(columns.updatedAt),
|
||||
index("links_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const payload_kv = pgTable(
|
||||
"payload_kv",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
key: varchar("key").notNull(),
|
||||
data: jsonb("data").notNull(),
|
||||
},
|
||||
(columns) => [uniqueIndex("payload_kv_key_idx").on(columns.key)],
|
||||
);
|
||||
|
||||
export const payload_locked_documents = pgTable(
|
||||
"payload_locked_documents",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
globalSlug: varchar("global_slug"),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("payload_locked_documents_global_slug_idx").on(columns.globalSlug),
|
||||
index("payload_locked_documents_updated_at_idx").on(columns.updatedAt),
|
||||
index("payload_locked_documents_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const payload_locked_documents_rels = pgTable(
|
||||
"payload_locked_documents_rels",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
order: integer("order"),
|
||||
parent: integer("parent_id").notNull(),
|
||||
path: varchar("path").notNull(),
|
||||
usersID: integer("users_id"),
|
||||
mediaID: integer("media_id"),
|
||||
projectsID: integer("projects_id"),
|
||||
technologiesID: integer("technologies_id"),
|
||||
linksID: integer("links_id"),
|
||||
},
|
||||
(columns) => [
|
||||
index("payload_locked_documents_rels_order_idx").on(columns.order),
|
||||
index("payload_locked_documents_rels_parent_idx").on(columns.parent),
|
||||
index("payload_locked_documents_rels_path_idx").on(columns.path),
|
||||
index("payload_locked_documents_rels_users_id_idx").on(columns.usersID),
|
||||
index("payload_locked_documents_rels_media_id_idx").on(columns.mediaID),
|
||||
index("payload_locked_documents_rels_projects_id_idx").on(
|
||||
columns.projectsID,
|
||||
),
|
||||
index("payload_locked_documents_rels_technologies_id_idx").on(
|
||||
columns.technologiesID,
|
||||
),
|
||||
index("payload_locked_documents_rels_links_id_idx").on(columns.linksID),
|
||||
foreignKey({
|
||||
columns: [columns["parent"]],
|
||||
foreignColumns: [payload_locked_documents.id],
|
||||
name: "payload_locked_documents_rels_parent_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["usersID"]],
|
||||
foreignColumns: [users.id],
|
||||
name: "payload_locked_documents_rels_users_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["mediaID"]],
|
||||
foreignColumns: [media.id],
|
||||
name: "payload_locked_documents_rels_media_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["projectsID"]],
|
||||
foreignColumns: [projects.id],
|
||||
name: "payload_locked_documents_rels_projects_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["technologiesID"]],
|
||||
foreignColumns: [technologies.id],
|
||||
name: "payload_locked_documents_rels_technologies_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["linksID"]],
|
||||
foreignColumns: [links.id],
|
||||
name: "payload_locked_documents_rels_links_fk",
|
||||
}).onDelete("cascade"),
|
||||
],
|
||||
);
|
||||
|
||||
export const payload_preferences = pgTable(
|
||||
"payload_preferences",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
key: varchar("key"),
|
||||
value: jsonb("value"),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("payload_preferences_key_idx").on(columns.key),
|
||||
index("payload_preferences_updated_at_idx").on(columns.updatedAt),
|
||||
index("payload_preferences_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const payload_preferences_rels = pgTable(
|
||||
"payload_preferences_rels",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
order: integer("order"),
|
||||
parent: integer("parent_id").notNull(),
|
||||
path: varchar("path").notNull(),
|
||||
usersID: integer("users_id"),
|
||||
},
|
||||
(columns) => [
|
||||
index("payload_preferences_rels_order_idx").on(columns.order),
|
||||
index("payload_preferences_rels_parent_idx").on(columns.parent),
|
||||
index("payload_preferences_rels_path_idx").on(columns.path),
|
||||
index("payload_preferences_rels_users_id_idx").on(columns.usersID),
|
||||
foreignKey({
|
||||
columns: [columns["parent"]],
|
||||
foreignColumns: [payload_preferences.id],
|
||||
name: "payload_preferences_rels_parent_fk",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [columns["usersID"]],
|
||||
foreignColumns: [users.id],
|
||||
name: "payload_preferences_rels_users_fk",
|
||||
}).onDelete("cascade"),
|
||||
],
|
||||
);
|
||||
|
||||
export const payload_migrations = pgTable(
|
||||
"payload_migrations",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
name: varchar("name"),
|
||||
batch: numeric("batch", { mode: "number" }),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
})
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(columns) => [
|
||||
index("payload_migrations_updated_at_idx").on(columns.updatedAt),
|
||||
index("payload_migrations_created_at_idx").on(columns.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const metadata = pgTable(
|
||||
"metadata",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
tagline: varchar("tagline").notNull(),
|
||||
resume: integer("resume_id")
|
||||
.notNull()
|
||||
.references(() => media.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
resumeFilename: varchar("resume_filename"),
|
||||
updatedAt: timestamp("updated_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
createdAt: timestamp("created_at", {
|
||||
mode: "string",
|
||||
withTimezone: true,
|
||||
precision: 3,
|
||||
}),
|
||||
},
|
||||
(columns) => [index("metadata_resume_idx").on(columns.resume)],
|
||||
);
|
||||
|
||||
export const relations_users_sessions = relations(
|
||||
users_sessions,
|
||||
({ one }) => ({
|
||||
_parentID: one(users, {
|
||||
fields: [users_sessions._parentID],
|
||||
references: [users.id],
|
||||
relationName: "sessions",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
export const relations_users = relations(users, ({ many }) => ({
|
||||
sessions: many(users_sessions, {
|
||||
relationName: "sessions",
|
||||
}),
|
||||
}));
|
||||
export const relations_media = relations(media, () => ({}));
|
||||
export const relations_projects_rels = relations(projects_rels, ({ one }) => ({
|
||||
parent: one(projects, {
|
||||
fields: [projects_rels.parent],
|
||||
references: [projects.id],
|
||||
relationName: "_rels",
|
||||
}),
|
||||
technologiesID: one(technologies, {
|
||||
fields: [projects_rels.technologiesID],
|
||||
references: [technologies.id],
|
||||
relationName: "technologies",
|
||||
}),
|
||||
}));
|
||||
export const relations_projects = relations(projects, ({ one, many }) => ({
|
||||
bannerImage: one(media, {
|
||||
fields: [projects.bannerImage],
|
||||
references: [media.id],
|
||||
relationName: "bannerImage",
|
||||
}),
|
||||
_rels: many(projects_rels, {
|
||||
relationName: "_rels",
|
||||
}),
|
||||
}));
|
||||
export const relations_technologies = relations(technologies, () => ({}));
|
||||
export const relations_links = relations(links, ({ one }) => ({
|
||||
project: one(projects, {
|
||||
fields: [links.project],
|
||||
references: [projects.id],
|
||||
relationName: "project",
|
||||
}),
|
||||
}));
|
||||
export const relations_payload_kv = relations(payload_kv, () => ({}));
|
||||
export const relations_payload_locked_documents_rels = relations(
|
||||
payload_locked_documents_rels,
|
||||
({ one }) => ({
|
||||
parent: one(payload_locked_documents, {
|
||||
fields: [payload_locked_documents_rels.parent],
|
||||
references: [payload_locked_documents.id],
|
||||
relationName: "_rels",
|
||||
}),
|
||||
usersID: one(users, {
|
||||
fields: [payload_locked_documents_rels.usersID],
|
||||
references: [users.id],
|
||||
relationName: "users",
|
||||
}),
|
||||
mediaID: one(media, {
|
||||
fields: [payload_locked_documents_rels.mediaID],
|
||||
references: [media.id],
|
||||
relationName: "media",
|
||||
}),
|
||||
projectsID: one(projects, {
|
||||
fields: [payload_locked_documents_rels.projectsID],
|
||||
references: [projects.id],
|
||||
relationName: "projects",
|
||||
}),
|
||||
technologiesID: one(technologies, {
|
||||
fields: [payload_locked_documents_rels.technologiesID],
|
||||
references: [technologies.id],
|
||||
relationName: "technologies",
|
||||
}),
|
||||
linksID: one(links, {
|
||||
fields: [payload_locked_documents_rels.linksID],
|
||||
references: [links.id],
|
||||
relationName: "links",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
export const relations_payload_locked_documents = relations(
|
||||
payload_locked_documents,
|
||||
({ many }) => ({
|
||||
_rels: many(payload_locked_documents_rels, {
|
||||
relationName: "_rels",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
export const relations_payload_preferences_rels = relations(
|
||||
payload_preferences_rels,
|
||||
({ one }) => ({
|
||||
parent: one(payload_preferences, {
|
||||
fields: [payload_preferences_rels.parent],
|
||||
references: [payload_preferences.id],
|
||||
relationName: "_rels",
|
||||
}),
|
||||
usersID: one(users, {
|
||||
fields: [payload_preferences_rels.usersID],
|
||||
references: [users.id],
|
||||
relationName: "users",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
export const relations_payload_preferences = relations(
|
||||
payload_preferences,
|
||||
({ many }) => ({
|
||||
_rels: many(payload_preferences_rels, {
|
||||
relationName: "_rels",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
export const relations_payload_migrations = relations(
|
||||
payload_migrations,
|
||||
() => ({}),
|
||||
);
|
||||
export const relations_metadata = relations(metadata, ({ one }) => ({
|
||||
resume: one(media, {
|
||||
fields: [metadata.resume],
|
||||
references: [media.id],
|
||||
relationName: "resume",
|
||||
}),
|
||||
}));
|
||||
|
||||
type DatabaseSchema = {
|
||||
enum_projects_status: typeof enum_projects_status;
|
||||
users_sessions: typeof users_sessions;
|
||||
users: typeof users;
|
||||
media: typeof media;
|
||||
projects: typeof projects;
|
||||
projects_rels: typeof projects_rels;
|
||||
technologies: typeof technologies;
|
||||
links: typeof links;
|
||||
payload_kv: typeof payload_kv;
|
||||
payload_locked_documents: typeof payload_locked_documents;
|
||||
payload_locked_documents_rels: typeof payload_locked_documents_rels;
|
||||
payload_preferences: typeof payload_preferences;
|
||||
payload_preferences_rels: typeof payload_preferences_rels;
|
||||
payload_migrations: typeof payload_migrations;
|
||||
metadata: typeof metadata;
|
||||
relations_users_sessions: typeof relations_users_sessions;
|
||||
relations_users: typeof relations_users;
|
||||
relations_media: typeof relations_media;
|
||||
relations_projects_rels: typeof relations_projects_rels;
|
||||
relations_projects: typeof relations_projects;
|
||||
relations_technologies: typeof relations_technologies;
|
||||
relations_links: typeof relations_links;
|
||||
relations_payload_kv: typeof relations_payload_kv;
|
||||
relations_payload_locked_documents_rels: typeof relations_payload_locked_documents_rels;
|
||||
relations_payload_locked_documents: typeof relations_payload_locked_documents;
|
||||
relations_payload_preferences_rels: typeof relations_payload_preferences_rels;
|
||||
relations_payload_preferences: typeof relations_payload_preferences;
|
||||
relations_payload_migrations: typeof relations_payload_migrations;
|
||||
relations_metadata: typeof relations_metadata;
|
||||
};
|
||||
|
||||
declare module "@payloadcms/db-postgres" {
|
||||
export interface GeneratedDatabaseSchema {
|
||||
schema: DatabaseSchema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
/* 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;
|
||||
projects: Project;
|
||||
technologies: Technology;
|
||||
links: Link;
|
||||
'payload-kv': PayloadKv;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
projects: ProjectsSelect<false> | ProjectsSelect<true>;
|
||||
technologies: TechnologiesSelect<false> | TechnologiesSelect<true>;
|
||||
links: LinksSelect<false> | LinksSelect<true>;
|
||||
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
};
|
||||
fallbackLocale: null;
|
||||
globals: {
|
||||
metadata: Metadatum;
|
||||
};
|
||||
globalsSelect: {
|
||||
metadata: MetadataSelect<false> | MetadataSelect<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;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "projects".
|
||||
*/
|
||||
export interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
shortDescription: string;
|
||||
icon?: string | null;
|
||||
status: 'draft' | 'published' | 'archived';
|
||||
featured?: boolean | null;
|
||||
/**
|
||||
* Automatically check GitHub for latest commits and update lastUpdated field
|
||||
*/
|
||||
autocheckUpdated?: boolean | null;
|
||||
/**
|
||||
* Automatically updated by cron job based on GitHub commits
|
||||
*/
|
||||
lastUpdated?: string | null;
|
||||
/**
|
||||
* Offset for WakaTime fetched data (optional)
|
||||
*/
|
||||
wakatimeOffset?: number | null;
|
||||
bannerImage?: (number | null) | Media;
|
||||
technologies?: (number | Technology)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "technologies".
|
||||
*/
|
||||
export interface Technology {
|
||||
id: number;
|
||||
name: string;
|
||||
url?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "links".
|
||||
*/
|
||||
export interface Link {
|
||||
id: number;
|
||||
url: string;
|
||||
icon?: string | null;
|
||||
description?: string | null;
|
||||
project: number | Project;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-kv".
|
||||
*/
|
||||
export interface PayloadKv {
|
||||
id: number;
|
||||
key: string;
|
||||
data:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
}
|
||||
/**
|
||||
* 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: 'projects';
|
||||
value: number | Project;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'technologies';
|
||||
value: number | Technology;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'links';
|
||||
value: number | Link;
|
||||
} | 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;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "projects_select".
|
||||
*/
|
||||
export interface ProjectsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
description?: T;
|
||||
shortDescription?: T;
|
||||
icon?: T;
|
||||
status?: T;
|
||||
featured?: T;
|
||||
autocheckUpdated?: T;
|
||||
lastUpdated?: T;
|
||||
wakatimeOffset?: T;
|
||||
bannerImage?: T;
|
||||
technologies?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "technologies_select".
|
||||
*/
|
||||
export interface TechnologiesSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
url?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "links_select".
|
||||
*/
|
||||
export interface LinksSelect<T extends boolean = true> {
|
||||
url?: T;
|
||||
icon?: T;
|
||||
description?: T;
|
||||
project?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-kv_select".
|
||||
*/
|
||||
export interface PayloadKvSelect<T extends boolean = true> {
|
||||
key?: T;
|
||||
data?: 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` "metadata".
|
||||
*/
|
||||
export interface Metadatum {
|
||||
id: number;
|
||||
tagline: string;
|
||||
resume: number | Media;
|
||||
/**
|
||||
* Optional: Override the filename for the resume (e.g., "resume.pdf")
|
||||
*/
|
||||
resumeFilename?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "metadata_select".
|
||||
*/
|
||||
export interface MetadataSelect<T extends boolean = true> {
|
||||
tagline?: T;
|
||||
resume?: T;
|
||||
resumeFilename?: 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 {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { postgresAdapter } from "@payloadcms/db-postgres";
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
import path from "path";
|
||||
import { buildConfig } from "payload";
|
||||
import { fileURLToPath } from "url";
|
||||
import { s3Storage } from "@payloadcms/storage-s3";
|
||||
import { migrations } from "./migrations";
|
||||
|
||||
import { Users } from "./collections/Users";
|
||||
import { Media } from "./collections/Media";
|
||||
import { Projects } from "./collections/Projects";
|
||||
import { Technologies } from "./collections/Technologies";
|
||||
import { Links } from "./collections/Links";
|
||||
import { Metadata } from "./globals/Metadata";
|
||||
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
user: Users.slug,
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
admin: "/",
|
||||
},
|
||||
collections: [Users, Media, Projects, Technologies, Links],
|
||||
globals: [Metadata],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || "",
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, "payload-types.ts"),
|
||||
},
|
||||
db: postgresAdapter({
|
||||
prodMigrations: migrations,
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
},
|
||||
}),
|
||||
graphQL: {
|
||||
disablePlaygroundInProduction: true,
|
||||
},
|
||||
plugins: [
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: true,
|
||||
},
|
||||
bucket: process.env.R2_BUCKET_NAME!,
|
||||
config: {
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
region: "auto",
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
|
||||
test.describe('Frontend', () => {
|
||||
let page: Page
|
||||
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
})
|
||||
|
||||
test('can go on homepage', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
|
||||
await expect(page).toHaveTitle(/Payload Blank Template/)
|
||||
|
||||
const headging = page.locator('h1').first()
|
||||
|
||||
await expect(headging).toHaveText('Welcome to your new project.')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getPayload, Payload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
|
||||
import { describe, it, beforeAll, expect } from 'vitest'
|
||||
|
||||
let payload: Payload
|
||||
|
||||
describe('API', () => {
|
||||
beforeAll(async () => {
|
||||
const payloadConfig = await config
|
||||
payload = await getPayload({ config: payloadConfig })
|
||||
})
|
||||
|
||||
it('fetches users', async () => {
|
||||
const users = await payload.find({
|
||||
collection: 'users',
|
||||
})
|
||||
expect(users).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"target": "ES2022",
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tsconfigPaths(), react()],
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
include: ['tests/int/**/*.int.spec.ts'],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
// Any setup scripts you might need go here
|
||||
|
||||
// Load .env files
|
||||
import 'dotenv/config'
|
||||
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@xevion/web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev --port 5000",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 5000",
|
||||
"start": "node build/index.js",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"deploy": "echo 'Use Docker deployment'",
|
||||
"clean": "rm -rf .svelte-kit build .turbo node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.1.0",
|
||||
"@fontsource/hanken-grotesk": "^5.1.0",
|
||||
"@fontsource/schibsted-grotesk": "^5.2.8",
|
||||
"@xevion/db": "workspace:*",
|
||||
"bits-ui": "^2.8.2",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"p5": "^1.11.7",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"postgres": "^3.4.5",
|
||||
"svelte-wrap-balancer": "^0.0.4",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.424",
|
||||
"@sveltejs/adapter-node": "^5.2.9",
|
||||
"@sveltejs/kit": "^2.21.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/p5": "^1.7.7",
|
||||
"svelte": "^5.45.6",
|
||||
"svelte-adapter-bun": "^1.0.1",
|
||||
"svelte-check": "^4.3.4",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
"vite": "^7.2.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
/* Custom colors */
|
||||
--color-zinc-850: #1d1d20;
|
||||
|
||||
/* Custom font sizes */
|
||||
--font-size-10xl: 10rem;
|
||||
|
||||
/* Drop shadows */
|
||||
--drop-shadow-extreme: 0 0 50px black;
|
||||
|
||||
/* Font families */
|
||||
--font-inter: "Inter Variable", sans-serif;
|
||||
--font-hanken: "Hanken Grotesk", sans-serif;
|
||||
--font-schibsted: "Schibsted Grotesk", sans-serif;
|
||||
|
||||
/* Background images */
|
||||
--background-image-gradient-radial: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
var(--tw-gradient-stops)
|
||||
);
|
||||
|
||||
/* Animations */
|
||||
--animate-bg-fast: fade 0.5s ease-in-out 0.5s forwards;
|
||||
--animate-bg: fade 2.5s ease-in-out 1.5s forwards;
|
||||
--animate-fade-in: fade-in 2.5s ease-in-out forwards;
|
||||
--animate-title: title 3s ease-out forwards;
|
||||
--animate-fade-left: fade-left 3s ease-in-out forwards;
|
||||
--animate-fade-right: fade-right 3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0%;
|
||||
}
|
||||
100% {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0%;
|
||||
}
|
||||
75% {
|
||||
opacity: 0%;
|
||||
}
|
||||
100% {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-left {
|
||||
0% {
|
||||
transform: translateX(100%);
|
||||
opacity: 0%;
|
||||
}
|
||||
30% {
|
||||
transform: translateX(0%);
|
||||
opacity: 100%;
|
||||
}
|
||||
100% {
|
||||
opacity: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-right {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0%;
|
||||
}
|
||||
30% {
|
||||
transform: translateX(0%);
|
||||
opacity: 100%;
|
||||
}
|
||||
100% {
|
||||
opacity: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes title {
|
||||
0% {
|
||||
line-height: 0%;
|
||||
letter-spacing: 0.25em;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
line-height: 0%;
|
||||
opacity: 0%;
|
||||
}
|
||||
80% {
|
||||
opacity: 100%;
|
||||
}
|
||||
100% {
|
||||
line-height: 100%;
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply font-inter overflow-x-hidden text-white;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply h-full;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
import Dots from "./Dots.svelte";
|
||||
|
||||
let {
|
||||
class: className = "",
|
||||
backgroundClass = "",
|
||||
children,
|
||||
}: {
|
||||
class?: string;
|
||||
backgroundClass?: string;
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="pointer-events-none fixed inset-0 -z-20 bg-black"></div>
|
||||
<Dots class={[
|
||||
backgroundClass
|
||||
]} />
|
||||
<main class={cn("relative min-h-screen text-zinc-50", className)}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</main>
|
||||
@@ -0,0 +1,360 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { ClassValue } from "clsx";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
|
||||
let { class: className = "" }: { class?: ClassValue } = $props();
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let gl: WebGLRenderingContext | null = null;
|
||||
let animationId: number | null = null;
|
||||
|
||||
// Noise sampling scale - larger values create smoother, more gradual flow patterns
|
||||
const SCALE = 1000;
|
||||
// Maximum displacement distance from grid position (in pixels)
|
||||
const LENGTH = 10;
|
||||
// Distance between grid points (in pixels) - controls dot density
|
||||
const SPACING = 20;
|
||||
// Global animation speed multiplier - higher values make everything move faster
|
||||
const TIMESCALE = 10.25 / 1000;
|
||||
// Rotation/angle animation speed multiplier
|
||||
const ANGLE_TIME_SCALE = 2.0;
|
||||
// Pulsing/length animation speed multiplier
|
||||
const LENGTH_TIME_SCALE = 1.5;
|
||||
// Base opacity of dots (0-1)
|
||||
const OPACITY = 0.9;
|
||||
// Radius of each dot (in pixels)
|
||||
const RADIUS = 3.5;
|
||||
// How much opacity varies with angle (0-1)
|
||||
const ANGLE_OPACITY_AMPLITUDE = 0.8;
|
||||
// Minimum opacity from angle calculation
|
||||
const ANGLE_OPACITY_FLOOR = 0.1;
|
||||
// Lower bound of random per-dot opacity
|
||||
const RANDOM_OPACITY_MIN = 0.5;
|
||||
// Upper bound of random per-dot opacity
|
||||
const RANDOM_OPACITY_MAX = 1.0;
|
||||
|
||||
// Simplex noise GLSL implementation
|
||||
const vertexShader = `
|
||||
attribute vec2 a_position;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentShader = `
|
||||
precision mediump float;
|
||||
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
uniform float u_seed;
|
||||
uniform float u_dpr;
|
||||
uniform float u_scale;
|
||||
uniform float u_length;
|
||||
uniform float u_spacing;
|
||||
uniform float u_opacity;
|
||||
uniform float u_radius;
|
||||
uniform float u_angleTimeScale;
|
||||
uniform float u_lengthTimeScale;
|
||||
uniform float u_angleOpacityAmp;
|
||||
uniform float u_angleOpacityFloor;
|
||||
uniform float u_randomOpacityMin;
|
||||
uniform float u_randomOpacityMax;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
|
||||
// Simplex 3D noise
|
||||
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
||||
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
|
||||
float snoise(vec3 v) {
|
||||
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
||||
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
||||
|
||||
vec3 i = floor(v + dot(v, C.yyy));
|
||||
vec3 x0 = v - i + dot(i, C.xxx);
|
||||
|
||||
vec3 g = step(x0.yzx, x0.xyz);
|
||||
vec3 l = 1.0 - g;
|
||||
vec3 i1 = min(g.xyz, l.zxy);
|
||||
vec3 i2 = max(g.xyz, l.zxy);
|
||||
|
||||
vec3 x1 = x0 - i1 + C.xxx;
|
||||
vec3 x2 = x0 - i2 + C.yyy;
|
||||
vec3 x3 = x0 - D.yyy;
|
||||
|
||||
i = mod289(i + u_seed);
|
||||
vec4 p = permute(permute(permute(
|
||||
i.z + vec4(0.0, i1.z, i2.z, 1.0))
|
||||
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
|
||||
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
||||
|
||||
float n_ = 0.142857142857;
|
||||
vec3 ns = n_ * D.wyz - D.xzx;
|
||||
|
||||
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
||||
|
||||
vec4 x_ = floor(j * ns.z);
|
||||
vec4 y_ = floor(j - 7.0 * x_);
|
||||
|
||||
vec4 x = x_ *ns.x + ns.yyyy;
|
||||
vec4 y = y_ *ns.x + ns.yyyy;
|
||||
vec4 h = 1.0 - abs(x) - abs(y);
|
||||
|
||||
vec4 b0 = vec4(x.xy, y.xy);
|
||||
vec4 b1 = vec4(x.zw, y.zw);
|
||||
|
||||
vec4 s0 = floor(b0)*2.0 + 1.0;
|
||||
vec4 s1 = floor(b1)*2.0 + 1.0;
|
||||
vec4 sh = -step(h, vec4(0.0));
|
||||
|
||||
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
||||
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
||||
|
||||
vec3 p0 = vec3(a0.xy, h.x);
|
||||
vec3 p1 = vec3(a0.zw, h.y);
|
||||
vec3 p2 = vec3(a1.xy, h.z);
|
||||
vec3 p3 = vec3(a1.zw, h.w);
|
||||
|
||||
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
|
||||
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
||||
m = m * m;
|
||||
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
||||
}
|
||||
|
||||
// Hash function for random per-point opacity
|
||||
float hash(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||
}
|
||||
|
||||
// Convert snoise [-1,1] to p5-style noise [0,1]
|
||||
float noise01(vec3 v) {
|
||||
return (snoise(v) + 1.0) * 0.5;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pixelCoord = gl_FragCoord.xy;
|
||||
|
||||
// Find nearest grid point (account for DPR)
|
||||
float spacing = u_spacing * u_dpr;
|
||||
float scaleDpr = u_scale * u_dpr;
|
||||
vec2 gridCoord = floor(pixelCoord / spacing) * spacing;
|
||||
|
||||
// Calculate distance to all nearby grid points (9 neighbors)
|
||||
float minDist = 1000000.0;
|
||||
vec2 closestPoint = vec2(0.0);
|
||||
float pointOpacity = 0.0;
|
||||
|
||||
for (float dx = -1.0; dx <= 1.0; dx += 1.0) {
|
||||
for (float dy = -1.0; dy <= 1.0; dy += 1.0) {
|
||||
vec2 testGrid = gridCoord + vec2(dx * spacing, dy * spacing);
|
||||
|
||||
// Get force direction at this grid point (matching original p5 formula)
|
||||
// Original: (noise(x/SCALE, y/SCALE, z) - 0.5) * 2 * TWO_PI
|
||||
float rad = (noise01(vec3(testGrid / scaleDpr, u_time * u_angleTimeScale)) - 0.5) * 4.0 * PI;
|
||||
// Original: (noise(x/SCALE, y/SCALE, z*2) + 0.5) * LENGTH
|
||||
float len = (noise01(vec3(testGrid / scaleDpr, u_time * u_lengthTimeScale)) + 0.5) * u_length * u_dpr;
|
||||
|
||||
// Calculate displaced position
|
||||
vec2 displacedPoint = testGrid + vec2(cos(rad), sin(rad)) * len;
|
||||
|
||||
float dist = distance(pixelCoord, displacedPoint);
|
||||
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closestPoint = testGrid;
|
||||
pointOpacity = hash(testGrid) * (u_randomOpacityMax - u_randomOpacityMin) + u_randomOpacityMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate angle for opacity calculation
|
||||
float rad = (noise01(vec3(closestPoint / scaleDpr, u_time * u_angleTimeScale)) - 0.5) * 4.0 * PI;
|
||||
|
||||
// Draw circle with configurable radius
|
||||
float circle = 1.0 - smoothstep(0.0, u_radius * u_dpr, minDist);
|
||||
|
||||
// Calculate opacity based on angle
|
||||
float angleOpacity = (abs(cos(rad)) * u_angleOpacityAmp + u_angleOpacityFloor) * pointOpacity * u_opacity;
|
||||
|
||||
// Light gray dots with calculated opacity
|
||||
gl_FragColor = vec4(vec3(200.0/255.0), circle * angleOpacity);
|
||||
}
|
||||
`;
|
||||
|
||||
function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {
|
||||
const shader = gl.createShader(type);
|
||||
if (!shader) return null;
|
||||
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
function createProgram(gl: WebGLRenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram | null {
|
||||
const program = gl.createProgram();
|
||||
if (!program) return null;
|
||||
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.error('Program link error:', gl.getProgramInfoLog(program));
|
||||
gl.deleteProgram(program);
|
||||
return null;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
if (!canvas) return;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = window.innerWidth * dpr;
|
||||
canvas.height = window.innerHeight * dpr;
|
||||
canvas.style.width = `${window.innerWidth}px`;
|
||||
canvas.style.height = `${window.innerHeight}px`;
|
||||
|
||||
if (gl) {
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
|
||||
if (!gl) {
|
||||
console.error('WebGL not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('WebGL context created');
|
||||
|
||||
const vShader = createShader(gl, gl.VERTEX_SHADER, vertexShader);
|
||||
const fShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader);
|
||||
|
||||
if (!vShader || !fShader) {
|
||||
console.error('Shader creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Shaders compiled successfully');
|
||||
|
||||
const program = createProgram(gl, vShader, fShader);
|
||||
if (!program) return;
|
||||
|
||||
// Set up geometry (full screen quad)
|
||||
const positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
-1, 1,
|
||||
1, -1,
|
||||
1, 1,
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
const positionLocation = gl.getAttribLocation(program, 'a_position');
|
||||
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
|
||||
const timeLocation = gl.getUniformLocation(program, 'u_time');
|
||||
const seedLocation = gl.getUniformLocation(program, 'u_seed');
|
||||
const dprLocation = gl.getUniformLocation(program, 'u_dpr');
|
||||
const scaleLocation = gl.getUniformLocation(program, 'u_scale');
|
||||
const lengthLocation = gl.getUniformLocation(program, 'u_length');
|
||||
const spacingLocation = gl.getUniformLocation(program, 'u_spacing');
|
||||
const opacityLocation = gl.getUniformLocation(program, 'u_opacity');
|
||||
const radiusLocation = gl.getUniformLocation(program, 'u_radius');
|
||||
const angleTimeScaleLocation = gl.getUniformLocation(program, 'u_angleTimeScale');
|
||||
const lengthTimeScaleLocation = gl.getUniformLocation(program, 'u_lengthTimeScale');
|
||||
const angleOpacityAmpLocation = gl.getUniformLocation(program, 'u_angleOpacityAmp');
|
||||
const angleOpacityFloorLocation = gl.getUniformLocation(program, 'u_angleOpacityFloor');
|
||||
const randomOpacityMinLocation = gl.getUniformLocation(program, 'u_randomOpacityMin');
|
||||
const randomOpacityMaxLocation = gl.getUniformLocation(program, 'u_randomOpacityMax');
|
||||
|
||||
gl.useProgram(program);
|
||||
gl.enableVertexAttribArray(positionLocation);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Enable blending for transparency
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
// Set static uniforms (these don't change per frame)
|
||||
gl.uniform1f(seedLocation, Math.random() * 1000);
|
||||
gl.uniform1f(dprLocation, dpr);
|
||||
gl.uniform1f(scaleLocation, SCALE);
|
||||
gl.uniform1f(lengthLocation, LENGTH);
|
||||
gl.uniform1f(spacingLocation, SPACING);
|
||||
gl.uniform1f(opacityLocation, OPACITY);
|
||||
gl.uniform1f(radiusLocation, RADIUS);
|
||||
gl.uniform1f(angleTimeScaleLocation, ANGLE_TIME_SCALE);
|
||||
gl.uniform1f(lengthTimeScaleLocation, LENGTH_TIME_SCALE);
|
||||
gl.uniform1f(angleOpacityAmpLocation, ANGLE_OPACITY_AMPLITUDE);
|
||||
gl.uniform1f(angleOpacityFloorLocation, ANGLE_OPACITY_FLOOR);
|
||||
gl.uniform1f(randomOpacityMinLocation, RANDOM_OPACITY_MIN);
|
||||
gl.uniform1f(randomOpacityMaxLocation, RANDOM_OPACITY_MAX);
|
||||
|
||||
resizeCanvas();
|
||||
|
||||
let startTime = Date.now();
|
||||
|
||||
function render() {
|
||||
if (!gl || !canvas) return;
|
||||
|
||||
const time = (Date.now() - startTime) / 1000 * TIMESCALE;
|
||||
|
||||
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
|
||||
gl.uniform1f(timeLocation, time);
|
||||
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
animationId = requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
};
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (animationId !== null) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
if (gl) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Dots overlay with fade-in animation -->
|
||||
<canvas
|
||||
bind:this={canvas}
|
||||
class={cn(
|
||||
"pointer-events-none fixed inset-0 -z-10",
|
||||
className
|
||||
)}
|
||||
></canvas>
|
||||
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
@@ -0,0 +1,33 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "@xevion/db";
|
||||
import { projectsRelations } from "@xevion/db";
|
||||
|
||||
const extendedSchema = {
|
||||
...schema,
|
||||
relations_projects: projectsRelations,
|
||||
};
|
||||
|
||||
let _db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
export function getDb() {
|
||||
if (!_db) {
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL environment variable is not set");
|
||||
}
|
||||
|
||||
const sql = postgres(connectionString);
|
||||
_db = drizzle(sql, { schema: extendedSchema });
|
||||
}
|
||||
|
||||
return _db;
|
||||
}
|
||||
|
||||
// For backward compatibility
|
||||
export const db = new Proxy({} as ReturnType<typeof drizzle>, {
|
||||
get(target, prop) {
|
||||
return getDb()[prop as keyof ReturnType<typeof drizzle>];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import "../app.css";
|
||||
import "@fontsource-variable/inter";
|
||||
import "@fontsource/hanken-grotesk/900.css";
|
||||
import "@fontsource/schibsted-grotesk/400.css";
|
||||
import "@fontsource/schibsted-grotesk/500.css";
|
||||
import "@fontsource/schibsted-grotesk/600.css";
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>Xevion.dev</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="The personal website of Xevion, a full-stack software developer."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import AppWrapper from "$lib/components/AppWrapper.svelte";
|
||||
import IconSimpleIconsGithub from "~icons/simple-icons/github";
|
||||
import IconSimpleIconsLinkedin from "~icons/simple-icons/linkedin";
|
||||
import IconSimpleIconsDiscord from "~icons/simple-icons/discord";
|
||||
import MaterialSymbolsMailRounded from "~icons/material-symbols/mail-rounded";
|
||||
import MaterialSymbolsVpnKey from "~icons/material-symbols/vpn-key";
|
||||
import IconLucideRss from "~icons/lucide/rss";
|
||||
</script>
|
||||
|
||||
<AppWrapper class="overflow-x-hidden font-schibsted">
|
||||
<!-- Top Navigation Bar -->
|
||||
<div class="flex w-full justify-end items-center pt-5 px-6 pb-9">
|
||||
<div class="flex gap-4 items-center">
|
||||
<!-- <a href="/rss" class="text-zinc-400 hover:text-zinc-200">
|
||||
<IconLucideRss class="size-5" />
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex items-center flex-col">
|
||||
<div
|
||||
class="max-w-2xl mx-6 border-b border-zinc-700 divide-y divide-zinc-700"
|
||||
>
|
||||
<!-- Name & Occupation -->
|
||||
<div class="flex flex-col pb-4">
|
||||
<span class="text-3xl font-bold text-white">Ryan Walters,</span>
|
||||
<span class="text-2xl font-normal text-zinc-400">
|
||||
Full-Stack Software Engineer
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="py-4 text-zinc-200">
|
||||
<p class="text-[0.95em]">
|
||||
A fanatical software engineer with expertise and passion for sound,
|
||||
scalable and high-performance applications. I'm always working on
|
||||
something new. <br />
|
||||
Sometimes innovative — sometimes crazy.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="py-3">
|
||||
<span class="text-zinc-200">Connect with me</span>
|
||||
<div class="flex gap-x-2 pl-3 pt-3 pb-2">
|
||||
<a
|
||||
href="https://github.com/Xevion"
|
||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<IconSimpleIconsGithub class="size-4 text-zinc-300" />
|
||||
<span class="text-sm text-zinc-100">GitHub</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://linkedin.com/in/ryancwalters"
|
||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<IconSimpleIconsLinkedin class="size-4 text-zinc-300" />
|
||||
<span class="text-sm text-zinc-100">LinkedIn</span>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<IconSimpleIconsDiscord class="size-4 text-zinc-300" />
|
||||
<span class="text-sm text-zinc-100">Discord</span>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:your.email@example.com"
|
||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<MaterialSymbolsMailRounded class="size-4.5 text-zinc-300" />
|
||||
<span class="text-sm text-zinc-100">Email</span>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<MaterialSymbolsVpnKey class="size-4.5 text-zinc-300" />
|
||||
<span class="text-sm text-zinc-100">PGP Key</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppWrapper>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import AppWrapper from "$lib/components/AppWrapper.svelte";
|
||||
</script>
|
||||
|
||||
<AppWrapper>
|
||||
<div class="flex min-h-screen items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="font-hanken text-4xl text-zinc-200 md:text-5xl">Blog</h1>
|
||||
<p class="mt-4 text-lg text-zinc-400">Coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</AppWrapper>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { getDb } from "$lib/server/db";
|
||||
import { projects } from "@xevion/db";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const db = getDb(event);
|
||||
|
||||
// Use Drizzle relations for efficient join query
|
||||
const projectsWithLinks = await db.query.projects.findMany({
|
||||
where: eq(projects.status, "published"),
|
||||
orderBy: [desc(projects.updatedAt)],
|
||||
with: {
|
||||
links: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
projects: projectsWithLinks,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import AppWrapper from "$lib/components/AppWrapper.svelte";
|
||||
import Balancer from "svelte-wrap-balancer";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<AppWrapper>
|
||||
<div
|
||||
class="relative z-10 mx-auto grid grid-cols-1 justify-center gap-y-4 px-4 py-20 align-middle sm:grid-cols-2 md:max-w-[50rem] lg:max-w-[75rem] lg:grid-cols-3 lg:gap-y-9"
|
||||
>
|
||||
<div class="mb-3 text-center sm:col-span-2 md:mb-5 lg:col-span-3 lg:mb-7">
|
||||
<h1 class="pb-3 font-hanken text-4xl text-zinc-200 opacity-100 md:text-5xl">
|
||||
Projects
|
||||
</h1>
|
||||
<Balancer>
|
||||
<p class="text-lg text-zinc-400">
|
||||
created, maintained, or contributed to by me...
|
||||
</p>
|
||||
</Balancer>
|
||||
</div>
|
||||
|
||||
{#each data.projects as project (project.id)}
|
||||
{@const links = project.links}
|
||||
{@const useAnchor = links.length > 0}
|
||||
{@const href = useAnchor ? links[0].url : undefined}
|
||||
|
||||
<div class="max-w-fit">
|
||||
<svelte:element
|
||||
this={useAnchor ? "a" : "div"}
|
||||
{href}
|
||||
target={useAnchor ? "_blank" : undefined}
|
||||
rel={useAnchor ? "noreferrer" : undefined}
|
||||
title={project.name}
|
||||
class="flex items-center justify-start overflow-hidden rounded bg-black/10 pb-2.5 pl-3 pr-5 pt-1 text-zinc-400 transition-colors hover:bg-zinc-500/10 hover:text-zinc-50"
|
||||
>
|
||||
<div class="flex h-full w-14 items-center justify-center pr-5">
|
||||
<i
|
||||
class={cn(
|
||||
project.icon ?? "fa-heart",
|
||||
"fa-solid text-3xl text-opacity-80 saturate-0"
|
||||
)}
|
||||
></i>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<span class="text-sm md:text-base lg:text-lg">
|
||||
{project.name}
|
||||
</span>
|
||||
<p
|
||||
class="truncate text-xs opacity-70 md:text-sm lg:text-base"
|
||||
title={project.shortDescription}
|
||||
>
|
||||
{project.shortDescription}
|
||||
</p>
|
||||
</div>
|
||||
</svelte:element>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</AppWrapper>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { getDb } from "$lib/server/db";
|
||||
import { metadata } from "@xevion/db";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
export const GET: RequestHandler = async (event) => {
|
||||
const db = getDb(event);
|
||||
|
||||
// TODO: Query the metadata global for resume URL
|
||||
// For now, redirect to a placeholder
|
||||
// const meta = await db.select().from(metadata).limit(1);
|
||||
|
||||
// Placeholder redirect until we have the schema set up
|
||||
redirect(302, "https://example.com/resume.pdf");
|
||||
};
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 648 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,88 @@
|
||||
==================================================================
|
||||
https://keybase.io/xevion
|
||||
--------------------------------------------------------------------
|
||||
|
||||
I hereby claim:
|
||||
|
||||
* I am an admin of https://xevion.dev
|
||||
* I am xevion (https://keybase.io/xevion) on keybase.
|
||||
* I have a public key with fingerprint 04EE F43A E775 B57F 5029 572E 1EF2 4DA2 70E7 9ECD
|
||||
|
||||
To do so, I am signing this object:
|
||||
|
||||
{
|
||||
"body": {
|
||||
"key": {
|
||||
"eldest_kid": "012026f3d2df48d71d6ca5a33418101c284f7a73aac8944dc52edbfef17bb92369600a",
|
||||
"fingerprint": "04eef43ae775b57f5029572e1ef24da270e79ecd",
|
||||
"host": "keybase.io",
|
||||
"key_id": "1ef24da270e79ecd",
|
||||
"kid": "010167e9d3b055e24b9e6b83ec0e59f3e8daf5a5cbfe4f3f7edb7e1aa1e6a1f1dfd00a",
|
||||
"uid": "120150a540a9487c406e6cf5e0423419",
|
||||
"username": "xevion"
|
||||
},
|
||||
"merkle_root": {
|
||||
"ctime": 1607280978,
|
||||
"hash_meta": "ee4f985b1f5b4915e4963c7ce2945ac76673edc180e59ffdeafca8e57fcac740",
|
||||
"seqno": 18330340
|
||||
},
|
||||
"revoke": {
|
||||
"sig_ids": [
|
||||
"4d1ae3b707b77cd3199a1a4d1c4becab985c2baecbc6c478da499b27d7fe7f230f"
|
||||
]
|
||||
},
|
||||
"service": {
|
||||
"hostname": "xevion.dev",
|
||||
"protocol": "https:"
|
||||
},
|
||||
"type": "web_service_binding",
|
||||
"version": 1
|
||||
},
|
||||
"ctime": 1607280982,
|
||||
"expire_in": 157680000,
|
||||
"prev": "12ae982e85b7fea5f216f2fe50d4145330bbf762a009c9183dc22d134da279ab",
|
||||
"seqno": 32,
|
||||
"tag": "signature"
|
||||
}
|
||||
|
||||
which yields the signature:
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: Keybase OpenPGP v2.1.13
|
||||
Comment: https://keybase.io/crypto
|
||||
|
||||
yMP0AnicbZN9bBRFGMavxUJaKbaAAkUElkSBVpidnd3ZPYyA0ICAjYBAaYVzZvZd
|
||||
etz17rgvKNiqICpQ+YjhIyBobARUvhQaiESFhgQrVr7EohRKFSUCJYGKgYrU2SIx
|
||||
Gvefzb77vO/zzG9mDmV28GSkPHL9mfciFzfVpRypiSU8vrpBUxcoPGyXKd4FSgDa
|
||||
XxC0IRb3Bfy24lWQihE2HM3GtkNMm6q2IZjONI2opopUgU3iUEY1xoRpEWILHYPN
|
||||
HXBUyrmFNcMyEGJKnuL4Q7MgGon6Q3F3LAFwiMaAUp3r1NERtnSKQQUHE5thioBa
|
||||
IGzZWBKOuR0yHGcxGOIPy5r88LXH+x/9vdxINShYtsaRrgMm3AKDmxoIBLrlaGDa
|
||||
zNGZLmRW4mgOlakpqIypYDDVUW3Hvps7cdcHI1VHTCeIWcSkgiADDOHogAiWKCxX
|
||||
GINoiJWCVM+DpD8cUsrzlFKIBoLgi4bDcRetiPtdgWogik1kUVMuj8VKfKUQZ7IP
|
||||
ZBTL1Lnq6JxYqg7EMjRBBWCL6ExQw6Aa2EI129fg2MAcwUyQ/IT8S5BMEYM5obB0
|
||||
MDUNaQTJCFFIhgPgusf8syS1mOItVoitMtA4RZRTKmxNtSymMlkVhINgXKYQmDMQ
|
||||
XBiCUAmLWBbH1KYOUAdryFFmlLtu0aRftA93t+lfyx9iQ1IGikTD8bAIB2W9JB6P
|
||||
xLwulnhZxBXOBe77e4SP+0O2PCKyIwnRmIvPq0rlf4iZOE+BeRF/FHx+V6FTw0Ty
|
||||
cX2knbtRDKQKJEUZlekOVg0HO6Ajm6hEl1Q4d6iBGUKWsCQmW2Bsq1r7IbIY/weh
|
||||
Jq3ibJacKbmFWDwRBaW85uDz93lSMjwd01Ldu+PJSM+6d6MmHsjyVJ6P9Nze0jCO
|
||||
v7JjUvCHdZ0K2sb0a1mRzFvb/VLfWk/jniVNlT6cf6JgW+m3t6JK9QtfTqvNH/v7
|
||||
/QU3Dn8UxBfmrOzy/tPjGnqfahrQkdYeaDZHhCdXxNe/++ofbZnPtUVsz5rak4v3
|
||||
bpvQ66uuT149/frMal/WzSt8+1B4dsvlPunHznyc03Vgn26hQZlDNiyZ+wXPT+09
|
||||
uqIsc+TxziWJr4u7p530NOdcGvvB1W8e2P74mQG7qi7+tMjbevi1vluRfW7fzty0
|
||||
hTkpqwo3li0TuY34WMHS5jtjro0s+5QUrfFXLjq116P30xdW3G4qOLIycKi48tzn
|
||||
garNA9dMKY1UTbvua9l5Pe3sSy/n0vD4lEWzKz4cmKA3lW5HJwy+nZc9rP+KX3MP
|
||||
tyWzT2WR5niwpbmqfveCTo1XWjOH7lrnOdgwTr2xP2tjYdfut2Y/ukEL+FqLr7UO
|
||||
O7K5Qw91xoO35z6xLHtqed3pLYE5iZGn72QsbV1iNZc89Vj6psFF62syerzVeDY5
|
||||
aVUpPf/2O6O8O5Tl+Wuzx5/YOWp+aq9V00fM9o+ad3nrpM6Jarh0fPRR1Dh6XzVJ
|
||||
zf4x3ml8Uf9Plr3ZM2XvjOX1dY2/FU1pSk+bvP8G7t/hl89mlpftyZ44vUufg2Lp
|
||||
ntXbEkW8b/3xC8MnPJwTrKE/B18c8EbhUA75Kxu++75+4uqrY5K58//EhfuG7z77
|
||||
UPFfKC78RA==
|
||||
=XyA8
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
And finally, I am proving ownership of this host by posting or
|
||||
appending to this document.
|
||||
|
||||
View my publicly-auditable identity here: https://keybase.io/xevion
|
||||
|
||||
==================================================================
|
||||
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Xevion.dev",
|
||||
"short_name": "Xevion.dev",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import adapter from "svelte-adapter-bun";
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
out: "build",
|
||||
precompress: false,
|
||||
}),
|
||||
alias: {
|
||||
$components: "src/lib/components",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["unplugin-icons/types/svelte"]
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import Icons from "unplugin-icons/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit(), Icons({ compiler: "svelte" })],
|
||||
});
|
||||