From 2ce13481d07c40245df57d16bd3d7eb7fcc45a90 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Sun, 9 Nov 2025 11:42:52 +0000 Subject: [PATCH] feat: enhance CV with project title links, experience durations, certifications, and improved navigation ## Project Title Links - Add projectName and projectDesc fields to Project struct - Split project titles to make only project name clickable - Update template logic for conditional title rendering - Apply changes to both English and Spanish versions ## Experience Duration Display - Restore duration calculation display in experience section - Move duration from date line to after company name - Style duration in light gray (#999) for subtle appearance - Calculate durations dynamically (e.g., "4 years 10 months") ## Certifications Section Enhancement - Add Codecademy Certifications (2022-2024) with AI Transformers and React courses - Add LinkedIn Learning Certifications (2019-2020) with 5 professional courses - Implement colored icon system with brand colors (purple, cyan, green, etc.) - Use responsibilities format matching Third Party Contributions layout - Reorder courses chronologically (most recent first) ## localStorage Improvements - Save CV length preference (short/long) - Save logos visibility preference (show/hide) - Save theme preference (default/clean) - Restore all preferences on page load ## Navigation UX Enhancements - Fix scroll positioning to show sections below action bar - Add keepHeaderVisible flag to maintain header visibility after navigation - Ensure smooth scrolling with proper offset calculations - Reset flag on scroll up to restore normal hide/show behavior ## Files Modified - internal/models/cv.go: Add ProjectName, ProjectDesc fields - templates/cv-content.html: Update project and experience rendering - static/css/main.css: Add duration-text styling - templates/index.html: Enhance scroll behavior and localStorage - data/cv-*.json: Add certifications, split project titles, reorder courses - static/images/courses/: Add codecademy.png, linkedin.png --- CODE_OF_CONDUCT.md | 133 ++++++++++++ CONTRIBUTING.md | 296 +++++++++++++++++++++++++++ LICENSE | 21 ++ README.md | 177 +++++++++++++++- SECURITY.md | 205 +++++++++++++++++++ data/cv-en.json | 37 ++++ data/cv-es.json | 37 ++++ internal/models/cv.go | 10 +- static/css/main.css | 5 + static/images/courses/codecademy.png | Bin 0 -> 17215 bytes static/images/courses/linkedin.png | Bin 0 -> 8902 bytes templates/cv-content.html | 10 +- templates/index.html | 77 +++++-- 13 files changed, 975 insertions(+), 33 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 static/images/courses/codecademy.png create mode 100644 static/images/courses/linkedin.png diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a1a1b8c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement via GitHub issues +or by contacting the project maintainer directly. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6376dd2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,296 @@ +# Contributing to CV Site + +First off, thank you for considering contributing to this project! This CV site is a personal project, but contributions are welcome to improve the template, fix bugs, or add features that others might find useful. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Enhancements](#suggesting-enhancements) + - [Pull Requests](#pull-requests) +- [Development Setup](#development-setup) +- [Style Guidelines](#style-guidelines) + - [Go Code Style](#go-code-style) + - [HTMX Patterns](#htmx-patterns) + - [CSS Style](#css-style) +- [Testing](#testing) +- [Commit Messages](#commit-messages) + +## Code of Conduct + +This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainer. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check existing issues to avoid duplicates. When creating a bug report, include as many details as possible: + +**Bug Report Template:** + +```markdown +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '...' +3. See error + +**Expected behavior** +What you expected to happen. + +**Environment:** +- Go version: [e.g., 1.21.5] +- OS: [e.g., macOS 14.0, Ubuntu 22.04] +- Browser (if applicable): [e.g., Chrome 120, Firefox 121] + +**Additional context** +Add any other context about the problem here. +``` + +### Suggesting Enhancements + +Enhancement suggestions are welcome! Please create an issue with: + +- **Clear title** describing the enhancement +- **Detailed description** of the proposed functionality +- **Use case** explaining why this would be useful +- **Implementation ideas** (optional) if you have thoughts on how to implement it + +### Pull Requests + +1. **Fork the repository** and create your branch from `main` +2. **Branch naming convention:** + - `feature/description` - New features (e.g., `feature/add-dark-mode`) + - `fix/description` - Bug fixes (e.g., `fix/pdf-export-fonts`) + - `docs/description` - Documentation updates (e.g., `docs/update-readme`) + - `refactor/description` - Code refactoring (e.g., `refactor/simplify-handlers`) + +3. **Make your changes:** + - Follow the [style guidelines](#style-guidelines) + - Add tests if adding new functionality (see [Testing](#testing)) + - Update documentation as needed + +4. **Test your changes:** + - Run `make dev` to test locally + - Test PDF export functionality + - Test both English and Spanish versions + - Test responsive design on different screen sizes + +5. **Commit your changes** with clear commit messages (see [Commit Messages](#commit-messages)) + +6. **Push to your fork** and submit a pull request to the `main` branch + +7. **Pull Request Template:** + +```markdown +**Description** +Brief description of what this PR does. + +**Type of change** +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +**How Has This Been Tested?** +Describe the tests you ran and how to reproduce them. + +**Checklist:** +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have tested the changes locally +``` + +## Development Setup + +### Prerequisites + +- **Go 1.21+** installed +- **Git** for version control +- **Make** (optional, but recommended) +- **Chrome/Chromium** for PDF generation testing + +### Setup Steps + +1. **Clone your fork:** + ```bash + git clone https://github.com/YOUR-USERNAME/cv.git + cd cv + ``` + +2. **Install dependencies:** + ```bash + go mod download + ``` + +3. **Set up environment:** + ```bash + cp .env.example .env + # Edit .env if needed + ``` + +4. **Run development server:** + ```bash + make dev + # Or: GO_ENV=development go run main.go + ``` + +5. **Open browser:** + ``` + http://localhost:1999 + ``` + +### Useful Make Commands + +- `make dev` - Run in development mode (hot-reload enabled) +- `make build` - Build production binary +- `make test` - Test endpoints (requires server running) +- `make clean` - Remove build artifacts +- `make help` - Show all available commands + +## Style Guidelines + +### Go Code Style + +- **Follow standard Go conventions:** + - Use `gofmt` to format code (runs automatically with most editors) + - Run `go vet` to catch common mistakes + - Use meaningful variable and function names + - Add comments for exported functions and complex logic + +- **Code organization:** + - Keep handlers in `main.go` or separate handler files + - Use the `internal/models` package for data structures + - Keep utilities in appropriate packages + +- **Error handling:** + ```go + // Good + if err != nil { + log.Printf("Error loading CV data: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + // Avoid silent failures + // Bad: ignoring errors without logging + data, _ := loadCV() + ``` + +### HTMX Patterns + +- **Use semantic HTML:** + ```html + + + + + ``` + +- **Keep HTMX attributes organized:** + - `hx-get/post` first + - `hx-target` second + - `hx-swap` third + - Other attributes follow + +- **Progressive enhancement:** + - Ensure basic functionality works without JavaScript + - HTMX should enhance, not be required + +### CSS Style + +- **Organization:** + - Group related styles together + - Use comments to separate sections + - Keep selectors specific but not overly complex + +- **Naming:** + - Use semantic class names + - Prefer descriptive names over abbreviations + +- **Responsive design:** + - Mobile-first approach + - Use media queries for larger screens + - Test on multiple screen sizes + +## Testing + +**Current State:** This project does not yet have automated tests. This is a known gap. + +**When adding tests (future):** + +- Write unit tests for new utility functions +- Add integration tests for HTTP handlers +- Test PDF generation functionality +- Ensure tests pass before submitting PR: + ```bash + go test ./... + ``` + +**Manual testing requirements:** + +- Test both language versions (English/Spanish) +- Test PDF export (both server-side and browser print) +- Test responsive design (mobile, tablet, desktop) +- Test in multiple browsers (Chrome, Firefox, Safari) +- Verify console has no errors +- Check network tab for failed requests + +## Commit Messages + +Use clear, descriptive commit messages following this format: + +``` +type: brief description + +Optional longer description explaining what and why (not how). + +Fixes #123 +``` + +**Types:** +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `style:` - Code style changes (formatting, no logic change) +- `refactor:` - Code refactoring +- `perf:` - Performance improvements +- `test:` - Adding or updating tests +- `chore:` - Maintenance tasks, dependency updates + +**Examples:** + +``` +feat: add dark mode toggle + +Add user preference for dark mode with localStorage persistence. +Respects system preference on first visit. + +Fixes #42 +``` + +``` +fix: correct PDF font rendering in headless Chrome + +The custom Quicksand font wasn't loading properly in chromedp. +Updated to wait for fonts to load before PDF generation. + +Fixes #38 +``` + +## Questions? + +Feel free to open an issue with the `question` label if you need help or clarification on anything! + +## Thank You! + +Your contributions help make this project better for everyone. Thank you for taking the time to contribute! 🎉 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aced3ad --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Juan Andrés Moreno Rubio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 3a59223..f97e2fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,29 @@ # CV Site - Go + HTMX +[![Go Version](https://img.shields.io/badge/Go-1.21%2B-00ADD8?logo=go)](https://go.dev/) +[![HTMX](https://img.shields.io/badge/HTMX-1.9.10-3366CC)](https://htmx.org/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) + **Modern, minimal curriculum vitae website** for Juan Andrés Moreno Rubio built with **Go** and **HTMX**. +A professional, bilingual CV site with server-side PDF generation, HTMX interactivity, and a clean paper design aesthetic. Perfect template for developers looking to create their own CV website with modern tech and minimal JavaScript. + +## 📑 Table of Contents + +- [Features](#-features) +- [Demo](#-demo) +- [Quick Start](#-quick-start) +- [Updating Your CV](#-updating-your-cv) +- [Export to PDF](#-export-to-pdf) +- [Key Technologies](#-key-technologies) +- [Documentation](#-documentation) +- [Deployment](#-deployment) +- [Customization](#-customization) +- [Contributing](#-contributing) +- [License](#-license) +- [Support](#-support) + ## 🚀 Features - ✅ **Bilingual Support** - Spanish and English with instant switching (no page reload) @@ -13,24 +35,55 @@ - ✅ **JSON-Based Content** - Easy to update without touching code - ✅ **AI Development Section** - Showcases modern AI-assisted development skills - ✅ **Fast & Lightweight** - Go backend with chromedp for PDF generation +- ✅ **Security Hardened** - CSP headers, XSS protection, secure defaults +- ✅ **Production Ready** - Docker support, systemd service, CI/CD workflows +- ✅ **Developer Friendly** - Hot reload, clear code structure, comprehensive Makefile + +## 📸 Demo + +**Live Features:** +- Single-page application with no page reloads +- Instant language switching (English ↔ Spanish) +- Professional PDF export with perfect font rendering +- Responsive design from mobile to desktop +- Clean paper aesthetic on gray background +- Print-friendly layouts + +**Note:** This is a personal CV site template. Fork it and customize the JSON files with your own information! ## 📋 Quick Start ### Prerequisites - **Go 1.21+** installed +- **Chrome/Chromium** (for PDF generation) +- **Make** (optional, recommended for easier development) -### Run +### Installation & Run \`\`\`bash -# Build and run +# Clone the repository +git clone https://github.com/yourusername/cv.git +cd cv + +# Option 1: Using Make (recommended) +make dev + +# Option 2: Using Go directly +go run main.go + +# Option 3: Build and run binary go build -o cv-server && ./cv-server \`\`\` -Open **http://localhost:1999** +### Access the Site -- 🇬🇧 English: http://localhost:1999/?lang=en -- 🇪🇸 Spanish: http://localhost:1999/?lang=es +Open **http://localhost:1999** in your browser + +- 🇬🇧 **English version:** http://localhost:1999/?lang=en +- 🇪🇸 **Spanish version:** http://localhost:1999/?lang=es + +**Language switching** is instant via HTMX - no page reload required! ## 📄 Updating Your CV @@ -66,11 +119,115 @@ No code changes needed - just refresh browser! ## 🎯 Key Technologies -- Backend: **Go** (stdlib net/http) -- PDF Generation: **chromedp** (headless Chrome automation) -- Frontend: **HTMX** 1.9.10 -- Styling: Custom **CSS** with Quicksand font -- Data: **JSON** files +- **Backend:** Go 1.21+ (stdlib `net/http`, graceful shutdown) +- **PDF Generation:** chromedp (headless Chrome automation) +- **Frontend:** HTMX 1.9.10 (hypermedia-driven interactions) +- **Styling:** Custom CSS with Quicksand font from Google Fonts +- **Data:** JSON files for easy content management +- **Deployment:** Docker, systemd service, GitHub Actions CI/CD + +## 📚 Documentation + +- **[ARCHITECTURE.md](ARCHITECTURE.md)** - System design, data flow, and technical decisions +- **[CONTRIBUTING.md](CONTRIBUTING.md)** - How to contribute (issues, PRs, code style) +- **[SECURITY.md](SECURITY.md)** - Security policy, vulnerability reporting, deployment considerations +- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** - Community guidelines and standards +- **[LICENSE](LICENSE)** - MIT License + +## 🚀 Deployment + +This project is production-ready with multiple deployment options: + +### Docker Deployment + +\`\`\`bash +# Build Docker image +make docker-build + +# Run container +make docker-run +\`\`\` + +### Systemd Service + +\`\`\`bash +# Install as systemd service +make install-service + +# Update running service +make update-service +\`\`\` + +### Manual Deployment + +\`\`\`bash +# Build optimized binary +make build + +# Run in production mode +GO_ENV=production ./cv-server +\`\`\` + +**Environment Configuration:** Copy `.env.example` to `.env` and customize: +- `PORT` - Server port (default: 1999) +- `GO_ENV` - Environment (development/production) +- `TEMPLATE_HOT_RELOAD` - Enable template hot-reload in development + +**Security:** See [SECURITY.md](SECURITY.md) for production deployment best practices. + +## 🎨 Customization + +### Update Your CV Content + +1. Edit `data/cv-en.json` and `data/cv-es.json` with your information +2. No code changes required - just refresh the browser! + +### Customize Styling + +- **Main styles:** `static/css/main.css` +- **Colors:** Modify CSS variables in `:root` selector +- **Fonts:** Update Google Fonts import in HTML templates +- **Layout:** Edit templates in `templates/` directory + +### Add New Sections + +1. Update the `CV` struct in `internal/models/cv.go` +2. Add content to JSON files in `data/` +3. Update templates in `templates/` to display new sections + +## 🤝 Contributing + +Contributions are welcome! Whether it's: + +- 🐛 Bug reports +- 💡 Feature suggestions +- 📝 Documentation improvements +- 🔧 Code contributions + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on: +- Submitting issues +- Creating pull requests +- Code style and testing requirements +- Development workflow + +## 📄 License + +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details. + +**TL;DR:** You can use this template for your own CV site, modify it, and distribute it. Just keep the original copyright notice. + +## 💬 Support + +- **Issues:** [GitHub Issues](https://github.com/yourusername/cv/issues) for bug reports and feature requests +- **Discussions:** [GitHub Discussions](https://github.com/yourusername/cv/discussions) for questions and ideas +- **Security:** See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities + +## 🙏 Acknowledgments + +- **HTMX** - For making hypermedia-driven applications enjoyable +- **chromedp** - For reliable headless Chrome automation +- **Go Community** - For excellent standard library and tooling +- **AI Assistance** - For accelerating development and documentation --- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ad72521 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,205 @@ +# Security Policy + +## Supported Versions + +This project is actively maintained. Security updates will be provided for the latest release on the `main` branch. + +| Version | Supported | +| ------- | ------------------ | +| main | :white_check_mark: | +| < main | :x: | + +## Reporting a Vulnerability + +We take the security of this CV site seriously. If you discover a security vulnerability, please help us protect users by following responsible disclosure practices. + +### How to Report + +**DO NOT** open a public issue for security vulnerabilities. + +Instead, please report security vulnerabilities by: + +1. **GitHub Security Advisories** (Preferred): + - Go to the repository's Security tab + - Click "Report a vulnerability" + - Fill out the form with details + +2. **Direct Contact**: + - Open a private issue or contact the maintainer directly + - Use encrypted communication if the vulnerability is severe + +### What to Include + +Please provide the following information in your report: + +- **Description** of the vulnerability +- **Steps to reproduce** the issue +- **Potential impact** (e.g., data exposure, XSS, CSRF) +- **Affected versions** (if known) +- **Suggested fix** (if you have one) +- **Your contact information** for follow-up questions + +### Response Timeline + +- **Initial Response**: Within 48 hours +- **Status Update**: Within 7 days +- **Fix Timeline**: Depends on severity + - Critical: Within 7 days + - High: Within 14 days + - Medium: Within 30 days + - Low: Next release cycle + +### Disclosure Policy + +- We ask that you give us reasonable time to fix the vulnerability before public disclosure +- We will credit you in the security advisory (unless you prefer to remain anonymous) +- Once the fix is deployed, we will publish a security advisory with details + +## Security Considerations for Deployments + +If you're deploying this CV site, please be aware of these security considerations: + +### 1. PDF Generation Security + +The server uses headless Chrome (via chromedp) to generate PDFs: + +- **Risk**: Chromedp executes JavaScript and renders HTML, which could be exploited if user input is not sanitized +- **Mitigation**: + - The CV data comes from trusted JSON files, not user input + - If you modify the application to accept user input, ensure proper sanitization + - Consider running chromedp in a sandboxed environment + +### 2. Content Security Policy + +The application includes CSP headers: + +```go +Content-Security-Policy: default-src 'self'; + script-src 'self' 'unsafe-inline' https://unpkg.com; + style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; + font-src 'self' https://fonts.gstatic.com; + img-src 'self' data: +``` + +- Review and adjust CSP headers based on your deployment needs +- Remove `'unsafe-inline'` if possible by moving inline scripts/styles to separate files + +### 3. Environment Variables + +Sensitive configuration is managed via environment variables: + +- **Never commit** `.env` file to version control +- Use `.env.example` as a template +- In production, use secure secret management (e.g., HashiCorp Vault, AWS Secrets Manager) + +### 4. HTTPS in Production + +- **Always use HTTPS** in production +- Configure TLS certificates (Let's Encrypt recommended) +- Consider using a reverse proxy (nginx, Caddy) for TLS termination + +### 5. Rate Limiting + +The application does not include built-in rate limiting: + +- **Recommendation**: Use a reverse proxy (nginx, Caddy) to implement rate limiting +- Protect the `/export/pdf` endpoint to prevent PDF generation abuse + +### 6. Input Validation + +While this application primarily serves static CV data: + +- If you extend it to accept user input, implement strict validation +- Sanitize all inputs before rendering in templates +- Use Go's `html/template` package (which auto-escapes) for HTML rendering + +### 7. Dependency Management + +Keep dependencies up to date: + +```bash +# Check for outdated dependencies +go list -u -m all + +# Update dependencies +go get -u ./... +go mod tidy +``` + +### 8. Security Headers + +The application sets security headers: + +- `X-Content-Type-Options: nosniff` +- `X-Frame-Options: DENY` +- `X-XSS-Protection: 1; mode=block` +- `Content-Security-Policy: ...` + +Review and enhance these headers based on your deployment needs. + +### 9. Logging and Monitoring + +- Enable structured logging in production +- Monitor for unusual patterns (e.g., excessive PDF generation requests) +- Set up alerts for errors and anomalies + +### 10. Docker Security + +If deploying via Docker: + +- Use official, minimal base images +- Run container as non-root user +- Scan images for vulnerabilities (e.g., `docker scan`, Trivy) +- Keep base images updated + +## Known Security Considerations + +### PDF Generation Resource Usage + +- **Issue**: PDF generation uses headless Chrome, which consumes significant CPU/memory +- **Impact**: Potential DoS via excessive PDF generation requests +- **Mitigation**: + - Implement rate limiting on `/export/pdf` endpoint + - Consider caching generated PDFs + - Monitor resource usage + +### Third-Party Dependencies + +External dependencies loaded from CDNs: + +- HTMX from `https://unpkg.com` +- Google Fonts from `https://fonts.googleapis.com` + +**Recommendation**: For production, consider: +- Self-hosting these dependencies for better control +- Using Subresource Integrity (SRI) hashes +- Implementing a Content Security Policy + +## Security Best Practices + +If you're forking this project for your own CV: + +1. **Review all code** before deploying +2. **Update personal information** in JSON files +3. **Configure security headers** appropriate for your use case +4. **Enable HTTPS** with valid certificates +5. **Keep dependencies updated** regularly +6. **Monitor application logs** for suspicious activity +7. **Backup your data** regularly +8. **Test security** before going live + +## Security Updates + +Security updates will be announced via: + +- GitHub Security Advisories +- Release notes on GitHub +- Git commit messages tagged with `[SECURITY]` + +## Contact + +For security concerns, please contact the project maintainer via GitHub. + +## Acknowledgments + +We appreciate the security research community's efforts in responsibly disclosing vulnerabilities. Thank you for helping keep this project secure! diff --git a/data/cv-en.json b/data/cv-en.json index f11db34..4a3c2eb 100644 --- a/data/cv-en.json +++ b/data/cv-en.json @@ -664,6 +664,35 @@ } ], "courses": [ + { + "title": "Codecademy Certifications", + "institution": "Codecademy", + "courseLogo": "codecademy.png", + "location": "Online", + "date": "2022-2024", + "duration": "Various", + "shortDescription": "Professional development courses in AI and modern web technologies through Codecademy's interactive learning platform.", + "responsibilities": [ + "
Intro to AI Transformers Course April 2024: Comprehensive introduction to transformer architecture and AI models, covering attention mechanisms, encoder-decoder structures, and practical applications in natural language processing
", + "
Learn React Course March 2022: Complete React framework training covering components, state management, hooks, lifecycle methods, and modern React development practices
" + ] + }, + { + "title": "LinkedIn Learning Certifications", + "institution": "LinkedIn Learning", + "courseLogo": "linkedin.png", + "location": "Online", + "date": "2019-2020", + "duration": "Various", + "shortDescription": "Professional development courses in SAP technologies, UX design, security, and data analytics through LinkedIn Learning's comprehensive training platform.", + "responsibilities": [ + "
Aprende lectura rápida April 2020: Speed reading techniques and comprehension strategies for professional development and efficient information processing
", + "
A Tour of the SAP Cloud Platform February 2020: Comprehensive overview of SAP Cloud Platform services, architecture, and integration capabilities for enterprise cloud solutions
", + "
Learning Android Security February 2020: Android security best practices, encryption methods, secure coding practices, and mobile application security fundamentals
", + "
Persuasive UX: Creating Credibility January 2020: User experience design principles focused on building trust, credibility, and persuasive design patterns for web applications
", + "
Big Data Foundations: Techniques and Concepts December 2019: Fundamentals of big data technologies, distributed computing, data processing frameworks, and analytics techniques
" + ] + }, { "title": "Servoy World 2011", "institution": "Servoy", @@ -738,6 +767,8 @@ "projects": [ { "title": "Somos Una Ola - Beach Cleaning Initiative", + "projectName": "Somos Una Ola", + "projectDesc": "Beach Cleaning Initiative", "url": "https://somosunaola.org", "projectLogo": "somosunaola.png", "location": "La Palma, Canary Islands", @@ -753,6 +784,8 @@ }, { "title": "Herrumbre Vivo Arte - Artist Portfolio Website", + "projectName": "Herrumbre Vivo Arte", + "projectDesc": "Artist Portfolio Website", "url": "https://herrumbrevivoarte.com", "projectLogo": "herrumbre-vivo.png", "location": "Fuencaliente, La Palma", @@ -768,6 +801,8 @@ }, { "title": "La Porra.club - Football Prediction Platform", + "projectName": "La Porra.club", + "projectDesc": "Football Prediction Platform", "url": "https://laporra.club", "projectLogo": "laporra.png", "gitRepoUrl": "/Users/txeo/laporra", @@ -784,6 +819,8 @@ }, { "title": "CDC Starter Kit - SAP Customer Data Cloud Demo", + "projectName": "CDC Starter Kit", + "projectDesc": "SAP Customer Data Cloud Demo", "url": "https://gigyademo.com/cdc-starter-kit/", "projectLogo": "sap.png", "location": "Online", diff --git a/data/cv-es.json b/data/cv-es.json index 6c1efd6..7c9c28e 100644 --- a/data/cv-es.json +++ b/data/cv-es.json @@ -669,6 +669,35 @@ } ], "courses": [ + { + "title": "Certificaciones Codecademy", + "institution": "Codecademy", + "courseLogo": "codecademy.png", + "location": "Online", + "date": "2022-2024", + "duration": "Varios", + "shortDescription": "Cursos de desarrollo profesional en IA y tecnologías web modernas a través de la plataforma de aprendizaje interactivo de Codecademy.", + "responsibilities": [ + "
Intro to AI Transformers Course Abril 2024: Introducción completa a la arquitectura de transformers y modelos de IA, cubriendo mecanismos de atención, estructuras encoder-decoder y aplicaciones prácticas en procesamiento de lenguaje natural
", + "
Learn React Course Marzo 2022: Formación completa en React framework cubriendo componentes, gestión de estado, hooks, métodos de ciclo de vida y prácticas modernas de desarrollo con React
" + ] + }, + { + "title": "Certificaciones LinkedIn Learning", + "institution": "LinkedIn Learning", + "courseLogo": "linkedin.png", + "location": "Online", + "date": "2019-2020", + "duration": "Varios", + "shortDescription": "Cursos de desarrollo profesional en tecnologías SAP, diseño UX, seguridad y análisis de datos a través de la plataforma de formación integral de LinkedIn Learning.", + "responsibilities": [ + "
Aprende lectura rápida Abril 2020: Técnicas de lectura rápida y estrategias de comprensión para desarrollo profesional y procesamiento eficiente de información
", + "
A Tour of the SAP Cloud Platform Febrero 2020: Visión general completa de servicios de SAP Cloud Platform, arquitectura y capacidades de integración para soluciones empresariales en la nube
", + "
Learning Android Security Febrero 2020: Mejores prácticas de seguridad Android, métodos de encriptación, prácticas de codificación segura y fundamentos de seguridad de aplicaciones móviles
", + "
Persuasive UX: Creating Credibility Enero 2020: Principios de diseño de experiencia de usuario enfocados en generar confianza, credibilidad y patrones de diseño persuasivo para aplicaciones web
", + "
Big Data Foundations: Techniques and Concepts Diciembre 2019: Fundamentos de tecnologías big data, computación distribuida, frameworks de procesamiento de datos y técnicas de análisis
" + ] + }, { "title": "Servoy World 2011", "institution": "Servoy", @@ -743,6 +772,8 @@ "projects": [ { "title": "Somos Una Ola - Iniciativa de Limpieza de Playas", + "projectName": "Somos Una Ola", + "projectDesc": "Iniciativa de Limpieza de Playas", "url": "https://somosunaola.org", "projectLogo": "somosunaola.png", "location": "La Palma, Islas Canarias", @@ -758,6 +789,8 @@ }, { "title": "Herrumbre Vivo Arte - Sitio Web Portfolio de Artista", + "projectName": "Herrumbre Vivo Arte", + "projectDesc": "Sitio Web Portfolio de Artista", "url": "https://herrumbrevivoarte.com", "projectLogo": "herrumbre-vivo.png", "location": "Fuencaliente, La Palma", @@ -773,6 +806,8 @@ }, { "title": "La Porra.club - Plataforma de Predicción de Fútbol", + "projectName": "La Porra.club", + "projectDesc": "Plataforma de Predicción de Fútbol", "url": "https://laporra.club", "projectLogo": "laporra.png", "gitRepoUrl": "/Users/txeo/laporra", @@ -789,6 +824,8 @@ }, { "title": "CDC Starter Kit - Demo de SAP Customer Data Cloud", + "projectName": "CDC Starter Kit", + "projectDesc": "Demo de SAP Customer Data Cloud", "url": "https://gigyademo.com/cdc-starter-kit/", "projectLogo": "sap.png", "location": "Online", diff --git a/internal/models/cv.go b/internal/models/cv.go index 23a4ed0..f122d43 100644 --- a/internal/models/cv.go +++ b/internal/models/cv.go @@ -101,13 +101,15 @@ type Language struct { type Project struct { Title string `json:"title"` + ProjectName string `json:"projectName,omitempty"` // Optional: linkable part of title + ProjectDesc string `json:"projectDesc,omitempty"` // Optional: non-linkable description part URL string `json:"url"` - ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename - GitRepoUrl string `json:"gitRepoUrl,omitempty"` // Optional git repository URL for dynamic dates + ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename + GitRepoUrl string `json:"gitRepoUrl,omitempty"` // Optional git repository URL for dynamic dates Location string `json:"location"` - StartDate string `json:"startDate,omitempty"` // Optional static start date + StartDate string `json:"startDate,omitempty"` // Optional static start date Current bool `json:"current"` - MaintainedBy string `json:"maintainedBy,omitempty"` // Optional maintainer name (e.g., "SAP") + MaintainedBy string `json:"maintainedBy,omitempty"` // Optional maintainer name (e.g., "SAP") Technologies []string `json:"technologies"` ShortDescription string `json:"shortDescription"` Responsibilities []string `json:"responsibilities"` diff --git a/static/css/main.css b/static/css/main.css index 642c1b3..7405210 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -779,6 +779,11 @@ iconify-icon { margin-top: 0.5rem; } +.duration-text { + color: #999; + font-weight: normal; +} + .responsibilities { list-style: none; margin-top: 1rem; diff --git a/static/images/courses/codecademy.png b/static/images/courses/codecademy.png new file mode 100644 index 0000000000000000000000000000000000000000..078c2bdf520679e453a3054c21f7ac4df63c5663 GIT binary patch literal 17215 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 UXkvA2B>(^b07*qoM6N<$f^g=`kpKVy literal 0 HcmV?d00001 diff --git a/templates/cv-content.html b/templates/cv-content.html index 633e9a2..68ca334 100644 --- a/templates/cv-content.html +++ b/templates/cv-content.html @@ -91,7 +91,7 @@ {{end}}
- {{.Position}}{{if .Company}} - {{if .CompanyURL}}{{.Company}}{{else}}{{.Company}}{{end}}{{end}}
+ {{.Position}}{{if .Company}} - {{if .CompanyURL}}{{.Company}}{{else}}{{.Company}}{{end}}{{if .Duration}} {{.Duration}}{{end}}{{end}}
{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}}) {{if .ShortDescription}} @@ -186,7 +186,13 @@
{{end}}
- {{if .URL}}{{.Title}}{{else}}{{.Title}}{{end}}
+ + {{if .ProjectName}} + {{if .URL}}{{.ProjectName}}{{else}}{{.ProjectName}}{{end}}{{if .ProjectDesc}} - {{.ProjectDesc}}{{end}} + {{else}} + {{if .URL}}{{.Title}}{{else}}{{.Title}}{{end}} + {{end}} +
{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{if eq $.Lang "es"}}presente{{else}}now{{end}}{{end}}{{end}}{{end}} - ({{.Location}}) {{if .ShortDescription}} diff --git a/templates/index.html b/templates/index.html index 4238b56..70f687a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -277,28 +277,41 @@ } } + // Flag to keep header visible after navigation + let keepHeaderVisible = false; + // Scroll to section smoothly function scrollToSection(sectionId) { event.preventDefault(); // Prevent default anchor behavior const section = document.getElementById(sectionId); if (section) { - const actionBarHeight = document.querySelector('.action-bar').offsetHeight; - const menuHeight = document.querySelector('.navigation-menu').offsetHeight; - const offset = actionBarHeight + (menuHeight || 0) + 20; // Add 20px padding + // Ensure header is visible before scrolling + const actionBar = document.querySelector('.action-bar'); + const navMenu = document.querySelector('.navigation-menu'); + actionBar.classList.remove('header-hidden'); + navMenu.classList.remove('header-hidden'); - const elementPosition = section.getBoundingClientRect().top; - const offsetPosition = elementPosition + window.pageYOffset - offset; - - window.scrollTo({ - top: offsetPosition, - behavior: 'smooth' - }); + // Set flag to keep header visible + keepHeaderVisible = true; // Close menu after clicking - const menu = document.getElementById('navigation-menu'); - menu.classList.remove('menu-open'); + navMenu.classList.remove('menu-open'); document.querySelector('.hamburger-btn').setAttribute('aria-expanded', 'false'); + + // Wait a bit for header to be visible, then calculate offset + setTimeout(() => { + const actionBarHeight = actionBar.offsetHeight; + const offset = actionBarHeight + 20; // Add 20px padding + + const elementPosition = section.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.pageYOffset - offset; + + window.scrollTo({ + top: offsetPosition, + behavior: 'smooth' + }); + }, 100); } } @@ -348,9 +361,11 @@ if (toggle.checked) { paper.classList.add('cv-long'); paper.classList.remove('cv-short'); + localStorage.setItem('cv-length', 'long'); } else { paper.classList.add('cv-short'); paper.classList.remove('cv-long'); + localStorage.setItem('cv-length', 'short'); } // Restore scroll position after DOM updates @@ -368,8 +383,10 @@ if (toggle.checked) { paper.classList.add('show-logos'); + localStorage.setItem('cv-logos', 'show'); } else { paper.classList.remove('show-logos'); + localStorage.setItem('cv-logos', 'hide'); } // Restore scroll position after DOM updates @@ -412,10 +429,31 @@ }, 100); } - // Initialize with short version, logos enabled, and saved theme + // Initialize with saved preferences or defaults document.addEventListener('DOMContentLoaded', function() { - document.querySelector('.cv-paper').classList.add('cv-short'); - document.querySelector('.cv-paper').classList.add('show-logos'); + const paper = document.querySelector('.cv-paper'); + + // Restore CV length preference + const savedLength = localStorage.getItem('cv-length') || 'short'; + if (savedLength === 'long') { + paper.classList.add('cv-long'); + paper.classList.remove('cv-short'); + document.getElementById('lengthToggle').checked = true; + } else { + paper.classList.add('cv-short'); + paper.classList.remove('cv-long'); + document.getElementById('lengthToggle').checked = false; + } + + // Restore logos preference + const savedLogos = localStorage.getItem('cv-logos') || 'show'; + if (savedLogos === 'show') { + paper.classList.add('show-logos'); + document.getElementById('logoToggle').checked = true; + } else { + paper.classList.remove('show-logos'); + document.getElementById('logoToggle').checked = false; + } // Restore theme preference const savedTheme = localStorage.getItem('cv-theme') || 'default'; @@ -436,10 +474,15 @@ const currentScroll = window.pageYOffset || document.documentElement.scrollTop; const isMenuOpen = navMenu.classList.contains('menu-open'); + // If scrolling up, reset the keepHeaderVisible flag + if (currentScroll < lastScrollTop) { + keepHeaderVisible = false; + } + // Hide/show header based on scroll direction if (currentScroll > scrollThreshold) { - if (currentScroll > lastScrollTop) { - // Scrolling down - hide header + if (currentScroll > lastScrollTop && !keepHeaderVisible) { + // Scrolling down - hide header (only if keepHeaderVisible is false) actionBar.classList.add('header-hidden'); // Only hide menu if it's open if (isMenuOpen) {