@@ -34,7 +34,7 @@ func (s *Server) inviteHandler(w http.ResponseWriter, r *http.Request) {
3434 user , err := s .getSessionUser (r )
3535 if err != nil {
3636 slog .Error ("inviteHandler: failed to get session user" , "err" , err )
37- renderError (w , "Authentication required" , http . StatusUnauthorized )
37+ renderError (w , "Authentication required" )
3838 return
3939 }
4040
@@ -43,7 +43,7 @@ func (s *Server) inviteHandler(w http.ResponseWriter, r *http.Request) {
4343 // Check $50+ sponsorship tier (5000 cents)
4444 if ! user .IsSponsorAtTier (5000 ) {
4545 slog .Error ("inviteHandler: user not eligible for team invitation" , "user" , user .Login , "user_id" , user .ID )
46- renderError (w , "Requires $50+/month sponsorship" , http . StatusForbidden )
46+ renderError (w , "Requires $50+/month sponsorship" )
4747 return
4848 }
4949
@@ -52,14 +52,14 @@ func (s *Server) inviteHandler(w http.ResponseWriter, r *http.Request) {
5252 // Parse form
5353 if err := r .ParseForm (); err != nil {
5454 slog .Error ("inviteHandler: failed to parse form" , "err" , err )
55- renderError (w , "Invalid form data" , http . StatusBadRequest )
55+ renderError (w , "Invalid form data" )
5656 return
5757 }
5858
5959 username := r .FormValue ("username" )
6060 if username == "" {
6161 slog .Error ("inviteHandler: empty username provided" , "user_id" , user .ID )
62- renderError (w , "Username required" , http . StatusBadRequest )
62+ renderError (w , "Username required" )
6363 return
6464 }
6565
@@ -80,10 +80,10 @@ func (s *Server) inviteHandler(w http.ResponseWriter, r *http.Request) {
8080 slog .Error ("inviteHandler: failed to invite to team" , "user" , username , "err" , err , "invited_by" , user .Login )
8181 // Check for common errors
8282 if strings .Contains (err .Error (), "404" ) || strings .Contains (err .Error (), "422" ) {
83- renderError (w , "User not found or already invited" , http . StatusBadRequest )
83+ renderError (w , "User not found or already invited" )
8484 return
8585 }
86- renderError (w , "Failed to invite: " + err .Error (), http . StatusInternalServerError )
86+ renderError (w , "Failed to invite: " + err .Error ())
8787 return
8888 }
8989
@@ -118,7 +118,7 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
118118 user , err := s .getSessionUser (r )
119119 if err != nil {
120120 slog .Error ("logoHandler: failed to get session user" , "err" , err )
121- renderError (w , "Authentication required" , http . StatusUnauthorized )
121+ renderError (w , "Authentication required" )
122122 return
123123 }
124124
@@ -127,7 +127,7 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
127127 // Check user is a sponsor (any tier)
128128 if ! user .IsSponsorAtTier (100 ) {
129129 slog .Error ("logoHandler: user not a sponsor" , "user" , user .Login , "user_id" , user .ID )
130- renderError (w , "Requires active sponsorship" , http . StatusForbidden )
130+ renderError (w , "Requires active sponsorship" )
131131 return
132132 }
133133
@@ -136,7 +136,7 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
136136 // Parse multipart form (5MB max)
137137 if err := r .ParseMultipartForm (5 * 1024 * 1024 ); err != nil {
138138 slog .Error ("logoHandler: failed to parse multipart form" , "err" , err )
139- renderError (w , "Invalid form data" , http . StatusBadRequest )
139+ renderError (w , "Invalid form data" )
140140 return
141141 }
142142
@@ -145,15 +145,15 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
145145
146146 if companyName == "" || website == "" {
147147 slog .Error ("logoHandler: missing required fields" , "user_id" , user .ID , "company" , companyName , "website" , website )
148- renderError (w , "Company name and website are required" , http . StatusBadRequest )
148+ renderError (w , "Company name and website are required" )
149149 return
150150 }
151151
152152 // Get uploaded file
153153 file , header , err := r .FormFile ("logo" )
154154 if err != nil {
155155 slog .Error ("logoHandler: failed to get logo file" , "err" , err )
156- renderError (w , "Logo file required" , http . StatusBadRequest )
156+ renderError (w , "Logo file required" )
157157 return
158158 }
159159 defer file .Close ()
@@ -167,15 +167,15 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
167167 // Validate file size
168168 if header .Size > 5 * 1024 * 1024 {
169169 slog .Error ("logoHandler: file too large" , "user_id" , user .ID , "size" , header .Size )
170- renderError (w , "File too large (max 5MB)" , http . StatusBadRequest )
170+ renderError (w , "File too large (max 5MB)" )
171171 return
172172 }
173173
174174 // Read file into memory for S3 upload
175175 fileData , err := io .ReadAll (file )
176176 if err != nil {
177177 slog .Error ("logoHandler: failed to read file" , "err" , err )
178- renderError (w , "Failed to read file" , http . StatusInternalServerError )
178+ renderError (w , "Failed to read file" )
179179 return
180180 }
181181
@@ -212,7 +212,7 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
212212 _ , err := s .s3Client .PutObject (r .Context (), putInput )
213213 if err != nil {
214214 slog .Error ("logoHandler: failed to upload to S3" , "err" , err , "user_id" , user .ID )
215- renderError (w , "Failed to upload logo: " + err .Error (), http . StatusInternalServerError )
215+ renderError (w , "Failed to upload logo: " + err .Error ())
216216 return
217217 }
218218
@@ -263,7 +263,7 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
263263 createdIssue , _ , err := s .ghClient .Issues .Create (r .Context (), "TecharoHQ" , * logoSubmissionRepo , issue )
264264 if err != nil {
265265 slog .Error ("logoHandler: failed to create GitHub issue" , "err" , err , "user_id" , user .ID , "company" , companyName )
266- renderError (w , "Failed to create issue: " + err .Error (), http . StatusInternalServerError )
266+ renderError (w , "Failed to create issue: " + err .Error ())
267267 return
268268 }
269269
@@ -301,9 +301,10 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
301301}
302302
303303// renderError renders an error message for HTMX.
304- func renderError (w http.ResponseWriter , message string , statusCode int ) {
304+ // Always returns 200 so HTMX swaps the response into the target element.
305+ func renderError (w http.ResponseWriter , message string ) {
305306 w .Header ().Set ("Content-Type" , "text/html" )
306- w .WriteHeader (statusCode )
307+ w .WriteHeader (http . StatusOK )
307308 templates .FormResult (message , false ).Render (context .Background (), w )
308309}
309310
@@ -334,7 +335,7 @@ func (s *Server) thothTokenHandler(w http.ResponseWriter, r *http.Request) {
334335 user , err := s .getSessionUser (r )
335336 if err != nil {
336337 slog .Error ("thothTokenHandler: failed to get session user" , "err" , err )
337- renderError (w , "Authentication required" , http . StatusUnauthorized )
338+ renderError (w , "Authentication required" )
338339 return
339340 }
340341
@@ -343,16 +344,20 @@ func (s *Server) thothTokenHandler(w http.ResponseWriter, r *http.Request) {
343344 // Check sponsorship tier (any active sponsorship)
344345 if ! user .IsSponsorAtTier (100 ) {
345346 slog .Error ("thothTokenHandler: user not a sponsor" , "user" , user .Login , "user_id" , user .ID )
346- renderError (w , "Requires active sponsorship" , http . StatusForbidden )
347+ renderError (w , "Requires active sponsorship" )
347348 return
348349 }
349350
350351 // Create Thoth user if not already provisioned
351352 if user .ThothUserID == nil {
352353 if user .Email == "" {
353- slog .Error ("thothTokenHandler: user has no email address" , "user_id" , user .ID , "login" , user .Login )
354- renderError (w , "Email address required. Please update your profile." , http .StatusBadRequest )
355- return
354+ user .Email = user .Login + "@fake-address.invalid"
355+ slog .Info ("thothTokenHandler: generated fake email for user" , "user_id" , user .ID , "login" , user .Login , "email" , user .Email )
356+ if err := s .db .Model (user ).Update ("email" , user .Email ).Error ; err != nil {
357+ slog .Error ("thothTokenHandler: failed to save fake email" , "err" , err , "user_id" , user .ID )
358+ renderError (w , "Failed to save user email" )
359+ return
360+ }
356361 }
357362
358363 slog .Debug ("thothTokenHandler: creating Thoth user" , "user_id" , user .ID , "login" , user .Login )
@@ -364,7 +369,7 @@ func (s *Server) thothTokenHandler(w http.ResponseWriter, r *http.Request) {
364369 })
365370 if err != nil {
366371 slog .Error ("thothTokenHandler: failed to create Thoth user" , "err" , err , "user_id" , user .ID )
367- renderError (w , "Failed to create Thoth user: " + err .Error (), http . StatusInternalServerError )
372+ renderError (w , "Failed to create Thoth user: " + err .Error ())
368373 return
369374 }
370375
@@ -373,7 +378,7 @@ func (s *Server) thothTokenHandler(w http.ResponseWriter, r *http.Request) {
373378
374379 if err := s .db .Save (user ).Error ; err != nil {
375380 slog .Error ("thothTokenHandler: failed to save Thoth user ID" , "err" , err , "user_id" , user .ID )
376- renderError (w , "Failed to save Thoth user: " + err .Error (), http . StatusInternalServerError )
381+ renderError (w , "Failed to save Thoth user: " + err .Error ())
377382 return
378383 }
379384
@@ -389,7 +394,7 @@ func (s *Server) thothTokenHandler(w http.ResponseWriter, r *http.Request) {
389394 })
390395 if err != nil {
391396 slog .Error ("thothTokenHandler: failed to issue JWT" , "err" , err , "user_id" , user .ID )
392- renderError (w , "Failed to issue token: " + err .Error (), http . StatusInternalServerError )
397+ renderError (w , "Failed to issue token: " + err .Error ())
393398 return
394399 }
395400
0 commit comments