214 lines
6.6 KiB
TypeScript
214 lines
6.6 KiB
TypeScript
import { PrismaClient } from "@prisma/client";
|
|
import slugify from "slugify";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
const CATEGORIES = [
|
|
"3D Modeling",
|
|
"3D Animation",
|
|
"CAD",
|
|
"3D Sculpting",
|
|
"AI Generated Content",
|
|
"Video",
|
|
"Photography",
|
|
"Graphic Design",
|
|
"Illustration",
|
|
];
|
|
|
|
function slug(s: string) {
|
|
return slugify(s, { lower: true, strict: true });
|
|
}
|
|
|
|
async function main() {
|
|
console.log("Seeding categories…");
|
|
for (let i = 0; i < CATEGORIES.length; i++) {
|
|
const name = CATEGORIES[i];
|
|
await prisma.category.upsert({
|
|
where: { slug: slug(name) },
|
|
create: { name, slug: slug(name), sortOrder: i },
|
|
update: { name, sortOrder: i },
|
|
});
|
|
}
|
|
|
|
// Convenience slugs for URL filters (/portfolio/3d, /portfolio/ai, etc.)
|
|
const aliases: Record<string, string> = {
|
|
"3d-modeling": "3d",
|
|
"3d-animation": "animation",
|
|
"ai-generated-content": "ai",
|
|
"graphic-design": "design",
|
|
};
|
|
// We keep canonical slugs; PortfolioView can use short names
|
|
|
|
console.log("Seeding default portfolio views…");
|
|
const cats = await prisma.category.findMany();
|
|
const bySlug = Object.fromEntries(cats.map((c) => [c.slug, c]));
|
|
|
|
async function upsertView(
|
|
name: string,
|
|
viewSlug: string,
|
|
description: string,
|
|
orderedSlugs: string[]
|
|
) {
|
|
const view = await prisma.portfolioView.upsert({
|
|
where: { slug: viewSlug },
|
|
create: { name, slug: viewSlug, description, showOthers: true },
|
|
update: { name, description },
|
|
});
|
|
await prisma.portfolioViewCategory.deleteMany({ where: { viewId: view.id } });
|
|
for (let i = 0; i < orderedSlugs.length; i++) {
|
|
const cat = bySlug[orderedSlugs[i]];
|
|
if (!cat) continue;
|
|
await prisma.portfolioViewCategory.create({
|
|
data: { viewId: view.id, categoryId: cat.id, priority: i },
|
|
});
|
|
}
|
|
}
|
|
|
|
await upsertView(
|
|
"Game Development",
|
|
"game-dev",
|
|
"Work prioritized for game studios and interactive media.",
|
|
["3d-animation", "3d-modeling", "3d-sculpting", "ai-generated-content", "cad"]
|
|
);
|
|
await upsertView(
|
|
"Engineering",
|
|
"engineering",
|
|
"Work prioritized for engineering and product teams.",
|
|
["cad", "3d-modeling", "illustration", "graphic-design"]
|
|
);
|
|
await upsertView(
|
|
"3D",
|
|
"3d",
|
|
"All 3D work front and center.",
|
|
["3d-modeling", "3d-animation", "3d-sculpting", "cad"]
|
|
);
|
|
await upsertView("Animation", "animation", "Animation-focused portfolio.", [
|
|
"3d-animation",
|
|
"video",
|
|
]);
|
|
await upsertView("CAD", "cad", "CAD and technical design.", ["cad", "3d-modeling"]);
|
|
await upsertView("AI", "ai", "AI-generated creative work.", [
|
|
"ai-generated-content",
|
|
"illustration",
|
|
"graphic-design",
|
|
]);
|
|
|
|
// Shorter category filter aliases as views that just prioritize that category
|
|
for (const [canonical, short] of Object.entries(aliases)) {
|
|
if (short === "3d" || short === "animation" || short === "ai") continue;
|
|
await upsertView(bySlug[canonical]?.name ?? short, short, `Filter: ${short}`, [
|
|
canonical,
|
|
]);
|
|
}
|
|
|
|
const existingResume = await prisma.resume.findFirst({ where: { isActive: true } });
|
|
if (!existingResume) {
|
|
console.log("Seeding sample resume…");
|
|
await prisma.resume.create({
|
|
data: {
|
|
isActive: true,
|
|
fullName: "J. Martin",
|
|
title: "3D Artist · Designer · Creative Technologist",
|
|
email: "contact@jmartgraphix.com",
|
|
website: "https://jmartgraphix.com",
|
|
location: "United States",
|
|
summary:
|
|
"Creative professional specializing in 3D modeling, CAD, animation, and visual design. Building polished digital experiences and production-ready assets for games, product visualization, and media.",
|
|
sections: [
|
|
{
|
|
type: "experience",
|
|
title: "Experience",
|
|
items: [
|
|
{
|
|
title: "Independent Creative / jmartgraphix",
|
|
organization: "Freelance",
|
|
location: "Remote",
|
|
startDate: "2018",
|
|
endDate: "Present",
|
|
description:
|
|
"Delivered 3D models, animations, CAD designs, and graphic work for clients across game development, product design, and marketing.",
|
|
highlights: [
|
|
"End-to-end 3D pipeline: modeling, sculpting, texturing, rendering",
|
|
"CAD and product visualization for engineering collaborators",
|
|
"Motion graphics and short-form video production",
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "skills",
|
|
title: "Software & Tools",
|
|
items: [
|
|
{
|
|
group: "3D & CAD",
|
|
skills: [
|
|
"Blender",
|
|
"Maya",
|
|
"ZBrush",
|
|
"SolidWorks",
|
|
"Fusion 360",
|
|
"Substance Painter",
|
|
],
|
|
},
|
|
{
|
|
group: "Design & Video",
|
|
skills: [
|
|
"Photoshop",
|
|
"Illustrator",
|
|
"After Effects",
|
|
"Premiere Pro",
|
|
"DaVinci Resolve",
|
|
],
|
|
},
|
|
{
|
|
group: "Other",
|
|
skills: ["AI image/video tools", "Git", "Web (HTML/CSS)"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "links",
|
|
title: "Links",
|
|
items: [
|
|
{ label: "Portfolio", url: "https://jmartgraphix.com" },
|
|
{ label: "ArtStation", url: "https://jmartgraphix.artstation.com/" },
|
|
{ label: "YouTube", url: "https://www.youtube.com/@samuraijkm" },
|
|
{ label: "Vimeo", url: "https://vimeo.com/jmartgraphix" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
await prisma.siteSetting.upsert({
|
|
where: { key: "site" },
|
|
create: {
|
|
key: "site",
|
|
value: {
|
|
name: "jmartgraphix",
|
|
tagline: "Creative Professional Portfolio",
|
|
about:
|
|
"I create 3D art, technical design, animation, and visual media — from concept through polished delivery.",
|
|
social: {
|
|
artstation: "https://jmartgraphix.artstation.com/",
|
|
youtube: "https://www.youtube.com/@samuraijkm",
|
|
vimeo: "https://vimeo.com/jmartgraphix",
|
|
},
|
|
heroTitle: "",
|
|
heroSubtitle: "3D · Design · Motion · Craft",
|
|
},
|
|
},
|
|
update: {},
|
|
});
|
|
|
|
console.log("Seed complete.");
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(() => prisma.$disconnect());
|