mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
modified: .env
This commit is contained in:
parent
be2ddda4d5
commit
0f7f5704da
15 changed files with 2642 additions and 24 deletions
5
.env
5
.env
|
|
@ -1,5 +1,6 @@
|
||||||
DATABASE_URL=postgresql://postgres:[YOUR_PASSWORD]@db.kmdeisowhtsalsekkzqd.supabase.co:5432/postgres
|
DATABASE_URL=postgresql://postgres:Max!FTW2023!@db.kmdeisowhtsalsekkzqd.supabase.co:5432/postgres
|
||||||
SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
|
SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
|
||||||
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ
|
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ
|
||||||
AI_INTEGRATIONS_OPENAI_BASE_URL=https://placeholder.openai.com
|
AI_INTEGRATIONS_OPENAI_BASE_URL=https://placeholder.openai.com
|
||||||
AI_INTEGRATIONS_OPENAI_API_KEY=placeholder_key
|
AI_INTEGRATIONS_OPENAI_API_KEY=placeholder_key
|
||||||
|
SESSION_SECRET=3a44273587118f6f2926b9b6839f2ddea3aea4f75ccb78b9510611b399a42823
|
||||||
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Attach to Chrome",
|
||||||
|
"port": 9222,
|
||||||
|
"request": "attach",
|
||||||
|
"type": "chrome",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:8080",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -45,14 +45,15 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
<script>
|
<!-- Service Worker disabled in development to prevent CORS issues -->
|
||||||
if ('serviceWorker' in navigator) {
|
<!-- <script>
|
||||||
|
if ('serviceWorker' in navigator && import.meta.env.PROD) {
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker.register('/sw.js')
|
navigator.serviceWorker.register('/sw.js')
|
||||||
.then(reg => console.log('SW registered:', reg.scope))
|
.then(reg => console.log('SW registered:', reg.scope))
|
||||||
.catch(err => console.error('SW registration failed:', err));
|
.catch(err => console.error('SW registration failed:', err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script> -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
import { Link, useLocation } from "wouter";
|
import { Link, useLocation } from "wouter";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useAuth } from "@/lib/auth";
|
import { useAuth } from "@/lib/auth";
|
||||||
|
|
@ -11,7 +12,13 @@ export default function AdminSites() {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
|
|
||||||
const { data: sites, isLoading } = useQuery({
|
const [editingSite, setEditingSite] = useState<any | null>(null);
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [formLoading, setFormLoading] = useState(false);
|
||||||
|
const [formError, setFormError] = useState<string | null>(null);
|
||||||
|
const [formSuccess, setFormSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { data: sites, isLoading, refetch } = useQuery({
|
||||||
queryKey: ["sites"],
|
queryKey: ["sites"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch("/api/sites");
|
const res = await fetch("/api/sites");
|
||||||
|
|
@ -19,6 +26,58 @@ export default function AdminSites() {
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// Add or update site
|
||||||
|
const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setFormLoading(true);
|
||||||
|
setFormError(null);
|
||||||
|
setFormSuccess(null);
|
||||||
|
const form = e.currentTarget;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const payload: any = Object.fromEntries(formData.entries());
|
||||||
|
try {
|
||||||
|
let res;
|
||||||
|
if (editingSite) {
|
||||||
|
res = await fetch(`/api/sites/${editingSite.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await fetch("/api/sites", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!res.ok) throw new Error("Failed to save site");
|
||||||
|
setFormSuccess(editingSite ? "Site updated!" : "Site created!");
|
||||||
|
setShowForm(false);
|
||||||
|
setEditingSite(null);
|
||||||
|
form.reset();
|
||||||
|
await refetch();
|
||||||
|
} catch (err: any) {
|
||||||
|
setFormError(err.message || "Error");
|
||||||
|
} finally {
|
||||||
|
setFormLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete site
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
if (!window.confirm("Delete this site?")) return;
|
||||||
|
setFormLoading(true);
|
||||||
|
setFormError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/sites/${id}`, { method: "DELETE" });
|
||||||
|
if (!res.ok) throw new Error("Failed to delete site");
|
||||||
|
await refetch();
|
||||||
|
} catch (err: any) {
|
||||||
|
setFormError(err.message || "Error");
|
||||||
|
} finally {
|
||||||
|
setFormLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout();
|
await logout();
|
||||||
|
|
@ -49,15 +108,47 @@ export default function AdminSites() {
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8 flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-display font-bold text-white uppercase tracking-wider">
|
<div>
|
||||||
AeThex Sites
|
<h2 className="text-2xl font-display font-bold text-white uppercase tracking-wider">
|
||||||
</h2>
|
AeThex Sites
|
||||||
<p className="text-muted-foreground text-sm mt-1">
|
</h2>
|
||||||
{sites?.length || 0} monitored sites
|
<p className="text-muted-foreground text-sm mt-1">
|
||||||
</p>
|
{sites?.length || 0} monitored sites
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="bg-primary text-white px-4 py-2 rounded font-bold hover:bg-primary/80 transition"
|
||||||
|
onClick={() => { setShowForm(true); setEditingSite(null); }}
|
||||||
|
>
|
||||||
|
+ Add Site
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formError && <div className="mb-4 text-red-500">{formError}</div>}
|
||||||
|
{formSuccess && <div className="mb-4 text-green-500">{formSuccess}</div>}
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<form className="mb-8 bg-card/50 border border-white/10 p-6 rounded" onSubmit={handleFormSubmit}>
|
||||||
|
<h3 className="font-bold text-white mb-2">{editingSite ? "Edit Site" : "Add Site"}</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<input name="name" defaultValue={editingSite?.name || ""} placeholder="Name" required className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="url" defaultValue={editingSite?.url || ""} placeholder="URL" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="status" defaultValue={editingSite?.status || "online"} placeholder="Status" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="uptime" defaultValue={editingSite?.uptime || ""} placeholder="Uptime (%)" type="number" step="0.01" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="response_time" defaultValue={editingSite?.response_time || ""} placeholder="Response Time (ms)" type="number" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="users" defaultValue={editingSite?.users || ""} placeholder="Users" type="number" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
<input name="requests" defaultValue={editingSite?.requests || ""} placeholder="Requests" type="number" className="p-2 rounded bg-background/50 border border-white/10 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex gap-2">
|
||||||
|
<button type="submit" className="bg-primary text-white px-4 py-2 rounded font-bold hover:bg-primary/80 transition" disabled={formLoading}>
|
||||||
|
{formLoading ? "Saving..." : (editingSite ? "Update" : "Create")}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="px-4 py-2 rounded border border-white/10 text-white" onClick={() => { setShowForm(false); setEditingSite(null); }}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="col-span-full text-center text-muted-foreground py-12">
|
<div className="col-span-full text-center text-muted-foreground py-12">
|
||||||
|
|
@ -85,12 +176,24 @@ export default function AdminSites() {
|
||||||
<Globe className="w-5 h-5 text-primary" />
|
<Globe className="w-5 h-5 text-primary" />
|
||||||
<h3 className="font-display text-white uppercase text-sm">{site.name}</h3>
|
<h3 className="font-display text-white uppercase text-sm">{site.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex items-center gap-1 text-xs ${getStatusColor(site.status)}`}>
|
<div className="flex items-center gap-2">
|
||||||
{getStatusIcon(site.status)}
|
<button
|
||||||
{site.status || 'unknown'}
|
className="text-xs text-blue-400 hover:underline"
|
||||||
|
onClick={() => { setEditingSite(site); setShowForm(true); }}
|
||||||
|
title="Edit"
|
||||||
|
>Edit</button>
|
||||||
|
<button
|
||||||
|
className="text-xs text-red-400 hover:underline"
|
||||||
|
onClick={() => handleDelete(site.id)}
|
||||||
|
title="Delete"
|
||||||
|
disabled={formLoading}
|
||||||
|
>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={`flex items-center gap-1 text-xs ${getStatusColor(site.status)}`}>
|
||||||
|
{getStatusIcon(site.status)}
|
||||||
|
{site.status || 'unknown'}
|
||||||
|
</div>
|
||||||
{site.url && (
|
{site.url && (
|
||||||
<a
|
<a
|
||||||
href={site.url}
|
href={site.url}
|
||||||
|
|
@ -101,7 +204,6 @@ export default function AdminSites() {
|
||||||
{site.url} <ExternalLink className="w-3 h-3" />
|
{site.url} <ExternalLink className="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4 text-xs">
|
<div className="grid grid-cols-2 gap-4 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-muted-foreground">Uptime</div>
|
<div className="text-muted-foreground">Uptime</div>
|
||||||
|
|
@ -120,7 +222,6 @@ export default function AdminSites() {
|
||||||
<div className="text-white font-bold">{site.requests || 0}</div>
|
<div className="text-white font-bold">{site.requests || 0}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{site.last_check && (
|
{site.last_check && (
|
||||||
<div className="mt-4 pt-4 border-t border-white/5 text-xs text-muted-foreground">
|
<div className="mt-4 pt-4 border-t border-white/5 text-xs text-muted-foreground">
|
||||||
Last check: {new Date(site.last_check).toLocaleString()}
|
Last check: {new Date(site.last_check).toLocaleString()}
|
||||||
|
|
|
||||||
260
migrations/0000_worried_mastermind.sql
Normal file
260
migrations/0000_worried_mastermind.sql
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
CREATE TABLE "achievements" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"icon" text,
|
||||||
|
"points_reward" integer DEFAULT 0,
|
||||||
|
"badge_color" text,
|
||||||
|
"rarity" text,
|
||||||
|
"xp_reward" integer DEFAULT 0,
|
||||||
|
"category" varchar DEFAULT 'milestone' NOT NULL,
|
||||||
|
CONSTRAINT "achievements_name_unique" UNIQUE("name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_alerts" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"site_id" varchar,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"severity" text NOT NULL,
|
||||||
|
"message" text NOT NULL,
|
||||||
|
"is_resolved" boolean DEFAULT false,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"resolved_at" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_applications" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"creator_id" varchar NOT NULL,
|
||||||
|
"opportunity_id" varchar NOT NULL,
|
||||||
|
"status" text DEFAULT 'submitted',
|
||||||
|
"cover_letter" text,
|
||||||
|
"response_message" text,
|
||||||
|
"applied_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_creators" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" varchar NOT NULL,
|
||||||
|
"username" text NOT NULL,
|
||||||
|
"bio" text,
|
||||||
|
"skills" json DEFAULT '[]'::json,
|
||||||
|
"avatar_url" text,
|
||||||
|
"experience_level" text,
|
||||||
|
"arm_affiliations" json DEFAULT '[]'::json,
|
||||||
|
"primary_arm" text,
|
||||||
|
"is_discoverable" boolean DEFAULT true,
|
||||||
|
"allow_recommendations" boolean DEFAULT true,
|
||||||
|
"devconnect_linked" boolean DEFAULT false,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now(),
|
||||||
|
CONSTRAINT "aethex_creators_username_unique" UNIQUE("username")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_events" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"site_id" varchar,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"date" timestamp NOT NULL,
|
||||||
|
"time" text NOT NULL,
|
||||||
|
"location" text,
|
||||||
|
"capacity" integer,
|
||||||
|
"image_url" text,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp,
|
||||||
|
"category" text,
|
||||||
|
"price" numeric,
|
||||||
|
"featured" boolean,
|
||||||
|
"speakers" json,
|
||||||
|
"agenda" json,
|
||||||
|
"full_description" text,
|
||||||
|
"map_url" text,
|
||||||
|
"ticket_types" json
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_opportunities" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text NOT NULL,
|
||||||
|
"job_type" text NOT NULL,
|
||||||
|
"salary_min" integer,
|
||||||
|
"salary_max" integer,
|
||||||
|
"experience_level" text,
|
||||||
|
"arm_affiliation" text NOT NULL,
|
||||||
|
"posted_by_id" varchar NOT NULL,
|
||||||
|
"status" text DEFAULT 'open',
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_passports" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" varchar NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
CONSTRAINT "aethex_passports_user_id_unique" UNIQUE("user_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_projects" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"creator_id" varchar NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"url" text,
|
||||||
|
"image_url" text,
|
||||||
|
"tags" json DEFAULT '[]'::json,
|
||||||
|
"is_featured" boolean DEFAULT false,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "aethex_sites" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"url" text,
|
||||||
|
"status" text,
|
||||||
|
"uptime" numeric,
|
||||||
|
"response_time" integer,
|
||||||
|
"users" integer,
|
||||||
|
"requests" integer,
|
||||||
|
"last_check" timestamp,
|
||||||
|
"services" json,
|
||||||
|
"metrics" json,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"metrics_history" json,
|
||||||
|
"owner_id" varchar,
|
||||||
|
"api_key_hash" text,
|
||||||
|
"handshake_token" text,
|
||||||
|
"handshake_token_expires_at" timestamp,
|
||||||
|
CONSTRAINT "aethex_sites_name_unique" UNIQUE("name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "applications" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"full_name" text NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"location" text,
|
||||||
|
"role_interest" text,
|
||||||
|
"primary_skill" text,
|
||||||
|
"experience_level" text,
|
||||||
|
"availability" text,
|
||||||
|
"portfolio_url" text,
|
||||||
|
"resume_url" text,
|
||||||
|
"interests" json,
|
||||||
|
"message" text,
|
||||||
|
"status" text DEFAULT 'new' NOT NULL,
|
||||||
|
"submitted_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "chat_messages" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" varchar NOT NULL,
|
||||||
|
"role" text NOT NULL,
|
||||||
|
"content" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "profiles" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"username" text,
|
||||||
|
"role" text DEFAULT 'member',
|
||||||
|
"onboarded" boolean DEFAULT false,
|
||||||
|
"bio" text,
|
||||||
|
"skills" json,
|
||||||
|
"avatar_url" text,
|
||||||
|
"banner_url" text,
|
||||||
|
"social_links" json,
|
||||||
|
"loyalty_points" integer DEFAULT 0,
|
||||||
|
"email" text,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now(),
|
||||||
|
"user_type" text DEFAULT 'community_member',
|
||||||
|
"experience_level" text DEFAULT 'beginner',
|
||||||
|
"full_name" text,
|
||||||
|
"location" text,
|
||||||
|
"total_xp" integer DEFAULT 0,
|
||||||
|
"level" integer DEFAULT 1,
|
||||||
|
"aethex_passport_id" varchar,
|
||||||
|
"status" text DEFAULT 'offline',
|
||||||
|
"is_verified" boolean DEFAULT false
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "projects" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"owner_id" varchar,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" text DEFAULT 'planning',
|
||||||
|
"github_url" text,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now(),
|
||||||
|
"user_id" varchar,
|
||||||
|
"engine" text,
|
||||||
|
"priority" text DEFAULT 'medium',
|
||||||
|
"progress" integer DEFAULT 0,
|
||||||
|
"live_url" text,
|
||||||
|
"technologies" json
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_achievements" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" varchar,
|
||||||
|
"achievement_id" varchar,
|
||||||
|
"site_id" text,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"unlocked_at" timestamp DEFAULT now(),
|
||||||
|
"earned_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user_profiles" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"username" text,
|
||||||
|
"full_name" text,
|
||||||
|
"avatar_url" text,
|
||||||
|
"user_type" text NOT NULL,
|
||||||
|
"experience_level" text DEFAULT 'beginner',
|
||||||
|
"bio" text,
|
||||||
|
"location" text,
|
||||||
|
"website_url" text,
|
||||||
|
"github_url" text,
|
||||||
|
"twitter_url" text,
|
||||||
|
"linkedin_url" text,
|
||||||
|
"total_xp" integer DEFAULT 0,
|
||||||
|
"level" integer DEFAULT 1,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now(),
|
||||||
|
"current_streak" integer DEFAULT 0,
|
||||||
|
"longest_streak" integer DEFAULT 0,
|
||||||
|
"last_streak_at" timestamp,
|
||||||
|
"loyalty_points" integer DEFAULT 0,
|
||||||
|
"reputation_score" integer DEFAULT 0,
|
||||||
|
"wallet_address" varchar,
|
||||||
|
"show_in_creator_directory" boolean DEFAULT false,
|
||||||
|
"arms" json DEFAULT '[]'::json,
|
||||||
|
"roles" json DEFAULT '[]'::json,
|
||||||
|
"last_active_at" timestamp DEFAULT now(),
|
||||||
|
"streak_days" integer DEFAULT 0,
|
||||||
|
"roblox_user_id" text,
|
||||||
|
"roblox_username" text,
|
||||||
|
"unity_player_id" text,
|
||||||
|
"unreal_player_id" text,
|
||||||
|
"godot_player_id" text,
|
||||||
|
"merged_to_user_id" varchar,
|
||||||
|
"aethex_domain" text,
|
||||||
|
"discord_id" text,
|
||||||
|
"discord_username" text,
|
||||||
|
"is_architect" boolean DEFAULT false,
|
||||||
|
"xp" integer DEFAULT 0,
|
||||||
|
"daily_streak" integer DEFAULT 0,
|
||||||
|
"last_daily" timestamp,
|
||||||
|
"last_xp_message" timestamp,
|
||||||
|
"badges" json DEFAULT '[]'::json,
|
||||||
|
CONSTRAINT "user_profiles_username_unique" UNIQUE("username"),
|
||||||
|
CONSTRAINT "user_profiles_wallet_address_unique" UNIQUE("wallet_address"),
|
||||||
|
CONSTRAINT "user_profiles_roblox_user_id_unique" UNIQUE("roblox_user_id"),
|
||||||
|
CONSTRAINT "user_profiles_unity_player_id_unique" UNIQUE("unity_player_id"),
|
||||||
|
CONSTRAINT "user_profiles_unreal_player_id_unique" UNIQUE("unreal_player_id"),
|
||||||
|
CONSTRAINT "user_profiles_godot_player_id_unique" UNIQUE("godot_player_id"),
|
||||||
|
CONSTRAINT "user_profiles_discord_id_unique" UNIQUE("discord_id")
|
||||||
|
);
|
||||||
1591
migrations/meta/0000_snapshot.json
Normal file
1591
migrations/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
13
migrations/meta/_journal.json
Normal file
13
migrations/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1766373949237,
|
||||||
|
"tag": "0000_worried_mastermind",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
179
package-lock.json
generated
179
package-lock.json
generated
|
|
@ -68,6 +68,7 @@
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-resizable-panels": "^2.1.9",
|
"react-resizable-panels": "^2.1.9",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
|
"socket.io": "^4.8.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|
@ -4020,6 +4021,12 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@supabase/auth-js": {
|
"node_modules/@supabase/auth-js": {
|
||||||
"version": "2.87.3",
|
"version": "2.87.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.87.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.87.3.tgz",
|
||||||
|
|
@ -4485,6 +4492,15 @@
|
||||||
"@types/pg": "*"
|
"@types/pg": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cors": {
|
||||||
|
"version": "2.8.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||||
|
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||||
|
|
@ -4653,6 +4669,7 @@
|
||||||
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"pg-protocol": "*",
|
"pg-protocol": "*",
|
||||||
|
|
@ -4823,6 +4840,15 @@
|
||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64id": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^4.5.0 || >= 5.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.8.21",
|
"version": "2.8.21",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz",
|
||||||
|
|
@ -5066,6 +5092,19 @@
|
||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cors": {
|
||||||
|
"version": "2.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": "^4",
|
||||||
|
"vary": "^1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
|
|
@ -5209,9 +5248,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
|
|
@ -5495,6 +5534,44 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/engine.io": {
|
||||||
|
"version": "6.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz",
|
||||||
|
"integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cors": "^2.8.12",
|
||||||
|
"@types/node": ">=10.0.0",
|
||||||
|
"accepts": "~1.3.4",
|
||||||
|
"base64id": "2.0.0",
|
||||||
|
"cookie": "~0.7.2",
|
||||||
|
"cors": "~2.8.5",
|
||||||
|
"debug": "~4.4.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.18.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.3",
|
"version": "5.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||||
|
|
@ -7513,6 +7590,102 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socket.io": {
|
||||||
|
"version": "4.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.2.tgz",
|
||||||
|
"integrity": "sha512-wMAICvNHJNtnd3Jq97xROyRyFjMQ2G8QsVF6V+K6+6lztP3GaTcIaos+6E7+8jD/NoY++/vCvU9AI+bvRBNXVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.4",
|
||||||
|
"base64id": "~2.0.0",
|
||||||
|
"cors": "~2.8.5",
|
||||||
|
"debug": "~4.4.1",
|
||||||
|
"engine.io": "~6.6.0",
|
||||||
|
"socket.io-adapter": "~2.5.2",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "~4.3.4",
|
||||||
|
"ws": "~8.17.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter/node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sonner": {
|
"node_modules/sonner": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-resizable-panels": "^2.1.9",
|
"react-resizable-panels": "^2.1.9",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
|
"socket.io": "^4.8.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import session from "express-session";
|
||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
import { serveStatic } from "./static";
|
import { serveStatic } from "./static";
|
||||||
import { createServer } from "http";
|
import { createServer } from "http";
|
||||||
|
import { setupWebSocket } from "./websocket";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
|
|
@ -91,13 +92,16 @@ app.use((req, res, next) => {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await registerRoutes(httpServer, app);
|
await registerRoutes(httpServer, app);
|
||||||
|
|
||||||
|
// Setup WebSocket server for real-time notifications and Aegis alerts
|
||||||
|
setupWebSocket(httpServer);
|
||||||
|
|
||||||
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
|
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
|
||||||
const status = err.status || err.statusCode || 500;
|
const status = err.status || err.statusCode || 500;
|
||||||
const message = err.message || "Internal Server Error";
|
const message = err.message || "Internal Server Error";
|
||||||
|
|
||||||
res.status(status).json({ message });
|
res.status(status).json({ message });
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,7 @@ export async function registerRoutes(
|
||||||
// ========== NEW ADMIN ROUTES ==========
|
// ========== NEW ADMIN ROUTES ==========
|
||||||
|
|
||||||
// Get all aethex sites (admin only)
|
// Get all aethex sites (admin only)
|
||||||
|
// List all sites
|
||||||
app.get("/api/sites", requireAdmin, async (req, res) => {
|
app.get("/api/sites", requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const sites = await storage.getSites();
|
const sites = await storage.getSites();
|
||||||
|
|
@ -358,6 +359,42 @@ export async function registerRoutes(
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a new site
|
||||||
|
app.post("/api/sites", requireAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const site = await storage.createSite(req.body);
|
||||||
|
res.status(201).json(site);
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update a site
|
||||||
|
app.patch("/api/sites/:id", requireAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const site = await storage.updateSite(req.params.id, req.body);
|
||||||
|
if (!site) {
|
||||||
|
return res.status(404).json({ error: "Site not found" });
|
||||||
|
}
|
||||||
|
res.json(site);
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete a site
|
||||||
|
app.delete("/api/sites/:id", requireAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deleted = await storage.deleteSite(req.params.id);
|
||||||
|
if (!deleted) {
|
||||||
|
return res.status(404).json({ error: "Site not found" });
|
||||||
|
}
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Get auth logs (admin only)
|
// Get auth logs (admin only)
|
||||||
app.get("/api/auth-logs", requireAdmin, async (req, res) => {
|
app.get("/api/auth-logs", requireAdmin, async (req, res) => {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ export interface IStorage {
|
||||||
getAlerts(): Promise<any[]>;
|
getAlerts(): Promise<any[]>;
|
||||||
updateAlert(id: string, updates: any): Promise<any>;
|
updateAlert(id: string, updates: any): Promise<any>;
|
||||||
|
|
||||||
|
// Notifications (for WebSocket)
|
||||||
|
getNotifications(): Promise<any[]>;
|
||||||
|
|
||||||
// Chat Messages (AI memory)
|
// Chat Messages (AI memory)
|
||||||
getChatHistory(userId: string, limit?: number): Promise<ChatMessage[]>;
|
getChatHistory(userId: string, limit?: number): Promise<ChatMessage[]>;
|
||||||
saveChatMessage(id: string, userId: string, role: string, content: string): Promise<void>;
|
saveChatMessage(id: string, userId: string, role: string, content: string): Promise<void>;
|
||||||
|
|
@ -47,6 +50,38 @@ export interface IStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SupabaseStorage implements IStorage {
|
export class SupabaseStorage implements IStorage {
|
||||||
|
// Create a new site
|
||||||
|
async createSite(site: any): Promise<any> {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('aethex_sites')
|
||||||
|
.insert(site)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a site
|
||||||
|
async updateSite(id: string, updates: any): Promise<any> {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('aethex_sites')
|
||||||
|
.update({ ...updates, updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', id)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a site
|
||||||
|
async deleteSite(id: string): Promise<boolean> {
|
||||||
|
const { error, count } = await supabase
|
||||||
|
.from('aethex_sites')
|
||||||
|
.delete({ count: 'exact' })
|
||||||
|
.eq('id', id);
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
async getProfiles(): Promise<Profile[]> {
|
async getProfiles(): Promise<Profile[]> {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
|
|
@ -246,6 +281,38 @@ export class SupabaseStorage implements IStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getNotifications(): Promise<any[]> {
|
||||||
|
// Get recent activity - applications, alerts, etc.
|
||||||
|
const [applications, alerts] = await Promise.all([
|
||||||
|
this.getApplications(),
|
||||||
|
this.getAlerts()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Transform into notification format
|
||||||
|
const notifications = [
|
||||||
|
...applications.slice(0, 5).map(app => ({
|
||||||
|
id: app.id,
|
||||||
|
type: 'application',
|
||||||
|
message: `New application from ${app.full_name}`,
|
||||||
|
timestamp: app.submitted_at,
|
||||||
|
unread: true
|
||||||
|
})),
|
||||||
|
...alerts.filter(a => !a.is_resolved).slice(0, 5).map(alert => ({
|
||||||
|
id: alert.id,
|
||||||
|
type: 'alert',
|
||||||
|
message: alert.message,
|
||||||
|
severity: alert.severity,
|
||||||
|
timestamp: alert.created_at,
|
||||||
|
unread: true
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sort by timestamp desc
|
||||||
|
return notifications.sort((a, b) =>
|
||||||
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getMetrics(): Promise<{
|
async getMetrics(): Promise<{
|
||||||
totalProfiles: number;
|
totalProfiles: number;
|
||||||
totalProjects: number;
|
totalProjects: number;
|
||||||
|
|
@ -276,3 +343,7 @@ export class SupabaseStorage implements IStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = new SupabaseStorage();
|
export const storage = new SupabaseStorage();
|
||||||
|
|
||||||
|
// Export helper functions for WebSocket
|
||||||
|
export const getAlerts = () => storage.getAlerts();
|
||||||
|
export const getNotifications = () => storage.getNotifications();
|
||||||
|
|
|
||||||
36
server/websocket.ts
Normal file
36
server/websocket.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Server } from "http";
|
||||||
|
import { Server as SocketIOServer } from "socket.io";
|
||||||
|
import { getAlerts, getNotifications } from "./storage";
|
||||||
|
|
||||||
|
export function setupWebSocket(httpServer: Server) {
|
||||||
|
const io = new SocketIOServer(httpServer, {
|
||||||
|
cors: {
|
||||||
|
origin: "*",
|
||||||
|
methods: ["GET", "POST"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
// Send initial notifications and alerts
|
||||||
|
Promise.all([getNotifications(), getAlerts()]).then(([notifications, alerts]) => {
|
||||||
|
socket.emit("notifications", notifications);
|
||||||
|
socket.emit("alerts", alerts);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for alert resolution events
|
||||||
|
socket.on("resolveAlert", async (alertId) => {
|
||||||
|
// You'd call your alert resolution logic here
|
||||||
|
// After resolving, broadcast updated alerts
|
||||||
|
const alerts = await getAlerts();
|
||||||
|
io.emit("alerts", alerts);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for request to refresh notifications
|
||||||
|
socket.on("refreshNotifications", async () => {
|
||||||
|
const notifications = await getNotifications();
|
||||||
|
socket.emit("notifications", notifications);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return io;
|
||||||
|
}
|
||||||
304
shared/schema.ts
304
shared/schema.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { pgTable, text, varchar, boolean, integer, timestamp, json } from "drizzle-orm/pg-core";
|
import { pgTable, text, varchar, boolean, integer, timestamp, json, decimal } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
|
@ -95,3 +95,305 @@ export const insertChatMessageSchema = createInsertSchema(chatMessages).omit({
|
||||||
|
|
||||||
export type InsertChatMessage = z.infer<typeof insertChatMessageSchema>;
|
export type InsertChatMessage = z.infer<typeof insertChatMessageSchema>;
|
||||||
export type ChatMessage = typeof chatMessages.$inferSelect;
|
export type ChatMessage = typeof chatMessages.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Sites table
|
||||||
|
export const aethex_sites = pgTable("aethex_sites", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
name: text("name").notNull().unique(),
|
||||||
|
url: text("url"),
|
||||||
|
status: text("status"),
|
||||||
|
uptime: decimal("uptime"),
|
||||||
|
response_time: integer("response_time"),
|
||||||
|
users: integer("users"),
|
||||||
|
requests: integer("requests"),
|
||||||
|
last_check: timestamp("last_check"),
|
||||||
|
services: json("services").$type<string[] | null>(),
|
||||||
|
metrics: json("metrics"),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
metrics_history: json("metrics_history").$type<any[] | null>(),
|
||||||
|
owner_id: varchar("owner_id"),
|
||||||
|
api_key_hash: text("api_key_hash"),
|
||||||
|
handshake_token: text("handshake_token"),
|
||||||
|
handshake_token_expires_at: timestamp("handshake_token_expires_at"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexSiteSchema = createInsertSchema(aethex_sites).omit({
|
||||||
|
created_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexSite = z.infer<typeof insertAethexSiteSchema>;
|
||||||
|
export type AethexSite = typeof aethex_sites.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Alerts table
|
||||||
|
export const aethex_alerts = pgTable("aethex_alerts", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
site_id: varchar("site_id"),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
severity: text("severity").notNull(),
|
||||||
|
message: text("message").notNull(),
|
||||||
|
is_resolved: boolean("is_resolved").default(false),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
resolved_at: timestamp("resolved_at"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexAlertSchema = createInsertSchema(aethex_alerts).omit({
|
||||||
|
created_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexAlert = z.infer<typeof insertAethexAlertSchema>;
|
||||||
|
export type AethexAlert = typeof aethex_alerts.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Applications table
|
||||||
|
export const aethex_applications = pgTable("aethex_applications", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
creator_id: varchar("creator_id").notNull(),
|
||||||
|
opportunity_id: varchar("opportunity_id").notNull(),
|
||||||
|
status: text("status").default("submitted"),
|
||||||
|
cover_letter: text("cover_letter"),
|
||||||
|
response_message: text("response_message"),
|
||||||
|
applied_at: timestamp("applied_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexApplicationSchema = createInsertSchema(aethex_applications).omit({
|
||||||
|
applied_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexApplication = z.infer<typeof insertAethexApplicationSchema>;
|
||||||
|
export type AethexApplication = typeof aethex_applications.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Creators table
|
||||||
|
export const aethex_creators = pgTable("aethex_creators", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
user_id: varchar("user_id").notNull(),
|
||||||
|
username: text("username").notNull().unique(),
|
||||||
|
bio: text("bio"),
|
||||||
|
skills: json("skills").$type<string[]>().default([]),
|
||||||
|
avatar_url: text("avatar_url"),
|
||||||
|
experience_level: text("experience_level"),
|
||||||
|
arm_affiliations: json("arm_affiliations").$type<string[]>().default([]),
|
||||||
|
primary_arm: text("primary_arm"),
|
||||||
|
is_discoverable: boolean("is_discoverable").default(true),
|
||||||
|
allow_recommendations: boolean("allow_recommendations").default(true),
|
||||||
|
devconnect_linked: boolean("devconnect_linked").default(false),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexCreatorSchema = createInsertSchema(aethex_creators).omit({
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexCreator = z.infer<typeof insertAethexCreatorSchema>;
|
||||||
|
export type AethexCreator = typeof aethex_creators.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Passports table
|
||||||
|
export const aethex_passports = pgTable("aethex_passports", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
user_id: varchar("user_id").notNull().unique(),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexPassportSchema = createInsertSchema(aethex_passports).omit({
|
||||||
|
created_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexPassport = z.infer<typeof insertAethexPassportSchema>;
|
||||||
|
export type AethexPassport = typeof aethex_passports.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Projects table
|
||||||
|
export const aethex_projects = pgTable("aethex_projects", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
creator_id: varchar("creator_id").notNull(),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
url: text("url"),
|
||||||
|
image_url: text("image_url"),
|
||||||
|
tags: json("tags").$type<string[]>().default([]),
|
||||||
|
is_featured: boolean("is_featured").default(false),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexProjectSchema = createInsertSchema(aethex_projects).omit({
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexProject = z.infer<typeof insertAethexProjectSchema>;
|
||||||
|
export type AethexProject = typeof aethex_projects.$inferSelect;
|
||||||
|
|
||||||
|
// User Profiles table (extended profiles)
|
||||||
|
export const user_profiles = pgTable("user_profiles", {
|
||||||
|
id: varchar("id").primaryKey(),
|
||||||
|
username: text("username").unique(),
|
||||||
|
full_name: text("full_name"),
|
||||||
|
avatar_url: text("avatar_url"),
|
||||||
|
user_type: text("user_type").notNull(),
|
||||||
|
experience_level: text("experience_level").default("beginner"),
|
||||||
|
bio: text("bio"),
|
||||||
|
location: text("location"),
|
||||||
|
website_url: text("website_url"),
|
||||||
|
github_url: text("github_url"),
|
||||||
|
twitter_url: text("twitter_url"),
|
||||||
|
linkedin_url: text("linkedin_url"),
|
||||||
|
total_xp: integer("total_xp").default(0),
|
||||||
|
level: integer("level").default(1),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at").defaultNow(),
|
||||||
|
current_streak: integer("current_streak").default(0),
|
||||||
|
longest_streak: integer("longest_streak").default(0),
|
||||||
|
last_streak_at: timestamp("last_streak_at"),
|
||||||
|
loyalty_points: integer("loyalty_points").default(0),
|
||||||
|
reputation_score: integer("reputation_score").default(0),
|
||||||
|
wallet_address: varchar("wallet_address").unique(),
|
||||||
|
show_in_creator_directory: boolean("show_in_creator_directory").default(false),
|
||||||
|
arms: json("arms").$type<string[]>().default([]),
|
||||||
|
roles: json("roles").$type<string[]>().default([]),
|
||||||
|
last_active_at: timestamp("last_active_at").defaultNow(),
|
||||||
|
streak_days: integer("streak_days").default(0),
|
||||||
|
roblox_user_id: text("roblox_user_id").unique(),
|
||||||
|
roblox_username: text("roblox_username"),
|
||||||
|
unity_player_id: text("unity_player_id").unique(),
|
||||||
|
unreal_player_id: text("unreal_player_id").unique(),
|
||||||
|
godot_player_id: text("godot_player_id").unique(),
|
||||||
|
merged_to_user_id: varchar("merged_to_user_id"),
|
||||||
|
aethex_domain: text("aethex_domain"),
|
||||||
|
discord_id: text("discord_id").unique(),
|
||||||
|
discord_username: text("discord_username"),
|
||||||
|
is_architect: boolean("is_architect").default(false),
|
||||||
|
xp: integer("xp").default(0),
|
||||||
|
daily_streak: integer("daily_streak").default(0),
|
||||||
|
last_daily: timestamp("last_daily"),
|
||||||
|
last_xp_message: timestamp("last_xp_message"),
|
||||||
|
badges: json("badges").default([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertUserProfileSchema = createInsertSchema(user_profiles).omit({
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
last_active_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertUserProfile = z.infer<typeof insertUserProfileSchema>;
|
||||||
|
export type UserProfile = typeof user_profiles.$inferSelect;
|
||||||
|
|
||||||
|
// Achievements table
|
||||||
|
export const achievements = pgTable("achievements", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
name: text("name").notNull().unique(),
|
||||||
|
description: text("description"),
|
||||||
|
icon: text("icon"),
|
||||||
|
points_reward: integer("points_reward").default(0),
|
||||||
|
badge_color: text("badge_color"),
|
||||||
|
rarity: text("rarity"),
|
||||||
|
xp_reward: integer("xp_reward").default(0),
|
||||||
|
category: varchar("category").notNull().default("milestone"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAchievementSchema = createInsertSchema(achievements);
|
||||||
|
|
||||||
|
export type InsertAchievement = z.infer<typeof insertAchievementSchema>;
|
||||||
|
export type Achievement = typeof achievements.$inferSelect;
|
||||||
|
|
||||||
|
// User Achievements table
|
||||||
|
export const user_achievements = pgTable("user_achievements", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
user_id: varchar("user_id"),
|
||||||
|
achievement_id: varchar("achievement_id"),
|
||||||
|
site_id: text("site_id"),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
unlocked_at: timestamp("unlocked_at").defaultNow(),
|
||||||
|
earned_at: timestamp("earned_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertUserAchievementSchema = createInsertSchema(user_achievements).omit({
|
||||||
|
created_at: true,
|
||||||
|
unlocked_at: true,
|
||||||
|
earned_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertUserAchievement = z.infer<typeof insertUserAchievementSchema>;
|
||||||
|
export type UserAchievement = typeof user_achievements.$inferSelect;
|
||||||
|
|
||||||
|
// Applications table
|
||||||
|
export const applications = pgTable("applications", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
type: text("type").notNull(),
|
||||||
|
full_name: text("full_name").notNull(),
|
||||||
|
email: text("email").notNull(),
|
||||||
|
location: text("location"),
|
||||||
|
role_interest: text("role_interest"),
|
||||||
|
primary_skill: text("primary_skill"),
|
||||||
|
experience_level: text("experience_level"),
|
||||||
|
availability: text("availability"),
|
||||||
|
portfolio_url: text("portfolio_url"),
|
||||||
|
resume_url: text("resume_url"),
|
||||||
|
interests: json("interests").$type<string[] | null>(),
|
||||||
|
message: text("message"),
|
||||||
|
status: text("status").notNull().default("new"),
|
||||||
|
submitted_at: timestamp("submitted_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertApplicationSchema = createInsertSchema(applications).omit({
|
||||||
|
submitted_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertApplication = z.infer<typeof insertApplicationSchema>;
|
||||||
|
export type Application = typeof applications.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Opportunities table
|
||||||
|
export const aethex_opportunities = pgTable("aethex_opportunities", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
description: text("description").notNull(),
|
||||||
|
job_type: text("job_type").notNull(),
|
||||||
|
salary_min: integer("salary_min"),
|
||||||
|
salary_max: integer("salary_max"),
|
||||||
|
experience_level: text("experience_level"),
|
||||||
|
arm_affiliation: text("arm_affiliation").notNull(),
|
||||||
|
posted_by_id: varchar("posted_by_id").notNull(),
|
||||||
|
status: text("status").default("open"),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexOpportunitySchema = createInsertSchema(aethex_opportunities).omit({
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexOpportunity = z.infer<typeof insertAethexOpportunitySchema>;
|
||||||
|
export type AethexOpportunity = typeof aethex_opportunities.$inferSelect;
|
||||||
|
|
||||||
|
// AeThex Events table
|
||||||
|
export const aethex_events = pgTable("aethex_events", {
|
||||||
|
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
|
site_id: varchar("site_id"),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
date: timestamp("date").notNull(), // Note: date is timestamp in Drizzle
|
||||||
|
time: text("time").notNull(), // time as text
|
||||||
|
location: text("location"),
|
||||||
|
capacity: integer("capacity"),
|
||||||
|
image_url: text("image_url"),
|
||||||
|
created_at: timestamp("created_at").defaultNow(),
|
||||||
|
updated_at: timestamp("updated_at"),
|
||||||
|
category: text("category"),
|
||||||
|
price: decimal("price"),
|
||||||
|
featured: boolean("featured"),
|
||||||
|
speakers: json("speakers").$type<string[] | null>(),
|
||||||
|
agenda: json("agenda"),
|
||||||
|
full_description: text("full_description"),
|
||||||
|
map_url: text("map_url"),
|
||||||
|
ticket_types: json("ticket_types"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertAethexEventSchema = createInsertSchema(aethex_events).omit({
|
||||||
|
created_at: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertAethexEvent = z.infer<typeof insertAethexEventSchema>;
|
||||||
|
export type AethexEvent = typeof aethex_events.$inferSelect;
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
|
hmr: {
|
||||||
|
clientPort: 443,
|
||||||
|
},
|
||||||
allowedHosts: true,
|
allowedHosts: true,
|
||||||
fs: {
|
fs: {
|
||||||
strict: true,
|
strict: true,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue