package console import ( "errors" "log" "net/http" ) // ErrTemporaryApp is the store operations' refusal (RenameAppIn, MoveApp, // SetAppControlPlane(…, true)) for a row with expires_at > 0. A temporary // row cannot change its name, its account, or its protection class // (temporary static sites spec §4): rename and move would point it at bucket // state it did not create, and the control-plane flag would take it out of // automatic destruction's reach. var ErrTemporaryApp = errors.New("the app is temporary. Make it permanent first with carlos apps keep") // temporaryRefusal answers "may this door proceed?" for the §4 table. msg is // "" when the app is not temporary; otherwise the sentence to show, naming // `carlos apps keep`, and the status a JSON door sends. A row that cannot be // read refuses with 503: "could not check" is not "permanent". // // Callers MUST be past their own membership gate first, for the same reason // controlPlaneRefusal gives: consulted earlier, this is an oracle. // // action is a whole clause ("ship a binary release to it"), so the sentence // reads as English for every door. func (s *Server) temporaryRefusal(acct *Account, app, action string) (msg string, status int) { life, err := s.store.AppLife(acct.ID, app) if err != nil { log.Printf("console: temporary check %s/%s: %v", acct.Sqid, app, err) return "Could not check whether " + app + " is temporary. Nothing was changed. Try again.", http.StatusServiceUnavailable } if life == nil || !life.Temporary() { return "", 0 } return app + " is temporary, so you cannot " + action + ". " + "Make it permanent first with: carlos apps keep --app " + app, http.StatusConflict } // refuseTemporary is temporaryRefusal for the JSON doors: it writes the // refusal and returns true when the caller must stop. func (s *Server) refuseTemporary(w http.ResponseWriter, acct *Account, app, action string) bool { msg, status := s.temporaryRefusal(acct, app, action) if msg == "" { return false } jsonError(w, status, msg) return true }