content: add approved public portfolio projection

This commit is contained in:
2026-09-02 01:00:36 +03:00
parent 6428b5dcd6
commit 49a4f75c8d
11 changed files with 460 additions and 11 deletions

5
config/site.preview.json Normal file
View File

@@ -0,0 +1,5 @@
{
"environment": "preview",
"canonicalUrl": "https://portfolio.localhost/",
"buildId": "preview-semantic-v1"
}

View File

@@ -0,0 +1,3 @@
{
"assets": []
}

View File

@@ -0,0 +1,245 @@
{
"identity": {
"name": "Luca Pîslaru",
"role": "System Administrator & Full-Stack Developer",
"valueProposition": "Building and operating reliable systems and software."
},
"navigation": [
{ "id": "start", "label": "Start" },
{ "id": "profile", "label": "Profile" },
{ "id": "experience", "label": "Experience" },
{ "id": "infrastructure", "label": "Infrastructure" },
{ "id": "selected-builds", "label": "Selected builds" },
{ "id": "additional-work", "label": "Additional work" },
{ "id": "toolkit", "label": "Toolkit" },
{ "id": "contact", "label": "Contact" }
],
"profile": {
"introduction": "I work across Linux infrastructure and full-stack delivery, pairing practical operations with software built to be understood, maintained, and improved over time.",
"screeningFacts": [
{ "label": "Education", "value": "Bachelor's degree in Business Management IT, Romanian-American University, 2023–2026" },
{ "label": "Languages", "value": "Romanian (native), English (C2), French (A1)" },
{ "label": "Certification", "value": "PCEP — Certified Entry-Level Python Programmer, 2022" }
]
},
"experience": [
{
"id": "mihai-razus-pfa",
"role": "Full-Stack Developer & DevOps collaborator",
"organization": "Mihai Razus PFA",
"dates": "Aug 2025 – Present",
"highlights": [
"Contribute to full-stack delivery across application and deployment work.",
"Support practical DevOps collaboration alongside software development."
]
},
{
"id": "camera-consultantilor-fiscali",
"role": "Green Innovation Intern",
"organization": "Camera Consultanților Fiscali",
"dates": "Jun 2025 – Aug 2025",
"highlights": [
"Contributed to green innovation work in an internship setting.",
"Applied structured problem-solving and digital skills to assigned work."
]
},
{
"id": "isense-solutions",
"role": "Data Science Intern",
"organization": "iSense Solutions",
"dates": "Jun 2025 – Aug 2025",
"highlights": [
"Contributed to data science work in an internship setting.",
"Applied analytical and programming skills to assigned tasks."
]
}
],
"systemsOperations": {
"id": "professional-systems-operations",
"title": "Professional systems operations",
"summary": "Professional work that connects application delivery with the Linux environments and deployment practices that support it.",
"evidence": [
"Linux server administration and service management.",
"Docker and Nginx deployment work, including TLS-aware service delivery.",
"Operational troubleshooting alongside full-stack development."
]
},
"infrastructure": {
"id": "self-hosted-platform-lifecycle",
"title": "Secure self-hosted platform and lifecycle",
"summary": "A personal Linux platform operated as a compact systems case study: secure ingress, deliberate access controls, service lifecycle discipline, and routine maintenance.",
"bands": [
{
"id": "ingress-and-tls",
"title": "Ingress and TLS",
"summary": "Routes services through Nginx with certificate issuance and renewal managed through Let's Encrypt and Certbot; exposed entry points receive fail2ban protection."
},
{
"id": "identity-and-access",
"title": "Identity and access",
"summary": "Uses an authentication layer for protected services, with passkeys, two-factor authentication, and recovery considered without publishing policy details."
},
{
"id": "service-lifecycle",
"title": "Service lifecycle",
"summary": "Deploys containerized services, manages updates, and treats service enrollment and retirement as part of ongoing maintenance."
},
{
"id": "operations-and-maintenance",
"title": "Operations",
"summary": "Investigates service issues and performs routine maintenance while keeping the platform understandable and manageable."
}
]
},
"projects": {
"selected": [
{
"id": "private-medication-tracker",
"title": "Private self-hosted medication tracker",
"visibility": "private",
"maturity": "active",
"summary": "A private multi-device medication system for schedules, stock awareness, adherence logging, and shared administration.",
"problem": "Medication routines need a dependable way to handle scheduled and as-needed entries across devices.",
"contribution": "Designed and built the private client, server, synchronization, and administrative workflow.",
"technicalDecisions": [
"Supports profiles, scheduled and as-needed medication, stock tracking, notifications, and logged activity.",
"Keeps a server component and administrative interface alongside the multi-device experience."
],
"outcome": "The product remains private; this portfolio presents its scope without a public link or media.",
"assetIds": []
},
{
"id": "footy-tracker",
"title": "Footy Tracker",
"visibility": "public",
"maturity": "work-in-progress",
"summary": "A work-in-progress computer-vision pipeline for football footage.",
"problem": "Football footage needs a repeatable way to detect and follow players over time.",
"contribution": "Built the player detection and tracking pipeline and continue to explore jersey recognition.",
"technicalDecisions": [
"Separates player detection and tracking from the ongoing jersey-recognition work.",
"Keeps the pipeline focused on footage analysis rather than claiming production readiness."
],
"outcome": "Player detection and tracking are the current focus; jersey recognition remains ongoing work.",
"assetIds": []
},
{
"id": "opinion-dynamics-simulation",
"title": "Opinion Dynamics Simulation",
"visibility": "public",
"maturity": "experimental",
"summary": "An experimental agent-based simulation for exploring how group opinions change.",
"problem": "Explore how local interactions can produce different group-level opinion patterns.",
"contribution": "Built the experimental model and the result views used to inspect its runs.",
"technicalDecisions": [
"Treats the work as an experimental model rather than a predictive product.",
"Uses structured result views to compare changing variables and observed trends."
],
"outcome": "The project is presented as research exploration, with conclusions limited to the modeled scenarios.",
"assetIds": []
}
],
"additional": [
{
"id": "private-game-prototype",
"title": "Private game prototype",
"visibility": "private",
"maturity": "prototype",
"summary": "A private three-week prototype focused on testing a compact gameplay loop.",
"problem": "Test a game concept quickly while keeping the scope deliberately small.",
"contribution": "Built and iterated on the prototype during a three-week development window.",
"technicalDecisions": [
"Focused the work on a short gameplay loop rather than a full production release."
],
"outcome": "The prototype remains private and is described without a public link or media.",
"assetIds": []
},
{
"id": "private-healthcare-platform",
"title": "Private healthcare platform",
"visibility": "private",
"maturity": "active",
"summary": "A private web platform presented here only at a high level.",
"problem": "A healthcare workflow needed a focused web-based interface.",
"contribution": "Contributed to the private platform's web experience.",
"technicalDecisions": [
"Keeps product details and any identifying interface data outside the public portfolio."
],
"outcome": "The platform remains private and has no public link or media.",
"assetIds": []
},
{
"id": "currency-converter",
"title": "Currency Converter",
"visibility": "public",
"maturity": "learning",
"summary": "A modest .NET and Avalonia learning project.",
"problem": "Practice a focused desktop workflow for currency conversion.",
"contribution": "Implemented the application as a scoped learning exercise.",
"technicalDecisions": [
"Uses .NET and Avalonia to explore a typed desktop interface."
],
"outcome": "A deliberately modest project for developing desktop application fundamentals.",
"assetIds": []
},
{
"id": "linux-modding-guide",
"title": "Linux Modding Guide",
"visibility": "public",
"maturity": "active",
"summary": "Community documentation that shares Linux troubleshooting and technical writing practice.",
"problem": "Useful Linux troubleshooting knowledge benefits from clear, reusable documentation.",
"contribution": "Wrote and maintained community-facing technical guidance.",
"technicalDecisions": [
"Favors practical explanations that help readers understand and resolve issues."
],
"outcome": "An ongoing example of technical writing without relying on popularity counts.",
"assetIds": []
}
]
},
"toolkit": [
{
"title": "Systems & delivery",
"technologies": [
{ "name": "Linux" },
{ "name": "Docker" },
{ "name": "Nginx" },
{ "name": "Proxmox / KVM" },
{ "name": "Git" }
]
},
{
"title": "Software & data",
"technologies": [
{ "name": "Python" },
{ "name": "Vue.js" },
{ "name": "JavaScript" },
{ "name": "C# / .NET" },
{ "name": "Flutter / Dart" },
{ "name": "PostgreSQL / SQL" },
{ "name": "Headless WordPress" }
]
},
{
"title": "Additional experience",
"technologies": [
{ "name": "Java" }
]
}
],
"contact": {
"email": "luca.pislaru@gmail.com",
"social": [
{ "service": "GitHub", "url": "https://github.com/LucaPisl" },
{ "service": "GitLab", "url": "https://gitlab.com/luca_pisl" },
{ "service": "LinkedIn", "url": "https://linkedin.com/in/luca-pisl" }
],
"cvMessage": "For a current CV or role-specific information, contact me directly."
},
"metadata": {
"title": "Luca Pîslaru — System Administrator & Full-Stack Developer",
"description": "Portfolio of Luca Pîslaru, a System Administrator & Full-Stack Developer building and operating reliable systems and software."
},
"sceneDescription": "A restrained retro-futuristic night drive supports the reading journey while the portfolio content remains fully available as a semantic document."
}

View File

@@ -0,0 +1,50 @@
# Public content checklist
This checklist records the Phase 1 public projection. Every item is either included as approved public content or omitted from this projection.
| Area | Public item | Status |
| --- | --- | --- |
| Identity | Luca Pîslaru | approved |
| Identity | System Administrator & Full-Stack Developer | approved |
| Identity | Building and operating reliable systems and software | approved |
| Profile | Education | approved |
| Profile | Languages | approved |
| Profile | PCEP — Certified Entry-Level Python Programmer | approved |
| Experience | Full-Stack Developer & DevOps collaborator at Mihai Razus PFA | approved |
| Experience | Green Innovation Intern at Camera Consultanților Fiscali | approved |
| Experience | Data Science Intern at iSense Solutions | approved |
| Systems operations | Professional systems operations | approved |
| Infrastructure | Ingress and TLS | approved |
| Infrastructure | Identity and access | approved |
| Infrastructure | Service lifecycle | approved |
| Infrastructure | Operations | approved |
| Selected build | Private self-hosted medication tracker | approved |
| Selected build | Footy Tracker | approved |
| Selected build | Opinion Dynamics Simulation | approved |
| Additional work | Private game prototype | approved |
| Additional work | Private healthcare platform | approved |
| Additional work | Currency Converter | approved |
| Additional work | Linux Modding Guide | approved |
| Toolkit | Linux | approved |
| Toolkit | Docker | approved |
| Toolkit | Nginx | approved |
| Toolkit | Proxmox / KVM | approved |
| Toolkit | Git | approved |
| Toolkit | Python | approved |
| Toolkit | Vue.js | approved |
| Toolkit | JavaScript | approved |
| Toolkit | C# / .NET | approved |
| Toolkit | Flutter / Dart | approved |
| Toolkit | PostgreSQL / SQL | approved |
| Toolkit | Headless WordPress | approved |
| Toolkit | Java | approved |
| Contact | luca.pislaru@gmail.com | approved |
| Contact | GitHub | approved |
| Contact | GitLab | approved |
| Contact | LinkedIn | approved |
| Contact | Current or role-specific CV by direct contact | approved |
| Metadata | Page title | approved |
| Metadata | Page description | approved |
| Metadata | Social preview asset | omit |
| Assets | Public asset records | omit |
| Assets | Project, toolkit, and metadata asset references | omit |

View File

@@ -14,7 +14,8 @@
"build:client": "vite build --outDir dist/client --manifest",
"build:ssr": "vite build --ssr src/entry-server.ts --outDir dist-ssr",
"prerender": "node scripts/prerender.mjs",
"build:preview": "npm run typecheck && npm run test:unit && npm run build:client && npm run build:ssr && npm run prerender -- --config config/site.preview.json"
"prepare:preview-assets": "node scripts/materialize-empty-asset-set.mjs",
"build:preview": "npm run prepare:preview-assets && npm run typecheck && npm run test:unit && npm run build:client && npm run build:ssr && npm run prerender -- --config config/site.preview.json"
},
"keywords": [],
"author": "",

View File

@@ -0,0 +1,33 @@
import { lstat, mkdir, readFile, readdir } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const manifestPath = resolve(repositoryRoot, 'content/public/asset-manifest.json');
const preparedAssetsPath = resolve(repositoryRoot, 'inputs/prepared-assets');
const readEmptyManifest = async () => {
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
if (Object.keys(manifest).length !== 1 || !Array.isArray(manifest.assets) || manifest.assets.length !== 0) {
throw new Error('Phase 1 asset manifest must contain exactly zero assets.');
}
};
const ensureEmptyPreparedAssetDirectory = async () => {
try {
const stats = await lstat(preparedAssetsPath);
if (stats.isSymbolicLink() || !stats.isDirectory()) {
throw new Error('Prepared asset path must be a non-symlink directory.');
}
} catch (error) {
if ((error).code !== 'ENOENT') throw error;
await mkdir(preparedAssetsPath, { recursive: true });
}
if ((await readdir(preparedAssetsPath)).length !== 0) {
throw new Error('Prepared asset directory must be empty during Phase 1.');
}
};
await readEmptyManifest();
await ensureEmptyPreparedAssetDirectory();

View File

@@ -9,6 +9,11 @@ const socialHosts = {
GitLab: 'gitlab.com',
LinkedIn: 'linkedin.com',
} as const;
const approvedPrivateProjectTitles = [
'Private self-hosted medication tracker',
'Private healthcare platform',
'Private game prototype',
] as const;
const hasUnpairedSurrogate = (value: string): boolean => {
for (let index = 0; index < value.length; index += 1) {
@@ -69,8 +74,8 @@ const projectSchema = z.object({
if (project.visibility === 'private' && project.url !== undefined) {
context.addIssue({ code: 'custom', message: 'A private project may not include a url.', path: ['url'] });
}
if (project.visibility === 'private' && project.title !== 'Private project') {
context.addIssue({ code: 'custom', message: 'Private project title must be generic.', path: ['title'] });
if (project.visibility === 'private' && !approvedPrivateProjectTitles.includes(project.title as (typeof approvedPrivateProjectTitles)[number])) {
context.addIssue({ code: 'custom', message: 'Private project title must use an approved generic title.', path: ['title'] });
}
});
@@ -111,7 +116,7 @@ const portfolioContentSchema = z.object({
}).strict(),
toolkit: z.array(z.object({
title: visibleString,
technologies: z.array(z.object({ name: visibleString, assetId: opaqueId }).strict()).min(1),
technologies: z.array(z.object({ name: visibleString, assetId: opaqueId.optional() }).strict()).min(1),
}).strict()).min(1),
contact: z.object({
email: z.email(),
@@ -131,7 +136,7 @@ const portfolioContentSchema = z.object({
metadata: z.object({
title: visibleString,
description: visibleString,
socialPreviewAssetId: opaqueId,
socialPreviewAssetId: opaqueId.optional(),
}).strict(),
sceneDescription: visibleString,
}).strict().superRefine((content, context) => {
@@ -192,7 +197,7 @@ const publicAssetSchema = z.object({
context.addIssue({ code: 'custom', message: 'Video assets require dimensions and duration.', path: ['durationMs'] });
}
});
const publicAssetManifestSchema = z.object({ assets: z.array(publicAssetSchema).min(1) }).strict().superRefine((manifest, context) => {
const publicAssetManifestSchema = z.object({ assets: z.array(publicAssetSchema) }).strict().superRefine((manifest, context) => {
const ids = manifest.assets.map((asset) => asset.id);
if (new Set(ids).size !== ids.length) {
context.addIssue({ code: 'custom', message: 'Duplicate asset ID in manifest.', path: ['assets'] });
@@ -233,8 +238,9 @@ export const validateProductionInputs = (
const referencedAssetIds = [
...content.projects.selected.flatMap((project) => project.assetIds),
...content.projects.additional.flatMap((project) => project.assetIds),
...content.toolkit.flatMap((group) => group.technologies.map((technology) => technology.assetId)),
content.metadata.socialPreviewAssetId,
...content.toolkit.flatMap((group) => group.technologies.flatMap((technology) =>
technology.assetId === undefined ? [] : [technology.assetId])),
...(content.metadata.socialPreviewAssetId === undefined ? [] : [content.metadata.socialPreviewAssetId]),
];
for (const assetId of referencedAssetIds) {
if (!manifestIds.has(assetId)) {

View File

@@ -56,7 +56,7 @@ export interface PublicProject {
export interface ToolkitTechnology {
name: string;
assetId: string;
assetId?: string;
}
export interface ToolkitGroup {
@@ -78,7 +78,7 @@ export interface ContactContent {
export interface MetadataContent {
title: string;
description: string;
socialPreviewAssetId: string;
socialPreviewAssetId?: string;
}
export interface PortfolioContent {

View File

@@ -47,7 +47,7 @@
"selected": [
{
"id": "private-build-one",
"title": "Private project",
"title": "Private self-hosted medication tracker",
"visibility": "private",
"maturity": "active",
"summary": "A private multi-device application.",

View File

@@ -0,0 +1,64 @@
import { execFile as execFileCallback } from 'node:child_process';
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { promisify } from 'node:util';
import { afterEach, describe, expect, it } from 'vitest';
import { parsePortfolioContent, parsePublicAssetManifest, parseSiteConfig } from '@/content/schemas';
const execFile = promisify(execFileCallback);
const repositoryRoot = process.cwd();
const readJson = async (relativePath: string): Promise<unknown> =>
JSON.parse(await readFile(`${repositoryRoot}/${relativePath}`, 'utf8')) as unknown;
const preparedAssetsPath = `${repositoryRoot}/inputs/prepared-assets`;
afterEach(async () => {
await rm(preparedAssetsPath, { recursive: true, force: true });
});
describe('approved public portfolio projection', () => {
it('contains approved identity and balanced evidence without prohibited surfaces', async () => {
const content = parsePortfolioContent(await readJson('content/public/portfolio-content.json'));
const manifest = parsePublicAssetManifest(await readJson('content/public/asset-manifest.json'));
const site = parseSiteConfig(await readJson('config/site.preview.json'));
expect(content.identity).toEqual(expect.objectContaining({
name: 'Luca Pîslaru',
role: 'System Administrator & Full-Stack Developer',
}));
expect(content.systemsOperations.title).toBe('Professional systems operations');
expect(content.infrastructure.title).toBe('Secure self-hosted platform and lifecycle');
expect(content.projects.selected).toHaveLength(3);
expect(content.projects.selected.filter((project) => project.visibility === 'private')
.every((project) => project.url === undefined)).toBe(true);
expect(content.projects.selected.find((project) => project.visibility === 'private')?.title)
.toBe('Private self-hosted medication tracker');
expect(content.projects.additional.find((project) => project.id === 'private-game-prototype')?.title)
.toBe('Private game prototype');
expect([...content.projects.selected, ...content.projects.additional]
.every((project) => project.assetIds.length === 0)).toBe(true);
expect(content.toolkit.flatMap((group) => group.technologies).map((technology) => technology.name))
.toEqual(expect.arrayContaining(['Linux', 'Docker', 'Python', 'Vue.js', 'Java']));
expect(content.contact).toEqual(expect.objectContaining({ email: 'luca.pislaru@gmail.com' }));
expect(content.contact.social.map((link) => link.url)).toEqual([
'https://github.com/LucaPisl',
'https://gitlab.com/luca_pisl',
'https://linkedin.com/in/luca-pisl',
]);
expect(manifest.assets).toEqual([]);
expect(site).toEqual({
environment: 'preview',
canonicalUrl: 'https://portfolio.localhost/',
buildId: 'preview-semantic-v1',
});
expect(JSON.stringify(content)).not.toMatch(/Arch Linux|CV download|heatmap|expert|percent/i);
});
it('materializes an empty prepared-asset directory and rejects unexpected members', async () => {
await execFile(process.execPath, ['scripts/materialize-empty-asset-set.mjs'], { cwd: repositoryRoot });
expect(await readdir(preparedAssetsPath)).toEqual([]);
await mkdir(preparedAssetsPath, { recursive: true });
await writeFile(`${preparedAssetsPath}/unexpected.webp`, 'not an asset');
await expect(execFile(process.execPath, ['scripts/materialize-empty-asset-set.mjs'], { cwd: repositoryRoot }))
.rejects.toThrow(/must be empty/i);
});
});

View File

@@ -20,7 +20,10 @@ type MutableContentFixture = {
contact: { social: Array<{ service: string; url: string }> };
projects: {
selected: Array<{ visibility: string; url?: string; assetIds: string[]; title: string }>;
additional: Array<{ assetIds: string[] }>;
};
toolkit: Array<{ technologies: Array<{ assetId?: string }> }>;
metadata: { socialPreviewAssetId?: string };
};
const cloneContent = (): MutableContentFixture => structuredClone(validContent) as MutableContentFixture;
@@ -32,6 +35,45 @@ describe('content schemas', () => {
expect(parseSiteConfig(validSite).environment).toBe('preview');
});
it('accepts an empty manifest before Phase 3 assets are prepared', () => {
expect(parsePublicAssetManifest({ assets: [] })).toEqual({ assets: [] });
});
it('accepts the approved generic private project titles', () => {
for (const title of [
'Private self-hosted medication tracker',
'Private healthcare platform',
'Private game prototype',
]) {
const content = cloneContent();
content.projects.selected[0]!.title = title;
expect(() => parsePortfolioContent(content)).not.toThrow();
}
});
it('permits omitted Phase 3 asset references while retaining supplied-reference checks', () => {
const content = cloneContent();
content.projects.selected[0]!.title = 'Private self-hosted medication tracker';
for (const project of [...content.projects.selected, ...content.projects.additional]) project.assetIds = [];
delete content.toolkit[0]!.technologies[0]!.assetId;
delete content.metadata.socialPreviewAssetId;
expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/'))).not.toThrow();
});
it('rejects supplied toolkit and metadata asset references absent from the manifest', () => {
const content = cloneContent();
content.projects.selected[0]!.title = 'Private self-hosted medication tracker';
for (const project of [...content.projects.selected, ...content.projects.additional]) project.assetIds = [];
content.toolkit[0]!.technologies[0]!.assetId = 'missing-toolkit-asset';
expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/')))
.toThrow(/unmanifested asset reference: missing-toolkit-asset/i);
delete content.toolkit[0]!.technologies[0]!.assetId;
content.metadata.socialPreviewAssetId = 'missing-social-preview';
expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/')))
.toThrow(/unmanifested asset reference: missing-social-preview/i);
});
it('rejects a public link on a private project', () => {
const input = cloneContent();
input.projects.selected[0]!.visibility = 'private';