268 lines
6.2 KiB
Go
268 lines
6.2 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"deployment-manager/internal/db"
|
|
"deployment-manager/internal/events"
|
|
"deployment-manager/internal/model"
|
|
)
|
|
|
|
type HTTPServer struct {
|
|
repoStore *db.RepoStore
|
|
eventBus *events.Bus
|
|
server *http.Server
|
|
}
|
|
|
|
func NewHTTPServer(database *sql.DB, eventBus *events.Bus) *HTTPServer {
|
|
return &HTTPServer{
|
|
repoStore: db.NewRepoStore(database),
|
|
eventBus: eventBus,
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) Start(addr string) error {
|
|
mux := http.NewServeMux()
|
|
|
|
// API routes
|
|
mux.HandleFunc("/api/repos", s.handleRepos)
|
|
mux.HandleFunc("/api/repos/", s.handleRepoActions)
|
|
|
|
// SSE endpoint
|
|
sseHandler := NewSSEHandler(s.eventBus)
|
|
mux.Handle("/events", sseHandler)
|
|
|
|
// Health check
|
|
mux.HandleFunc("/health", s.handleHealth)
|
|
|
|
s.server = &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
}
|
|
|
|
log.Printf("HTTP server starting on %s", addr)
|
|
return s.server.ListenAndServe()
|
|
}
|
|
|
|
func (s *HTTPServer) Shutdown(ctx context.Context) error {
|
|
if s.server != nil {
|
|
return s.server.Shutdown(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *HTTPServer) handleRepos(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
s.getRepos(w, r)
|
|
case http.MethodPost:
|
|
s.createRepo(w, r)
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) handleRepo(w http.ResponseWriter, r *http.Request) {
|
|
repoID, err := extractRepoID(r.URL.Path)
|
|
if err != nil {
|
|
http.Error(w, "Invalid repo ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
s.getRepo(w, r, repoID)
|
|
case http.MethodDelete:
|
|
s.deleteRepo(w, r, repoID)
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) handleRepoActions(w http.ResponseWriter, r *http.Request) {
|
|
repoID, err := extractRepoID(r.URL.Path)
|
|
if err != nil {
|
|
http.Error(w, "Invalid repo ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Check if this is an action endpoint
|
|
parts := strings.Split(r.URL.Path, "/")
|
|
if len(parts) >= 5 {
|
|
// This is an action endpoint (/api/repos/{id}/{action})
|
|
action := extractAction(r.URL.Path)
|
|
|
|
switch r.Method {
|
|
case http.MethodPost:
|
|
switch action {
|
|
case "stop":
|
|
s.stopRepo(w, r, repoID)
|
|
case "restart":
|
|
s.restartRepo(w, r, repoID)
|
|
default:
|
|
http.Error(w, "Invalid action", http.StatusBadRequest)
|
|
}
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
} else {
|
|
// This is a repo detail endpoint (/api/repos/{id})
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
s.getRepo(w, r, repoID)
|
|
case http.MethodDelete:
|
|
s.deleteRepo(w, r, repoID)
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *HTTPServer) getRepos(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.URL.Query().Get("user_id")
|
|
|
|
var repos []*model.Repo
|
|
var err error
|
|
|
|
if userID != "" {
|
|
repos, err = s.repoStore.ListByUser(userID)
|
|
} else {
|
|
repos, err = s.repoStore.ListByStatus(model.StatusDeployed)
|
|
}
|
|
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(repos)
|
|
}
|
|
|
|
func (s *HTTPServer) getRepo(w http.ResponseWriter, r *http.Request, repoID int64) {
|
|
repo, err := s.repoStore.Get(repoID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(repo)
|
|
}
|
|
|
|
func (s *HTTPServer) createRepo(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
RepoURL string `json:"repo_url"`
|
|
UserID string `json:"user_id"`
|
|
Type model.RepoType `json:"type"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
repo := &model.Repo{
|
|
RepoURL: req.RepoURL,
|
|
Status: model.StatusNeedToDeploy,
|
|
UserID: req.UserID,
|
|
Type: req.Type,
|
|
}
|
|
|
|
if err := s.repoStore.Create(repo); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Publish event
|
|
s.eventBus.Publish(events.NewRepoEvent(repo, events.EventTypeRepoCreated))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(repo)
|
|
}
|
|
|
|
func (s *HTTPServer) deleteRepo(w http.ResponseWriter, r *http.Request, repoID int64) {
|
|
repo, err := s.repoStore.Get(repoID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Update status to deleted for cleanup
|
|
repo.Status = model.StatusDeleted
|
|
if err := s.repoStore.Update(repo); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Publish event
|
|
s.eventBus.Publish(events.NewRepoEvent(repo, events.EventTypeRepoDeleted))
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (s *HTTPServer) stopRepo(w http.ResponseWriter, r *http.Request, repoID int64) {
|
|
repo, err := s.repoStore.Get(repoID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
repo.Status = model.StatusStopped
|
|
if err := s.repoStore.Update(repo); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Publish event
|
|
s.eventBus.Publish(events.NewRepoEvent(repo, events.EventTypeRepoUpdated))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(repo)
|
|
}
|
|
|
|
func (s *HTTPServer) restartRepo(w http.ResponseWriter, r *http.Request, repoID int64) {
|
|
repo, err := s.repoStore.Get(repoID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
repo.Status = model.StatusRestarting
|
|
if err := s.repoStore.Update(repo); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Publish event
|
|
s.eventBus.Publish(events.NewRepoEvent(repo, events.EventTypeRepoUpdated))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(repo)
|
|
}
|
|
|
|
func extractRepoID(path string) (int64, error) {
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) < 4 {
|
|
return 0, fmt.Errorf("invalid path")
|
|
}
|
|
|
|
return strconv.ParseInt(parts[3], 10, 64)
|
|
}
|
|
|
|
func extractAction(path string) string {
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) >= 5 {
|
|
return parts[4]
|
|
}
|
|
return ""
|
|
}
|