package http import ( "encoding/json" "fmt" "log/slog" "net/http" "git.yigid.dev/fyb/tingz/internal/deploy" "git.yigid.dev/fyb/tingz/internal/user" ) type Server struct { userMgr *user.Manager deployMgr *deploy.Manager logger *slog.Logger baseURL string } func NewServer(userMgr *user.Manager, deployMgr *deploy.Manager, logger *slog.Logger, baseURL string) *Server { return &Server{ userMgr: userMgr, deployMgr: deployMgr, logger: logger, baseURL: baseURL, } } func (s *Server) handleAuthCreate(w http.ResponseWriter, r *http.Request) { var req struct { Username string `json:"username"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { s.logger.Warn("failed to decode request", slog.String("error", err.Error())) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "invalid request body", }) return } if !user.ValidateUsername(req.Username) { s.logger.Warn("invalid username", slog.String("username", req.Username)) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "invalid username format", }) return } token, err := s.userMgr.CreateOrUpdate(r.Context(), req.Username) if err != nil { s.logger.Error("failed to create/update user", slog.String("username", req.Username), slog.String("error", err.Error())) writeJSON(w, http.StatusInternalServerError, map[string]string{ "status": "error", "error": "failed to create user", }) return } s.logger.Info("user created/updated", slog.String("username", req.Username)) writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", "token": token, }) } func (s *Server) handleAuthDelete(w http.ResponseWriter, r *http.Request) { var req struct { Username string `json:"username"` Files string `json:"files"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { s.logger.Warn("failed to decode request", slog.String("error", err.Error())) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "invalid request body", }) return } if !user.ValidateUsername(req.Username) { s.logger.Warn("invalid username", slog.String("username", req.Username)) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "invalid username format", }) return } if req.Files == "" { req.Files = "persist" } deleteFiles := false if req.Files == "delete" { deleteFiles = true } else if req.Files != "persist" { writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "files must be 'persist' or 'delete'", }) return } err := s.userMgr.Delete(r.Context(), req.Username, deleteFiles) if err != nil { s.logger.Error("failed to delete user", slog.String("username", req.Username), slog.String("error", err.Error())) writeJSON(w, http.StatusInternalServerError, map[string]string{ "status": "error", "error": err.Error(), }) return } s.logger.Info("user deleted", slog.String("username", req.Username), slog.Bool("files_deleted", deleteFiles)) writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", }) } func (s *Server) handleDeploy(w http.ResponseWriter, r *http.Request) { username, ok := getUsernameFromContext(r.Context()) if !ok { writeJSON(w, http.StatusUnauthorized, map[string]string{ "status": "error", "error": "unauthorized", }) return } if err := r.ParseMultipartForm(32 << 20); err != nil { s.logger.Warn("failed to parse multipart form", slog.String("error", err.Error())) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "failed to parse multipart form", }) return } project := r.FormValue("project") if !user.ValidateProjectName(project) { s.logger.Warn("invalid project name", slog.String("project", project)) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "invalid project name format", }) return } file, header, err := r.FormFile("file") if err != nil { s.logger.Warn("failed to get file from form", slog.String("error", err.Error())) writeJSON(w, http.StatusBadRequest, map[string]string{ "status": "error", "error": "missing or invalid file", }) return } defer file.Close() s.logger.Info("processing deployment", slog.String("username", username), slog.String("project", project), slog.String("filename", header.Filename), slog.Int64("size", header.Size)) releaseID, err := s.deployMgr.Deploy(r.Context(), username, project, file) if err != nil { s.logger.Error("deployment failed", slog.String("username", username), slog.String("project", project), slog.String("error", err.Error())) writeJSON(w, http.StatusInternalServerError, map[string]string{ "status": "error", "error": err.Error(), }) return } url := fmt.Sprintf("%s/%s/%s/", s.baseURL, username, project) writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", "project": project, "username": username, "url": url, "release": releaseID, }) } func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", }) } func (s *Server) handleHello(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{ "message": "Close the world, .txen eht nepO", "author": "Yigid BALABAN ", "authorHomepage": "https://yigid.dev/", }) } func writeJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) }