content: publish evidence-led portfolio copy
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
"identity": {
|
||||
"name": "Luca Pîslaru",
|
||||
"role": "System Administrator & Full-Stack Developer",
|
||||
"valueProposition": "Building and operating reliable systems and software."
|
||||
"valueProposition": "I build full-stack software and operate the Linux systems behind it, from offline-first applications to access-controlled self-hosted services and careful data migrations.",
|
||||
"location": { "city": "Bucharest", "country": "Romania", "countryCode": "RO" },
|
||||
"availability": "Based in Bucharest and open to on-site, hybrid, or remote roles locally, as well as international remote opportunities."
|
||||
},
|
||||
"navigation": [
|
||||
{ "id": "start", "label": "Start" },
|
||||
@@ -15,81 +17,98 @@
|
||||
{ "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.",
|
||||
"introduction": "I’m an early-career technologist working across Linux administration and end-to-end application development. I value systems that can be understood, maintained, secured, and recovered: clear access boundaries, deliberate deployments, useful documentation, and software refined through real use.",
|
||||
"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" }
|
||||
{ "label": "Certifications", "value": "Advanced Digital Competencies Certificate, 2025; 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",
|
||||
"id": "trupa-lui-razus",
|
||||
"role": "Full-Stack Developer & Hosting Administrator (Collaborator)",
|
||||
"organization": "Trupa lui Răzuș",
|
||||
"dates": "August 2025-Present",
|
||||
"location": "Bucharest, Romania",
|
||||
"highlights": [
|
||||
"Contribute to full-stack delivery across application and deployment work.",
|
||||
"Support practical DevOps collaboration alongside software development."
|
||||
"Built the theatre group's first owned web presence, replacing a social-media-only discovery path with an indexable website and direct contact options.",
|
||||
"Developed a custom Vue frontend and a headless WordPress publishing workflow that lets an editor publish news and promotional content without changing code.",
|
||||
"Configured role-separated CMS access and restricted administrative routes and endpoints while keeping exact access rules private.",
|
||||
"Manage the domain, cPanel hosting, DNS/TLS configuration, custom .htaccess behavior, and controlled Git/SSH deployments with verification and rollback."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "camera-consultantilor-fiscali",
|
||||
"role": "Green Innovation Intern",
|
||||
"organization": "Camera Consultanților Fiscali",
|
||||
"dates": "Jun 2025 - Aug 2025",
|
||||
"dates": "June 2025-August 2025",
|
||||
"location": "Bucharest, Romania",
|
||||
"highlights": [
|
||||
"Contributed to green innovation work in an internship setting.",
|
||||
"Applied structured problem-solving and digital skills to assigned work."
|
||||
"Contributed to eco-innovation work within the INOVATIV 2030 program.",
|
||||
"Developed and presented sustainable-technology business models."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "isense-solutions",
|
||||
"role": "Data Science Intern",
|
||||
"organization": "iSense Solutions",
|
||||
"dates": "Jun 2025 - Aug 2025",
|
||||
"dates": "June 2025-August 2025",
|
||||
"location": "Bucharest, Romania",
|
||||
"highlights": [
|
||||
"Contributed to data science work in an internship setting.",
|
||||
"Applied analytical and programming skills to assigned tasks."
|
||||
"Processed and analyzed datasets using SPSS and Microsoft Excel.",
|
||||
"Prepared quantitative reports supporting business-development decisions."
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.",
|
||||
"summary": "Application delivery and hosting work with explicit boundaries between public content, authenticated administration, controlled releases, and recovery.",
|
||||
"evidence": [
|
||||
"Linux server administration and service management.",
|
||||
"Docker and Nginx deployment work, including TLS-aware service delivery.",
|
||||
"Operational troubleshooting alongside full-stack development."
|
||||
"Public Vue frontend backed by a role-separated headless WordPress editing workflow.",
|
||||
"Private source control followed by a deliberate SSH deployment, post-release verification, and Git-based rollback path.",
|
||||
"Domain, DNS, TLS, cPanel, and restricted-route administration performed alongside application 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.",
|
||||
"summary": "A self-hosted Linux environment operated as a long-term systems practice: traffic enters through a controlled edge, selected services receive an additional identity boundary, changes are staged deliberately, and important data has local and offsite recovery paths.",
|
||||
"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."
|
||||
"summary": "Route services through Nginx, manage DNS and certificate lifecycle, and use Fail2Ban at exposed entry points."
|
||||
},
|
||||
{
|
||||
"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."
|
||||
"summary": "Place selected services behind Authelia and constrain administrative, filesystem, and container access without publishing the underlying policy."
|
||||
},
|
||||
{
|
||||
"id": "service-lifecycle",
|
||||
"title": "Service lifecycle",
|
||||
"summary": "Deploys containerized services, manages updates, and treats service enrollment and retirement as part of ongoing maintenance."
|
||||
"summary": "Deploy and update services with Docker Compose, including a full platform migration from a Raspberry Pi to a dedicated Linux server."
|
||||
},
|
||||
{
|
||||
"id": "operations-and-maintenance",
|
||||
"title": "Operations",
|
||||
"summary": "Investigates service issues and performs routine maintenance while keeping the platform understandable and manageable."
|
||||
}
|
||||
]
|
||||
"id": "operations-and-recovery",
|
||||
"title": "Operations and recovery",
|
||||
"summary": "Use Btrfs storage, a pre-upgrade backup script, rsync-based copies, and an offsite backup for important data."
|
||||
}
|
||||
],
|
||||
"featuredOperation": {
|
||||
"title": "Migrating a stateful Nextcloud service",
|
||||
"context": "The existing standalone deployment contained much more than files: contacts, calendars, tasks, and collaboration data all had to survive a move to Nextcloud All-in-One.",
|
||||
"actions": [
|
||||
"Aligned application versions and updated installed applications before migration.",
|
||||
"Backed up the source deployment and prepared rollback checkpoints.",
|
||||
"Converted the existing MySQL database to PostgreSQL before the All-in-One migration.",
|
||||
"Exported and imported application state, transferred the data directory, corrected ownership and permissions, and preserved required instance configuration.",
|
||||
"Validated access to the expected files and services before relying on the new deployment."
|
||||
],
|
||||
"outcome": "The migration completed without data loss. The All-in-One deployment has remained Luca's active Nextcloud instance for roughly a year, including storage, synchronization, contacts, calendars, tasks, and Nextcloud Talk."
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"selected": [
|
||||
@@ -98,29 +117,34 @@
|
||||
"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.",
|
||||
"summary": "A solo-built, self-hosted medication-tracking system with an offline-first Flutter client, TypeScript API, PostgreSQL database, and Vue administration interface.",
|
||||
"problem": "A private medication routine needed dependable offline tracking, reminders, history, and synchronization without turning personal data into a public product.",
|
||||
"contribution": "Independently designed, implemented, deployed, and operated the complete mobile app, API, administration interface, database, synchronization model, and self-hosted environment.",
|
||||
"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."
|
||||
"Use Flutter, Riverpod, and Drift/SQLite so the mobile workflow remains useful offline and can queue changes before pulling server updates.",
|
||||
"Use Fastify, TypeScript, Drizzle, and PostgreSQL for the API and synchronized data model, alongside a Vue administration interface.",
|
||||
"Apply authentication, refresh-token, TOTP, backup-code, biometric-lock, and database access-boundary mechanisms while continuing to harden the system through tests and audit findings.",
|
||||
"Run the containerized service behind Nginx/TLS and an additional Authelia access layer.",
|
||||
"Support schedules, as-needed entries, stock tracking, dose history, reminders, background sync, exports, analytics, an Android widget, and English/Romanian localization."
|
||||
],
|
||||
"outcome": "The product remains private; this portfolio presents its scope without a public link or media.",
|
||||
"outcome": "The system has supported Luca's private medication tracking consistently for several months. Real use exposed Android reminder failures; investigation restored dependable delivery, and current work is replacing the remaining recurring behavior with occurrence-specific reconciliation to address duplicate and stale reminders. The project works for its current private use but is not presented as ready for production.",
|
||||
"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.",
|
||||
"maturity": "active",
|
||||
"summary": "A two-person university computer-vision project that processed previously unseen football footage and generated an annotated output video.",
|
||||
"problem": "Build a time-constrained demonstration that could identify football players, referees, and the ball in match footage and explore jersey-number recognition.",
|
||||
"contribution": "Led the project's data and machine-learning work: dataset acquisition and conversion, YOLO configuration and detector training, jersey-classifier design, experimentation, checkpointing, evaluation, and demo integration. A collaborator created the initial repository structure and video-processing pipeline.",
|
||||
"technicalDecisions": [
|
||||
"Separates player detection and tracking from the ongoing jersey-recognition work.",
|
||||
"Keeps the pipeline focused on footage analysis rather than claiming production readiness."
|
||||
"Convert SoccerNet tracking data into a reproducible YOLO training layout and keep training behavior configuration-driven.",
|
||||
"Compare pretrained ResNet, MobileNet, and EfficientNet variants for jersey classification.",
|
||||
"Add class weighting, checkpoint/resume, focal loss, mixup/cutmix, label smoothing, and early stopping as controlled experiments rather than assuming each technique would improve the model.",
|
||||
"Prefer the simpler ResNet34 configuration after an eight-hour ablation set showed that aggressive augmentation increased overfitting and reduced validation performance."
|
||||
],
|
||||
"outcome": "Player detection and tracking are the current focus; jersey recognition remains ongoing work.",
|
||||
"outcome": "The submitted demo processed previously unseen, compressed broadcast footage and retained strong player detection. Measured validation results were 0.942 mAP@0.5 for players, 0.726 for referees, and 0.198 for the ball. The best jersey classifier reached approximately 0.778 validation accuracy, while integrated jersey labels remained inconsistent. The result is a working academic demonstration, not a production tracking system.",
|
||||
"url": "https://github.com/LucaPisl/footy-tracker",
|
||||
"assetIds": []
|
||||
},
|
||||
@@ -129,14 +153,15 @@
|
||||
"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.",
|
||||
"summary": "An agent-based research experiment for observing how local interactions, network structure, and cascade conditions shape group-level opinion patterns.",
|
||||
"problem": "Explore how simplified local rules can produce polarization, rapid cascades, and uneven final distributions.",
|
||||
"contribution": "Built the simulation and analysis views used to inspect modeled runs and compare changes over time.",
|
||||
"technicalDecisions": [
|
||||
"Treats the work as an experimental model rather than a predictive product.",
|
||||
"Uses structured result views to compare changing variables and observed trends."
|
||||
"Keep the work explicitly experimental rather than presenting it as a predictive model of real people.",
|
||||
"Expose polarization, cascade thresholds, graph degree, revolt frequency, and final distributions as inspectable outputs.",
|
||||
"Document performance and network-formation limitations alongside the results."
|
||||
],
|
||||
"outcome": "The project is presented as research exploration, with conclusions limited to the modeled scenarios.",
|
||||
"outcome": "The project produced useful exploratory visualizations and made the model's assumptions and limitations visible; conclusions remain limited to the simulated scenarios.",
|
||||
"url": "https://github.com/LucaPisl/opinion-dynamics-simulation",
|
||||
"assetIds": []
|
||||
}
|
||||
@@ -147,13 +172,13 @@
|
||||
"title": "Private game prototype",
|
||||
"visibility": "private",
|
||||
"maturity": "prototype",
|
||||
"summary": "A private three-week prototype focused on testing a compact gameplay loop.",
|
||||
"summary": "A private three-week prototype built to test a compact gameplay loop under a deliberately small scope.",
|
||||
"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.",
|
||||
"outcome": "No real title, link, video, or identifying media.",
|
||||
"assetIds": []
|
||||
},
|
||||
{
|
||||
@@ -161,13 +186,13 @@
|
||||
"title": "Private healthcare platform",
|
||||
"visibility": "private",
|
||||
"maturity": "active",
|
||||
"summary": "A private web platform presented here only at a high level.",
|
||||
"summary": "A confidentiality-safe summary of Luca's web-interface contribution.",
|
||||
"problem": "A healthcare workflow needed a focused web-based interface.",
|
||||
"contribution": "Contributed to the private platform's web experience.",
|
||||
"contribution": "Contributed to the private platform's web-interface 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.",
|
||||
"outcome": "No product identity, private link, screenshot, or health data.",
|
||||
"assetIds": []
|
||||
},
|
||||
{
|
||||
@@ -175,13 +200,11 @@
|
||||
"title": "Currency Converter",
|
||||
"visibility": "public",
|
||||
"maturity": "learning",
|
||||
"summary": "A modest .NET and Avalonia learning project.",
|
||||
"problem": "Practice a focused desktop workflow for currency conversion.",
|
||||
"summary": "A scoped .NET/Avalonia desktop project integrating external exchange-rate data and practicing typed UI development.",
|
||||
"problem": "Practice a focused desktop workflow for currency conversion with external exchange-rate data.",
|
||||
"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.",
|
||||
"technicalDecisions": ["Uses .NET and Avalonia to explore a typed desktop interface."],
|
||||
"outcome": "A scoped project for developing desktop application fundamentals.",
|
||||
"url": "https://github.com/LucaPisl/CurrencyConverterAvalonia",
|
||||
"assetIds": []
|
||||
},
|
||||
@@ -190,13 +213,13 @@
|
||||
"title": "Linux Modding Guide",
|
||||
"visibility": "public",
|
||||
"maturity": "active",
|
||||
"summary": "Community documentation that shares Linux troubleshooting and technical writing practice.",
|
||||
"summary": "Ongoing community documentation focused on practical Linux troubleshooting and reusable technical explanations.",
|
||||
"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.",
|
||||
"outcome": "An ongoing example of practical technical writing.",
|
||||
"url": "https://github.com/LucaPisl/LinuxModdingGuide",
|
||||
"assetIds": []
|
||||
}
|
||||
@@ -204,35 +227,59 @@
|
||||
},
|
||||
"toolkit": [
|
||||
{
|
||||
"title": "Systems & delivery",
|
||||
"title": "Systems and delivery",
|
||||
"technologies": [
|
||||
{ "name": "Linux" },
|
||||
{ "name": "Docker" },
|
||||
{ "name": "Nginx" },
|
||||
{ "name": "Proxmox / KVM" },
|
||||
{ "name": "cPanel" },
|
||||
{ "name": "DNS" },
|
||||
{ "name": "TLS" },
|
||||
{ "name": "Authelia" },
|
||||
{ "name": "Fail2Ban" },
|
||||
{ "name": "Btrfs" },
|
||||
{ "name": "rsync" },
|
||||
{ "name": "Nextcloud" },
|
||||
{ "name": "Proxmox/KVM" },
|
||||
{ "name": "Git" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Software & data",
|
||||
"title": "Applications",
|
||||
"technologies": [
|
||||
{ "name": "Python" },
|
||||
{ "name": "TypeScript/JavaScript" },
|
||||
{ "name": "Vue.js" },
|
||||
{ "name": "JavaScript" },
|
||||
{ "name": "C# / .NET" },
|
||||
{ "name": "Flutter / Dart" },
|
||||
{ "name": "PostgreSQL / SQL" },
|
||||
{ "name": "Flutter/Dart" },
|
||||
{ "name": "Fastify" },
|
||||
{ "name": "C#/.NET" },
|
||||
{ "name": "Java" },
|
||||
{ "name": "Headless WordPress" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Additional experience",
|
||||
"title": "Data and storage",
|
||||
"technologies": [
|
||||
{ "name": "Java" }
|
||||
{ "name": "PostgreSQL" },
|
||||
{ "name": "SQL" },
|
||||
{ "name": "Drift/SQLite" },
|
||||
{ "name": "Drizzle ORM" },
|
||||
{ "name": "SPSS" },
|
||||
{ "name": "Microsoft Excel" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Machine learning",
|
||||
"technologies": [
|
||||
{ "name": "PyTorch" },
|
||||
{ "name": "Ultralytics/YOLO" },
|
||||
{ "name": "OpenCV" },
|
||||
{ "name": "SoccerNet" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"contact": {
|
||||
"introduction": "I’m open to System Administrator and Full-Stack Developer opportunities in Bucharest, including on-site, hybrid, and remote work, as well as international remote roles.",
|
||||
"email": "luca.pislaru@gmail.com",
|
||||
"social": [
|
||||
{ "service": "GitHub", "url": "https://github.com/LucaPisl" },
|
||||
@@ -243,7 +290,7 @@
|
||||
},
|
||||
"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."
|
||||
"description": "Portfolio of Luca Pîslaru, a Bucharest-based System Administrator and Full-Stack Developer working across Linux infrastructure and end-to-end software delivery."
|
||||
},
|
||||
"soundtrack": {
|
||||
"intro": {
|
||||
|
||||
@@ -6,20 +6,22 @@ This checklist records the Phase 1 public projection. Every item is either inclu
|
||||
| --- | --- | --- |
|
||||
| Identity | Luca Pîslaru | approved |
|
||||
| Identity | System Administrator & Full-Stack Developer | approved |
|
||||
| Identity | Building and operating reliable systems and software | approved |
|
||||
| Identity | Bucharest, Romania | approved |
|
||||
| Identity | On-site, hybrid, and remote availability | 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 |
|
||||
| Profile | Advanced Digital Competencies Certificate and PCEP - Certified Entry-Level Python Programmer | approved |
|
||||
| Experience | Full-Stack Developer & Hosting Administrator (Collaborator) at Trupa lui Răzuș | 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 |
|
||||
| Infrastructure | Nextcloud migration completed without data loss and active for roughly a year | approved |
|
||||
| Infrastructure | Operations and recovery | approved |
|
||||
| Selected build | Private self-hosted medication tracker - private, active, and not ready for production | approved |
|
||||
| Selected build | Footy Tracker collaboration and measured detector results | approved |
|
||||
| Selected build | Opinion Dynamics Simulation | approved |
|
||||
| Additional work | Private game prototype | approved |
|
||||
| Additional work | Private healthcare platform | approved |
|
||||
@@ -47,4 +49,5 @@ This checklist records the Phase 1 public projection. Every item is either inclu
|
||||
| Metadata | Page description | approved |
|
||||
| Metadata | Social preview asset | omit |
|
||||
| Assets | Public asset records | omit |
|
||||
| Assets | Project, toolkit, and metadata asset references | omit |
|
||||
| Assets | Project and social media | pending approval |
|
||||
| Assets | Toolkit and metadata asset references | omit |
|
||||
|
||||
@@ -116,6 +116,7 @@ const projectSchema = z.object({
|
||||
url: httpUrl.optional(),
|
||||
assetIds: z.array(opaqueId),
|
||||
media: z.array(mediaPresentationSchema).optional(),
|
||||
mediaLayout: z.enum(['single', 'portrait-strip', 'chart-pair']).optional(),
|
||||
}).strict().superRefine((project, context) => {
|
||||
if (project.visibility === 'private' && project.url !== undefined) {
|
||||
context.addIssue({ code: 'custom', message: 'A private project may not include a url.', path: ['url'] });
|
||||
@@ -138,6 +139,13 @@ const projectSchema = z.object({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (project.mediaLayout !== undefined && (project.media === undefined || project.media.length === 0)) {
|
||||
context.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Project mediaLayout requires at least one media presentation.',
|
||||
path: ['mediaLayout'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const portfolioContentSchema = z.object({
|
||||
@@ -145,6 +153,12 @@ const portfolioContentSchema = z.object({
|
||||
name: z.literal('Luca Pîslaru'),
|
||||
role: z.literal('System Administrator & Full-Stack Developer'),
|
||||
valueProposition: visibleString,
|
||||
location: z.object({
|
||||
city: visibleString,
|
||||
country: visibleString,
|
||||
countryCode: z.string().regex(/^[A-Z]{2}$/, 'Country code must be a two-letter uppercase code.'),
|
||||
}).strict(),
|
||||
availability: visibleString,
|
||||
}).strict(),
|
||||
navigation: z.array(navigationItemSchema).length(8),
|
||||
profile: z.object({
|
||||
@@ -170,6 +184,12 @@ const portfolioContentSchema = z.object({
|
||||
title: z.literal('Secure self-hosted platform and lifecycle'),
|
||||
summary: visibleString,
|
||||
bands: z.array(z.object({ id: opaqueId, title: visibleString, summary: visibleString }).strict()).length(4),
|
||||
featuredOperation: z.object({
|
||||
title: visibleString,
|
||||
context: visibleString,
|
||||
actions: z.array(visibleString).min(3).max(6),
|
||||
outcome: visibleString,
|
||||
}).strict(),
|
||||
}).strict(),
|
||||
projects: z.object({
|
||||
selected: z.array(projectSchema).length(3),
|
||||
@@ -180,6 +200,7 @@ const portfolioContentSchema = z.object({
|
||||
technologies: z.array(z.object({ name: visibleString, assetId: opaqueId.optional() }).strict()).min(1),
|
||||
}).strict()).min(1),
|
||||
contact: z.object({
|
||||
introduction: visibleString,
|
||||
email: z.email(),
|
||||
social: z.array(z.object({
|
||||
service: z.enum(['GitHub', 'GitLab', 'LinkedIn']),
|
||||
|
||||
@@ -20,6 +20,7 @@ export type SectionLayout =
|
||||
|
||||
export type ProjectVisibility = 'public' | 'private';
|
||||
export type ProjectMaturity = 'learning' | 'experimental' | 'prototype' | 'work-in-progress' | 'active';
|
||||
export type ProjectMediaLayout = 'single' | 'portrait-strip' | 'chart-pair';
|
||||
|
||||
export interface MediaPresentationBase {
|
||||
assetId: string;
|
||||
@@ -84,11 +85,19 @@ export interface InfrastructureEvidenceBand {
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface FeaturedOperation {
|
||||
title: string;
|
||||
context: string;
|
||||
actions: readonly string[];
|
||||
outcome: string;
|
||||
}
|
||||
|
||||
export interface InfrastructureCaseStudy {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
bands: readonly InfrastructureEvidenceBand[];
|
||||
featuredOperation: FeaturedOperation;
|
||||
}
|
||||
|
||||
export interface PublicProject {
|
||||
@@ -104,6 +113,7 @@ export interface PublicProject {
|
||||
url?: string;
|
||||
assetIds: readonly string[];
|
||||
media?: readonly MediaPresentation[];
|
||||
mediaLayout?: ProjectMediaLayout;
|
||||
}
|
||||
|
||||
export interface ToolkitTechnology {
|
||||
@@ -122,6 +132,7 @@ export interface ContactSocialLink {
|
||||
}
|
||||
|
||||
export interface ContactContent {
|
||||
introduction: string;
|
||||
email: string;
|
||||
social: readonly ContactSocialLink[];
|
||||
cvMessage: string;
|
||||
@@ -150,6 +161,8 @@ export interface PortfolioContent {
|
||||
name: 'Luca Pîslaru';
|
||||
role: 'System Administrator & Full-Stack Developer';
|
||||
valueProposition: string;
|
||||
location: { city: string; country: string; countryCode: string };
|
||||
availability: string;
|
||||
};
|
||||
navigation: ReadonlyArray<{ id: SectionId; label: string }>;
|
||||
profile: { introduction: string; screeningFacts: ReadonlyArray<{ label: string; value: string }> };
|
||||
|
||||
16
tests/fixtures/portfolio-content.valid.json
vendored
16
tests/fixtures/portfolio-content.valid.json
vendored
@@ -2,7 +2,9 @@
|
||||
"identity": {
|
||||
"name": "Luca Pîslaru",
|
||||
"role": "System Administrator & Full-Stack Developer",
|
||||
"valueProposition": "Building and operating reliable systems and software."
|
||||
"valueProposition": "Building and operating reliable systems and software.",
|
||||
"location": { "city": "Bucharest", "country": "Romania", "countryCode": "RO" },
|
||||
"availability": "Based in Bucharest and open to remote roles."
|
||||
},
|
||||
"navigation": [
|
||||
{ "id": "start", "label": "Start" },
|
||||
@@ -41,7 +43,13 @@
|
||||
{ "id": "identity-access", "title": "Identity and access", "summary": "Protected service access." },
|
||||
{ "id": "service-lifecycle", "title": "Service lifecycle", "summary": "Maintained representative services." },
|
||||
{ "id": "operations", "title": "Operations", "summary": "Resolved routine service issues." }
|
||||
]
|
||||
],
|
||||
"featuredOperation": {
|
||||
"title": "Synthetic stateful service migration",
|
||||
"context": "A representative migration preserved essential application state.",
|
||||
"actions": ["Prepared versions.", "Created recovery checkpoints.", "Validated the new service."],
|
||||
"outcome": "The synthetic migration retained expected service data."
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"selected": [
|
||||
@@ -55,7 +63,8 @@
|
||||
"contribution": "Designed and maintained the application.",
|
||||
"technicalDecisions": ["Used a synchronised client and server design."],
|
||||
"outcome": "The work remains private.",
|
||||
"assetIds": ["selected-build-one"]
|
||||
"assetIds": ["selected-build-one"],
|
||||
"media": [{ "kind": "image", "assetId": "selected-build-one", "alt": "Synthetic project evidence." }]
|
||||
},
|
||||
{
|
||||
"id": "public-build-two",
|
||||
@@ -101,6 +110,7 @@
|
||||
"technologies": [{ "name": "Linux", "assetId": "toolkit-linux" }]
|
||||
}],
|
||||
"contact": {
|
||||
"introduction": "Open to System Administrator and Full-Stack Developer opportunities.",
|
||||
"email": "hello@example.test",
|
||||
"social": [{ "service": "GitHub", "url": "https://github.com/example" }],
|
||||
"cvMessage": "For a current CV or role-specific information, contact me directly."
|
||||
|
||||
@@ -15,10 +15,23 @@ describe('approved public portfolio projection', () => {
|
||||
new URL('file:///prepared-assets/'),
|
||||
);
|
||||
|
||||
expect(content.identity).toEqual(expect.objectContaining({
|
||||
expect(content.identity).toMatchObject({
|
||||
name: 'Luca Pîslaru',
|
||||
role: 'System Administrator & Full-Stack Developer',
|
||||
}));
|
||||
location: { city: 'Bucharest', country: 'Romania', countryCode: 'RO' },
|
||||
});
|
||||
expect(content.experience[0]).toMatchObject({
|
||||
organization: 'Trupa lui Răzuș',
|
||||
role: 'Full-Stack Developer & Hosting Administrator (Collaborator)',
|
||||
});
|
||||
expect(content.infrastructure.featuredOperation.outcome).toMatch(/without data loss.*roughly a year/iu);
|
||||
expect(content.projects.selected.map(({ id }) => id)).toEqual([
|
||||
'private-medication-tracker',
|
||||
'footy-tracker',
|
||||
'opinion-dynamics-simulation',
|
||||
]);
|
||||
expect(content.projects.selected[1]?.contribution).toMatch(/collaborator/iu);
|
||||
expect(content.projects.selected[1]?.outcome).toMatch(/0\.942 mAP@0\.5.*0\.198/iu);
|
||||
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);
|
||||
@@ -59,7 +72,9 @@ describe('approved public portfolio projection', () => {
|
||||
canonicalUrl: 'https://portfolio.localhost/',
|
||||
buildId: 'preview-semantic-v1',
|
||||
});
|
||||
expect(JSON.stringify(content)).not.toMatch(/Arch Linux|CV download|heatmap|expert|percent/i);
|
||||
expect(JSON.stringify(content)).not.toMatch(
|
||||
/Arch Linux|Mihai Razus PFA|DevOps collaborator|CV download|heatmap|fully secure|production-ready|expert|percent/iu,
|
||||
);
|
||||
});
|
||||
|
||||
it('publishes only the four verified owner repositories and no private project links', async () => {
|
||||
|
||||
@@ -37,6 +37,12 @@ const infrastructure: InfrastructureCaseStudy = {
|
||||
summary: "Routine maintenance and investigation.",
|
||||
},
|
||||
],
|
||||
featuredOperation: {
|
||||
title: "Synthetic stateful service migration",
|
||||
context: "A representative migration preserved application state.",
|
||||
actions: ["Prepared versions.", "Created recovery checkpoints.", "Validated the new service."],
|
||||
outcome: "The synthetic migration retained expected data.",
|
||||
},
|
||||
};
|
||||
|
||||
const privateProject: PublicProject = {
|
||||
@@ -159,6 +165,7 @@ describe("semantic section components", () => {
|
||||
|
||||
it("renders a visible mailto contact and safe verified social links", () => {
|
||||
const contact: ContactContent = {
|
||||
introduction: "Open to System Administrator and Full-Stack Developer opportunities.",
|
||||
email: "hello@example.test",
|
||||
cvMessage:
|
||||
"For a current CV or role-specific information, contact me directly.",
|
||||
|
||||
@@ -15,11 +15,23 @@ const validContent = fixture('portfolio-content.valid.json');
|
||||
const validManifest = fixture('asset-manifest.valid.json');
|
||||
const validSite = fixture('site-config.preview.json');
|
||||
type MutableContentFixture = {
|
||||
identity: {
|
||||
location: { city: string; country: string; countryCode: string };
|
||||
availability: string;
|
||||
};
|
||||
navigation: Array<{ id: string }>;
|
||||
profile: { introduction: string };
|
||||
contact: { social: Array<{ service: string; url: string }> };
|
||||
contact: { introduction: string; social: Array<{ service: string; url: string }> };
|
||||
infrastructure: { featuredOperation: { actions: string[] } };
|
||||
projects: {
|
||||
selected: Array<{ visibility: string; url?: string; assetIds: string[]; title: string }>;
|
||||
selected: Array<{
|
||||
visibility: string;
|
||||
url?: string;
|
||||
assetIds: string[];
|
||||
title: string;
|
||||
media?: Array<{ assetId: string; alt: string; kind: 'image' }>;
|
||||
mediaLayout?: 'single' | 'portrait-strip' | 'chart-pair';
|
||||
}>;
|
||||
additional: Array<{ assetIds: string[] }>;
|
||||
};
|
||||
toolkit: Array<{ technologies: Array<{ assetId?: string }> }>;
|
||||
@@ -41,6 +53,25 @@ describe('content schemas', () => {
|
||||
expect(parseSiteConfig(validSite).environment).toBe('preview');
|
||||
});
|
||||
|
||||
it('requires public availability, city-level location, featured operations evidence, and contact copy', () => {
|
||||
const content = cloneContent();
|
||||
expect(content.identity.location).toEqual({ city: 'Bucharest', country: 'Romania', countryCode: 'RO' });
|
||||
expect(content.identity.availability).toMatch(/Bucharest.*remote/iu);
|
||||
expect(content.infrastructure.featuredOperation.actions.length).toBeGreaterThanOrEqual(3);
|
||||
expect(content.contact.introduction).toMatch(/System Administrator.*Full-Stack Developer/iu);
|
||||
|
||||
delete (content.identity as Partial<typeof content.identity>).availability;
|
||||
expect(() => parsePortfolioContent(content)).toThrow();
|
||||
});
|
||||
|
||||
it('accepts only the approved project media layouts', () => {
|
||||
const content = cloneContent();
|
||||
content.projects.selected[0]!.mediaLayout = 'portrait-strip';
|
||||
expect(() => parsePortfolioContent(content)).not.toThrow();
|
||||
content.projects.selected[0]!.mediaLayout = 'carousel' as never;
|
||||
expect(() => parsePortfolioContent(content)).toThrow(/mediaLayout|unrecognized/iu);
|
||||
});
|
||||
|
||||
it('accepts an empty manifest before Phase 3 assets are prepared', () => {
|
||||
expect(parsePublicAssetManifest({ assets: [] })).toEqual({ assets: [] });
|
||||
});
|
||||
@@ -117,6 +148,7 @@ describe('content schemas', () => {
|
||||
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.projects.selected[0]!.media;
|
||||
delete content.toolkit[0]!.technologies[0]!.assetId;
|
||||
delete content.metadata.socialPreviewAssetId;
|
||||
expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/'))).not.toThrow();
|
||||
@@ -126,6 +158,7 @@ describe('content schemas', () => {
|
||||
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.projects.selected[0]!.media;
|
||||
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);
|
||||
@@ -205,6 +238,7 @@ describe('content schemas', () => {
|
||||
it('rejects an asset reference absent from the manifest', () => {
|
||||
const content = cloneContent();
|
||||
content.projects.selected[0]!.assetIds = ['missing-asset'];
|
||||
content.projects.selected[0]!.media![0]!.assetId = 'missing-asset';
|
||||
expect(() => validateProductionInputs(content, validManifest, validSite, new URL('file:///prepared/')))
|
||||
.toThrow(/unmanifested asset/i);
|
||||
});
|
||||
@@ -217,6 +251,7 @@ describe('content schemas', () => {
|
||||
|
||||
const unreferencedManifestAsset = cloneContent();
|
||||
unreferencedManifestAsset.projects.selected[0]!.assetIds = [];
|
||||
delete unreferencedManifestAsset.projects.selected[0]!.media;
|
||||
expect(() => validateProductionInputs(unreferencedManifestAsset, validManifest, validSite, new URL('file:///prepared/')))
|
||||
.toThrow(/unreferenced manifest asset/i);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user