init
This commit is contained in:
253
internal/api/http.go
Normal file
253
internal/api/http.go
Normal file
@@ -0,0 +1,253 @@
|
||||
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.handleRepo)
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user