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 }