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.
This commit is contained in:
2026-01-04 13:18:34 -06:00
parent 31b1804fc9
commit af81d8e048
110 changed files with 8392 additions and 12918 deletions
File diff suppressed because it is too large Load Diff
+38
View File
@@ -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
+5
View File
@@ -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.
+31
View File
@@ -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 });
+63
View File
@@ -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"
}
}
+41
View File
@@ -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,
}
+35
View File
@@ -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;
+27
View File
@@ -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;
+31
View File
@@ -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,
},
],
};
+16
View File
@@ -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,
};
+92
View File
@@ -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",
},
],
};
+13
View File
@@ -0,0 +1,13 @@
import type { CollectionConfig } from "payload";
export const Users: CollectionConfig = {
slug: "users",
admin: {
useAsTitle: "email",
},
auth: true,
fields: [
// Email added by default
// Add more fields as needed
],
};
+32
View File
@@ -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")',
},
},
],
};
File diff suppressed because it is too large Load Diff
@@ -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";`)
}
+9
View File
@@ -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;
}
}
+470
View File
@@ -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 {}
}
+61
View File
@@ -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.')
})
})
+20
View File
@@ -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()
})
})
+45
View File
@@ -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"
],
}
+12
View File
@@ -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'],
},
})
+4
View File
@@ -0,0 +1,4 @@
// Any setup scripts you might need go here
// Load .env files
import 'dotenv/config'
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+46
View File
@@ -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"
}
}
+108
View File
@@ -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;
}
+11
View File
@@ -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>
+1
View File
@@ -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>
+360
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+33
View File
@@ -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>];
},
});
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+21
View File
@@ -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()}
+85
View File
@@ -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 &mdash; 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>
+12
View File
@@ -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,
};
};
+61
View File
@@ -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>
+15
View File
@@ -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");
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.
+88
View File
@@ -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
==================================================================
+3
View File
@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:
+19
View File
@@ -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"
}
+19
View File
@@ -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;
+21
View File
@@ -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
}
+8
View File
@@ -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" })],
});