initial commit

This commit is contained in:
Daniel Noulet 2026-10-07 15:21:47 +02:00
commit e7987e88a7
70 changed files with 13661 additions and 0 deletions

View file

@ -0,0 +1,105 @@
package service
import (
"context"
"sync"
"time"
"github.com/dnoulet/go-first/internal/model"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
type ContactService struct {
mu sync.RWMutex
submissions map[uuid.UUID]*model.ContactSubmission
emailService EmailService
}
type EmailService interface {
SendContactNotification(submission *model.ContactSubmission) error
SendAutoReply(submission *model.ContactSubmission) error
}
func NewContactService(emailService EmailService) *ContactService {
return &ContactService{
submissions: make(map[uuid.UUID]*model.ContactSubmission),
emailService: emailService,
}
}
func (s *ContactService) Create(ctx context.Context, req *model.ContactForm) (*model.ContactSubmission, error) {
submission := &model.ContactSubmission{
ID: uuid.New(),
Name: req.Name,
Email: req.Email,
Subject: req.Subject,
Message: req.Message,
Status: "new",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
s.mu.Lock()
s.submissions[submission.ID] = submission
s.mu.Unlock()
if s.emailService != nil {
go func() {
if err := s.emailService.SendContactNotification(submission); err != nil {
log.Error().Err(err).Msg("Failed to send contact notification email")
}
if err := s.emailService.SendAutoReply(submission); err != nil {
log.Error().Err(err).Msg("Failed to send auto-reply email")
}
}()
}
return submission, nil
}
func (s *ContactService) GetAll(ctx context.Context) ([]*model.ContactSubmission, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.ContactSubmission
for _, sub := range s.submissions {
result = append(result, sub)
}
return result, nil
}
func (s *ContactService) GetByID(ctx context.Context, id uuid.UUID) (*model.ContactSubmission, error) {
s.mu.RLock()
defer s.mu.RUnlock()
sub, ok := s.submissions[id]
if !ok {
return nil, ErrNotFound
}
return sub, nil
}
func (s *ContactService) UpdateStatus(ctx context.Context, id uuid.UUID, status string) (*model.ContactSubmission, error) {
s.mu.Lock()
defer s.mu.Unlock()
sub, ok := s.submissions[id]
if !ok {
return nil, ErrNotFound
}
sub.Status = status
sub.UpdatedAt = time.Now()
return sub, nil
}
func (s *ContactService) Delete(ctx context.Context, id uuid.UUID) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.submissions[id]; !ok {
return ErrNotFound
}
delete(s.submissions, id)
return nil
}

View file

@ -0,0 +1,77 @@
package service
import (
"context"
"fmt"
"github.com/dnoulet/go-first/internal/model"
"github.com/rs/zerolog/log"
)
type MockEmailService struct{}
func NewMockEmailService() *MockEmailService {
return &MockEmailService{}
}
func (m *MockEmailService) SendContactNotification(submission *model.ContactSubmission) error {
log.Info().
Str("to", "contact@urpi.be").
Str("subject", "New Contact Form Submission: "+submission.Subject).
Str("from_name", submission.Name).
Str("from_email", submission.Email).
Msg("Sending contact notification email (mock)")
return nil
}
func (m *MockEmailService) SendAutoReply(submission *model.ContactSubmission) error {
log.Info().
Str("to", submission.Email).
Str("subject", "Re: "+submission.Subject).
Msg("Sending auto-reply email (mock)")
return nil
}
type SMTPEmailService struct {
host string
port int
username string
password string
from string
fromName string
}
func NewSMTPEmailService(host string, port int, username, password, from, fromName string) *SMTPEmailService {
return &SMTPEmailService{
host: host,
port: port,
username: username,
password: password,
from: from,
fromName: fromName,
}
}
func (s *SMTPEmailService) SendContactNotification(submission *model.ContactSubmission) error {
log.Info().
Str("to", "contact@urpi.be").
Str("subject", "New Contact Form Submission: "+submission.Subject).
Msg("Sending contact notification email via SMTP")
return nil
}
func (s *SMTPEmailService) SendAutoReply(submission *model.ContactSubmission) error {
log.Info().
Str("to", submission.Email).
Str("subject", "Re: "+submission.Subject).
Msg("Sending auto-reply email via SMTP")
return nil
}
func (s *SMTPEmailService) sendEmail(ctx context.Context, to, subject, body string) error {
log.Debug().
Str("to", to).
Str("subject", subject).
Msg("Sending email via SMTP")
return fmt.Errorf("SMTP not implemented")
}

View file

@ -0,0 +1,16 @@
package service
import "errors"
var (
ErrNotFound = errors.New("resource not found")
ErrAlreadyExists = errors.New("resource already exists")
ErrInvalidInput = errors.New("invalid input")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrInternal = errors.New("internal server error")
ErrValidation = errors.New("validation failed")
ErrConflict = errors.New("conflict")
ErrRateLimited = errors.New("rate limited")
ErrServiceUnavailable = errors.New("service unavailable")
)

View file

@ -0,0 +1,137 @@
package service
import (
"context"
"sync"
"time"
"github.com/dnoulet/go-first/internal/model"
"github.com/google/uuid"
)
type ExperienceService struct {
mu sync.RWMutex
experiences map[uuid.UUID]*model.Experience
}
func NewExperienceService() *ExperienceService {
s := &ExperienceService{
experiences: make(map[uuid.UUID]*model.Experience),
}
s.seedData()
return s
}
func (s *ExperienceService) seedData() {
experiences := []*model.Experience{
{
ID: uuid.New(),
Company: "URPI",
Role: "Founder & Lead Developer",
Location: "Belgium",
StartDate: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
EndDate: nil,
Current: true,
Description: "Founded URPI to provide web application and backend development services. Specializing in Go, Java, and cloud-native architectures. Delivered projects for clients in fintech, e-commerce, and SaaS sectors.",
TechStack: []string{"Go", "Java", "React", "Kubernetes", "AWS", "PostgreSQL", "Redis", "gRPC"},
Order: 1,
},
{
ID: uuid.New(),
Company: "TechCorp Solutions",
Role: "Senior Backend Engineer",
Location: "Brussels, Belgium",
StartDate: time.Date(2017, 6, 1, 0, 0, 0, 0, time.UTC),
EndDate: ptrTime(time.Date(2019, 12, 31, 0, 0, 0, 0, time.UTC)),
Current: false,
Description: "Led backend development for high-scale financial services platform. Designed and implemented microservices architecture, CI/CD pipelines, and monitoring infrastructure. Mentored junior developers and conducted code reviews.",
TechStack: []string{"Java", "Spring Boot", "Kotlin", "PostgreSQL", "Apache Kafka", "Docker", "Kubernetes", "AWS"},
Order: 2,
},
{
ID: uuid.New(),
Company: "StartupXYZ",
Role: "Full Stack Developer",
Location: "Antwerp, Belgium",
StartDate: time.Date(2015, 3, 1, 0, 0, 0, 0, time.UTC),
EndDate: ptrTime(time.Date(2017, 5, 31, 0, 0, 0, 0, time.UTC)),
Current: false,
Description: "Built and maintained full-stack web applications for various clients. Worked with React, Node.js, Python, and Ruby on Rails. Implemented REST APIs, real-time features, and automated testing pipelines.",
TechStack: []string{"React", "Node.js", "Python", "Ruby on Rails", "PostgreSQL", "MongoDB", "AWS", "Docker"},
Order: 3,
},
{
ID: uuid.New(),
Company: "Digital Agency",
Role: "Junior Developer",
Location: "Ghent, Belgium",
StartDate: time.Date(2013, 9, 1, 0, 0, 0, 0, time.UTC),
EndDate: ptrTime(time.Date(2015, 2, 28, 0, 0, 0, 0, time.UTC)),
Current: false,
Description: "Started career building websites and web applications for SME clients. Learned PHP, JavaScript, MySQL, and modern frontend frameworks. Developed custom CMS solutions and e-commerce integrations.",
TechStack: []string{"PHP", "JavaScript", "MySQL", "HTML/CSS", "jQuery", "WordPress", "Magento"},
Order: 4,
},
}
for _, exp := range experiences {
s.experiences[exp.ID] = exp
}
}
func ptrTime(t time.Time) *time.Time {
return &t
}
func (s *ExperienceService) GetAll(ctx context.Context) ([]*model.Experience, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.Experience
for _, exp := range s.experiences {
result = append(result, exp)
}
return result, nil
}
func (s *ExperienceService) GetByID(ctx context.Context, id uuid.UUID) (*model.Experience, error) {
s.mu.RLock()
defer s.mu.RUnlock()
exp, ok := s.experiences[id]
if !ok {
return nil, ErrNotFound
}
return exp, nil
}
func (s *ExperienceService) Create(ctx context.Context, exp *model.Experience) (*model.Experience, error) {
s.mu.Lock()
defer s.mu.Unlock()
exp.ID = uuid.New()
s.experiences[exp.ID] = exp
return exp, nil
}
func (s *ExperienceService) Update(ctx context.Context, exp *model.Experience) (*model.Experience, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.experiences[exp.ID]; !ok {
return nil, ErrNotFound
}
s.experiences[exp.ID] = exp
return exp, nil
}
func (s *ExperienceService) Delete(ctx context.Context, id uuid.UUID) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.experiences[id]; !ok {
return ErrNotFound
}
delete(s.experiences, id)
return nil
}

View file

@ -0,0 +1,241 @@
package service
import (
"context"
"encoding/json"
"os"
"sync"
"github.com/dnoulet/go-first/internal/model"
"github.com/google/uuid"
)
type ProjectService struct {
mu sync.RWMutex
projects map[uuid.UUID]*model.Project
}
func NewProjectService() *ProjectService {
s := &ProjectService{
projects: make(map[uuid.UUID]*model.Project),
}
s.seedData()
return s
}
type catalogProject struct {
Title string `json:"title"`
Description string `json:"description"`
ShortDesc string `json:"shortDesc"`
TechStack []string `json:"techStack"`
ImageURL string `json:"imageUrl"`
DemoURL *string `json:"demoUrl"`
RepoURL string `json:"repoUrl"`
Category string `json:"category"`
Featured bool `json:"featured"`
Order int `json:"order"`
}
func projectsFile() string {
if v := os.Getenv("GO_FIRST_PROJECTS_FILE"); v != "" {
return v
}
for _, candidate := range []string{"projects.json", "/app/projects.json"} {
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
return candidate
}
}
return ""
}
func loadCatalog() ([]*model.Project, bool) {
path := projectsFile()
if path == "" {
return nil, false
}
raw, err := os.ReadFile(path)
if err != nil {
return nil, false
}
var rows []catalogProject
if err := json.Unmarshal(raw, &rows); err != nil || len(rows) == 0 {
return nil, false
}
projects := make([]*model.Project, 0, len(rows))
for _, row := range rows {
demo := ""
if row.DemoURL != nil {
demo = *row.DemoURL
}
projects = append(projects, &model.Project{
ID: uuid.New(),
Title: row.Title,
Description: row.Description,
ShortDesc: row.ShortDesc,
TechStack: row.TechStack,
ImageURL: row.ImageURL,
DemoURL: demo,
RepoURL: row.RepoURL,
Category: row.Category,
Featured: row.Featured,
Order: row.Order,
})
}
return projects, true
}
func (s *ProjectService) seedData() {
if projects, ok := loadCatalog(); ok {
for _, project := range projects {
s.projects[project.ID] = project
}
return
}
projects := []*model.Project{
{
ID: uuid.New(),
Title: "E-Commerce Platform",
Description: "A full-featured e-commerce platform built with Go microservices, React frontend, and Kubernetes deployment. Features include user authentication, product catalog, shopping cart, payment integration, order management, and admin dashboard.",
ShortDesc: "Scalable e-commerce platform with Go microservices and React",
TechStack: []string{"Go", "React", "PostgreSQL", "Redis", "Kubernetes", "Docker", "gRPC"},
ImageURL: "/images/projects/ecommerce.jpg",
DemoURL: "https://demo.urpi.be/ecommerce",
RepoURL: "https://github.com/urpi/ecommerce-platform",
Category: "Full Stack",
Featured: true,
Order: 1,
},
{
ID: uuid.New(),
Title: "API Gateway Service",
Description: "High-performance API gateway built in Go with rate limiting, authentication, request/response transformation, circuit breaker, and distributed tracing. Handles 100k+ requests/second with sub-millisecond latency.",
ShortDesc: "High-performance Go API gateway with advanced features",
TechStack: []string{"Go", "Redis", "Prometheus", "Grafana", "OpenTelemetry", "Docker"},
ImageURL: "/images/projects/apigateway.jpg",
DemoURL: "",
RepoURL: "https://github.com/urpi/api-gateway",
Category: "Backend",
Featured: true,
Order: 2,
},
{
ID: uuid.New(),
Title: "Real-time Collaboration Tool",
Description: "Real-time collaborative document editor with operational transformation, WebSocket connections, presence indicators, and conflict resolution. Built with Go backend and React frontend.",
ShortDesc: "Real-time collaborative editor with operational transformation",
TechStack: []string{"Go", "React", "WebSocket", "Redis", "PostgreSQL", "TypeScript"},
ImageURL: "/images/projects/collab.jpg",
DemoURL: "https://collab.urpi.be",
RepoURL: "https://github.com/urpi/realtime-collab",
Category: "Full Stack",
Featured: true,
Order: 3,
},
{
ID: uuid.New(),
Title: "Distributed Task Queue",
Description: "Distributed task queue system with priority queues, delayed jobs, retry logic, dead letter queues, and horizontal scaling. Built for high-throughput background job processing.",
ShortDesc: "Scalable distributed task queue with priority and retry logic",
TechStack: []string{"Go", "Redis", "PostgreSQL", "Docker", "Kubernetes"},
ImageURL: "/images/projects/taskqueue.jpg",
DemoURL: "",
RepoURL: "https://github.com/urpi/distributed-task-queue",
Category: "Backend",
Featured: false,
Order: 4,
},
{
ID: uuid.New(),
Title: "Infrastructure as Code Framework",
Description: "Custom IaC framework for managing multi-cloud infrastructure with Go templates, drift detection, and policy enforcement. Supports AWS, GCP, and Azure.",
ShortDesc: "Multi-cloud IaC framework with drift detection",
TechStack: []string{"Go", "Terraform", "AWS", "GCP", "Azure", "Docker"},
ImageURL: "/images/projects/iac.jpg",
DemoURL: "",
RepoURL: "https://github.com/urpi/iac-framework",
Category: "DevOps",
Featured: false,
Order: 5,
},
{
ID: uuid.New(),
Title: "Financial Transaction Processor",
Description: "High-throughput financial transaction processing system with ACID guarantees, audit logging, regulatory compliance, and real-time fraud detection. Processes millions of transactions daily.",
ShortDesc: "High-throughput financial transaction processor with fraud detection",
TechStack: []string{"Java", "Spring Boot", "PostgreSQL", "Apache Kafka", "Kubernetes"},
ImageURL: "/images/projects/fintech.jpg",
DemoURL: "",
RepoURL: "",
Category: "Backend",
Featured: true,
Order: 6,
},
}
for _, project := range projects {
s.projects[project.ID] = project
}
}
func (s *ProjectService) GetAll(ctx context.Context, category string, featured bool) ([]*model.Project, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.Project
for _, project := range s.projects {
if category != "" && project.Category != category {
continue
}
if featured && !project.Featured {
continue
}
result = append(result, project)
}
return result, nil
}
func (s *ProjectService) GetFeatured(ctx context.Context) ([]*model.Project, error) {
return s.GetAll(ctx, "", true)
}
func (s *ProjectService) GetByID(ctx context.Context, id uuid.UUID) (*model.Project, error) {
s.mu.RLock()
defer s.mu.RUnlock()
project, ok := s.projects[id]
if !ok {
return nil, ErrNotFound
}
return project, nil
}
func (s *ProjectService) Create(ctx context.Context, project *model.Project) (*model.Project, error) {
s.mu.Lock()
defer s.mu.Unlock()
project.ID = uuid.New()
s.projects[project.ID] = project
return project, nil
}
func (s *ProjectService) Update(ctx context.Context, project *model.Project) (*model.Project, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.projects[project.ID]; !ok {
return nil, ErrNotFound
}
s.projects[project.ID] = project
return project, nil
}
func (s *ProjectService) Delete(ctx context.Context, id uuid.UUID) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.projects[id]; !ok {
return ErrNotFound
}
delete(s.projects, id)
return nil
}

View file

@ -0,0 +1,140 @@
package service
import (
"context"
"sync"
"github.com/dnoulet/go-first/internal/model"
"github.com/google/uuid"
)
type ServiceService struct {
mu sync.RWMutex
services map[uuid.UUID]*model.Service
}
func NewServiceService() *ServiceService {
s := &ServiceService{
services: make(map[uuid.UUID]*model.Service),
}
s.seedData()
return s
}
func (s *ServiceService) seedData() {
services := []*model.Service{
{
ID: uuid.New(),
Title: "Web Application Development",
Description: "Custom web applications built with modern technologies. From concept to deployment, we deliver scalable, maintainable solutions tailored to your business needs.",
Icon: "code",
Features: []string{"React/Next.js Frontend", "Go/Java Backend", "REST/GraphQL APIs", "Real-time Features", "Progressive Web Apps", "TypeScript"},
Order: 1,
Active: true,
},
{
ID: uuid.New(),
Title: "Backend & API Development",
Description: "Robust backend systems and APIs designed for scale. Microservices, event-driven architectures, and high-performance systems that power your business logic.",
Icon: "server",
Features: []string{"Microservices Architecture", "REST & GraphQL APIs", "gRPC Services", "Event-Driven Systems", "Database Design", "Caching Strategies"},
Order: 2,
Active: true,
},
{
ID: uuid.New(),
Title: "Cloud Infrastructure & DevOps",
Description: "End-to-end infrastructure solutions on AWS, GCP, or Azure. Container orchestration, CI/CD pipelines, monitoring, and infrastructure as code.",
Icon: "cloud",
Features: []string{"Kubernetes & Docker", "AWS/GCP/Azure", "Terraform/IaC", "CI/CD Pipelines", "Monitoring & Logging", "Security Hardening"},
Order: 3,
Active: true,
},
{
ID: uuid.New(),
Title: "System Architecture & Consulting",
Description: "Expert architectural guidance for complex systems. Technology selection, scalability planning, performance optimization, and technical debt reduction.",
Icon: "architecture",
Features: []string{"System Design Reviews", "Technology Evaluation", "Scalability Planning", "Performance Optimization", "Migration Strategies", "Code Audits"},
Order: 4,
Active: true,
},
{
ID: uuid.New(),
Title: "Legacy Modernization",
Description: "Transform legacy systems into modern, maintainable applications. Strangler fig pattern, domain-driven design, and incremental migration strategies.",
Icon: "refresh",
Features: []string{"Legacy Assessment", "Strangler Fig Migration", "Domain-Driven Design", "Data Migration", "API Facade Pattern", "Automated Testing"},
Order: 5,
Active: true,
},
{
ID: uuid.New(),
Title: "Technical Advisory & CTO Services",
Description: "Fractional CTO and technical advisory services for startups and scale-ups. Technology strategy, team building, and technical roadmap planning.",
Icon: "advisor",
Features: []string{"Technology Strategy", "Team Building & Hiring", "Technical Roadmaps", "Vendor Evaluation", "Architecture Decisions", "Investor Technical Due Diligence"},
Order: 6,
Active: true,
},
}
for _, svc := range services {
s.services[svc.ID] = svc
}
}
func (s *ServiceService) GetAll(ctx context.Context) ([]*model.Service, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.Service
for _, svc := range s.services {
if svc.Active {
result = append(result, svc)
}
}
return result, nil
}
func (s *ServiceService) GetByID(ctx context.Context, id uuid.UUID) (*model.Service, error) {
s.mu.RLock()
defer s.mu.RUnlock()
svc, ok := s.services[id]
if !ok {
return nil, ErrNotFound
}
return svc, nil
}
func (s *ServiceService) Create(ctx context.Context, svc *model.Service) (*model.Service, error) {
s.mu.Lock()
defer s.mu.Unlock()
svc.ID = uuid.New()
s.services[svc.ID] = svc
return svc, nil
}
func (s *ServiceService) Update(ctx context.Context, svc *model.Service) (*model.Service, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.services[svc.ID]; !ok {
return nil, ErrNotFound
}
s.services[svc.ID] = svc
return svc, nil
}
func (s *ServiceService) Delete(ctx context.Context, id uuid.UUID) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.services[id]; !ok {
return ErrNotFound
}
delete(s.services, id)
return nil
}

View file

@ -0,0 +1,106 @@
package service
import (
"context"
"sync"
"github.com/dnoulet/go-first/internal/model"
"github.com/google/uuid"
)
type SkillService struct {
mu sync.RWMutex
skills map[uuid.UUID]*model.Skill
}
func NewSkillService() *SkillService {
s := &SkillService{
skills: make(map[uuid.UUID]*model.Skill),
}
s.seedData()
return s
}
func (s *SkillService) seedData() {
skills := []*model.Skill{
{ID: uuid.New(), Name: "Go", Category: "Backend", Proficiency: 5, Icon: "go", Order: 1},
{ID: uuid.New(), Name: "Java", Category: "Backend", Proficiency: 5, Icon: "java", Order: 2},
{ID: uuid.New(), Name: "C#", Category: "Backend", Proficiency: 4, Icon: "csharp", Order: 3},
{ID: uuid.New(), Name: "Python", Category: "Backend", Proficiency: 4, Icon: "python", Order: 4},
{ID: uuid.New(), Name: "Ruby", Category: "Backend", Proficiency: 3, Icon: "ruby", Order: 5},
{ID: uuid.New(), Name: "Kotlin", Category: "Backend", Proficiency: 4, Icon: "kotlin", Order: 6},
{ID: uuid.New(), Name: "REST APIs", Category: "Architecture", Proficiency: 5, Icon: "api", Order: 7},
{ID: uuid.New(), Name: "Microservices", Category: "Architecture", Proficiency: 5, Icon: "microservices", Order: 8},
{ID: uuid.New(), Name: "System Design", Category: "Architecture", Proficiency: 5, Icon: "architecture", Order: 9},
{ID: uuid.New(), Name: "Infrastructure", Category: "DevOps", Proficiency: 4, Icon: "infrastructure", Order: 10},
{ID: uuid.New(), Name: "Docker", Category: "DevOps", Proficiency: 5, Icon: "docker", Order: 11},
{ID: uuid.New(), Name: "Kubernetes", Category: "DevOps", Proficiency: 4, Icon: "kubernetes", Order: 12},
{ID: uuid.New(), Name: "React", Category: "Frontend", Proficiency: 4, Icon: "react", Order: 13},
{ID: uuid.New(), Name: "TypeScript", Category: "Frontend", Proficiency: 4, Icon: "typescript", Order: 14},
{ID: uuid.New(), Name: "PostgreSQL", Category: "Database", Proficiency: 5, Icon: "postgresql", Order: 15},
{ID: uuid.New(), Name: "Redis", Category: "Database", Proficiency: 4, Icon: "redis", Order: 16},
{ID: uuid.New(), Name: "MongoDB", Category: "Database", Proficiency: 3, Icon: "mongodb", Order: 17},
{ID: uuid.New(), Name: "GraphQL", Category: "API", Proficiency: 4, Icon: "graphql", Order: 18},
{ID: uuid.New(), Name: "gRPC", Category: "API", Proficiency: 4, Icon: "grpc", Order: 19},
{ID: uuid.New(), Name: "CI/CD", Category: "DevOps", Proficiency: 5, Icon: "cicd", Order: 20},
}
for _, skill := range skills {
s.skills[skill.ID] = skill
}
}
func (s *SkillService) GetAll(ctx context.Context, category string) ([]*model.Skill, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.Skill
for _, skill := range s.skills {
if category == "" || skill.Category == category {
result = append(result, skill)
}
}
return result, nil
}
func (s *SkillService) GetByID(ctx context.Context, id uuid.UUID) (*model.Skill, error) {
s.mu.RLock()
defer s.mu.RUnlock()
skill, ok := s.skills[id]
if !ok {
return nil, ErrNotFound
}
return skill, nil
}
func (s *SkillService) Create(ctx context.Context, skill *model.Skill) (*model.Skill, error) {
s.mu.Lock()
defer s.mu.Unlock()
skill.ID = uuid.New()
s.skills[skill.ID] = skill
return skill, nil
}
func (s *SkillService) Update(ctx context.Context, skill *model.Skill) (*model.Skill, error) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.skills[skill.ID]; !ok {
return nil, ErrNotFound
}
s.skills[skill.ID] = skill
return skill, nil
}
func (s *SkillService) Delete(ctx context.Context, id uuid.UUID) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.skills[id]; !ok {
return ErrNotFound
}
delete(s.skills, id)
return nil
}