From ffcb97dae63ee04f610f1a032ea9b78939271016 Mon Sep 17 00:00:00 2001 From: tscrond Date: Thu, 19 Jun 2025 13:29:43 +0200 Subject: [PATCH] reorg api with cleaner pattern + add unspecified shares with optional emails --- internal/api/auth.go | 107 ++++----------------- internal/api/data_delete.go | 58 +++-------- internal/api/data_get.go | 53 +++------- internal/api/notes_handler.go | 78 +++------------ internal/api/sharing_get.go | 176 ++++++++-------------------------- internal/api/sharing_post.go | 56 ++++++----- internal/api/upload_post.go | 43 ++------- pkg/lib.go | 28 ++++++ 8 files changed, 162 insertions(+), 437 deletions(-) diff --git a/internal/api/auth.go b/internal/api/auth.go index c4f4986..ba2bb31 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -14,6 +14,7 @@ import ( "github.com/tscrond/dropper/internal/mappings" "github.com/tscrond/dropper/internal/repo/sqlc" "github.com/tscrond/dropper/internal/userdata" + pkg "github.com/tscrond/dropper/pkg" "golang.org/x/oauth2" ) @@ -30,21 +31,13 @@ func (s *APIServer) authCallback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() code := r.URL.Query().Get("code") if code == "" { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "status": http.StatusBadRequest, - "response": "Missing authorization code", - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "Missing authorization code") return } t, err := s.OAuthConfig.Exchange(ctx, code) if err != nil { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "status": http.StatusBadRequest, - "response": "Missing authorization code", - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "Missing authorization code") return } @@ -53,11 +46,7 @@ func (s *APIServer) authCallback(w http.ResponseWriter, r *http.Request) { // Getting the user public details from google API endpoint resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo") if err != nil { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "status": http.StatusBadRequest, - "response": "Missing authorization code", - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "Missing authorization code") return } defer resp.Body.Close() @@ -67,11 +56,7 @@ func (s *APIServer) authCallback(w http.ResponseWriter, r *http.Request) { // Reading the JSON body using JSON decoder err = json.NewDecoder(resp.Body).Decode(&jsonResp) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "status": http.StatusInternalServerError, - "response": err.Error(), - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "Error decoding JSON response") return } @@ -182,32 +167,20 @@ func (s *APIServer) authMiddleware(next http.Handler) http.Handler { cookie, err := r.Cookie("access_token") // fmt.Println(cookie) if err != nil || cookie.Value == "" { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "status": http.StatusForbidden, - "response": "Unauthorized", - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "", "Unauthorized") return } valid, verifiedUserData := s.verifyToken(cookie.Value) if !valid { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "status": http.StatusForbidden, - "response": "Unauthorized (invalid or expired session)", - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "", "Unauthorized (invalid or expired session)") return } // log.Println("verified user:", verifiedUserData) userInfo, err := s.fetchUserInfo(cookie.Value) if err != nil { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "status": http.StatusForbidden, - "response": "Could not fetch logged user info", - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "", "Could not fetch logged user info") return } // log.Println("logged user info::", userInfo) @@ -256,10 +229,7 @@ func (s *APIServer) logout(w http.ResponseWriter, r *http.Request) { // Check if access_token cookie exists cookie, err := r.Cookie("access_token") if err != nil { - w.WriteHeader(http.StatusNotFound) - JSON(w, map[string]any{ - "response": "cookie_not_found", - "code": http.StatusNotFound, + pkg.WriteJSONResponse(w, http.StatusNotFound, "cookie_not_found", map[string]any{ "logout_successful": true, }) return @@ -272,9 +242,7 @@ func (s *APIServer) logout(w http.ResponseWriter, r *http.Request) { req, err := http.NewRequest("POST", revokeURL, nil) if err != nil { - JSON(w, map[string]interface{}{ - "response": "internal_server_error", - "code": http.StatusInternalServerError, + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "logout_error", map[string]any{ "logout_successful": false, }) return @@ -287,9 +255,7 @@ func (s *APIServer) logout(w http.ResponseWriter, r *http.Request) { client := http.DefaultClient resp, err := client.Do(req) if err != nil { - JSON(w, map[string]any{ - "response": "internal_server_error", - "code": http.StatusInternalServerError, + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "logout_error", map[string]any{ "logout_successful": false, }) return @@ -298,10 +264,7 @@ func (s *APIServer) logout(w http.ResponseWriter, r *http.Request) { // Check response status if resp.StatusCode != http.StatusOK { - w.WriteHeader(resp.StatusCode) - JSON(w, map[string]any{ - "response": "failed_to_revoke_token", - "code": resp.StatusCode, + pkg.WriteJSONResponse(w, resp.StatusCode, "failed_to_revoke_token", map[string]any{ "logout_successful": false, }) return @@ -321,19 +284,14 @@ func (s *APIServer) logout(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) // Return success response - JSON(w, map[string]any{ - "response": "session_invalidated", - "code": http.StatusOK, + pkg.WriteJSONResponse(w, http.StatusOK, "session_invalidated", map[string]any{ "logout_successful": true, }) } func (s *APIServer) isValid(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", map[string]any{ "authenticated": false, "user_info": nil, }) @@ -341,14 +299,10 @@ func (s *APIServer) isValid(w http.ResponseWriter, r *http.Request) { } cookie, err := r.Cookie("access_token") if err != nil || cookie.Value == "" { - w.WriteHeader(http.StatusForbidden) - response := map[string]any{ - "response": "access_denied", - "code": http.StatusForbidden, + pkg.WriteJSONResponse(w, http.StatusForbidden, "access_denied", map[string]any{ "authenticated": false, "user_info": nil, - } - JSON(w, response) + }) return } @@ -356,25 +310,17 @@ func (s *APIServer) isValid(w http.ResponseWriter, r *http.Request) { valid, userInfo := s.verifyToken(cookie.Value) if !valid { - w.WriteHeader(http.StatusForbidden) - response := map[string]interface{}{ - "response": "access_denied", - "code": http.StatusForbidden, + pkg.WriteJSONResponse(w, http.StatusForbidden, "access_denied", map[string]any{ "authenticated": false, "user_info": nil, - } - JSON(w, response) + }) return } - w.WriteHeader(http.StatusOK) - response := map[string]interface{}{ - "response": "access_granted", - "code": http.StatusOK, + pkg.WriteJSONResponse(w, http.StatusOK, "access_granted", map[string]any{ "authenticated": true, "user_info": userInfo, - } - JSON(w, response) + }) } func (s *APIServer) fetchUserInfo(accessToken string) (*userdata.AuthorizedUserInfo, error) { @@ -402,16 +348,3 @@ func (s *APIServer) fetchUserInfo(accessToken string) (*userdata.AuthorizedUserI return &user, nil } - -func JSON(w http.ResponseWriter, v any) { - w.Header().Set("Content-Type", "application/json") - - if err := json.NewEncoder(w).Encode(v); err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "status": http.StatusInternalServerError, - "response": "Error encoding JSON", - }) - return - } -} diff --git a/internal/api/data_delete.go b/internal/api/data_delete.go index 13a43f3..bb930b9 100644 --- a/internal/api/data_delete.go +++ b/internal/api/data_delete.go @@ -8,27 +8,20 @@ import ( "github.com/tscrond/dropper/internal/repo/sqlc" "github.com/tscrond/dropper/internal/userdata" + pkg "github.com/tscrond/dropper/pkg" ) func (s *APIServer) deleteFile(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodDelete { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", nil) return } object := r.URL.Query().Get("file") if object == "" { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", nil) } // parse data of logged in user @@ -36,11 +29,7 @@ func (s *APIServer) deleteFile(w http.ResponseWriter, r *http.Request) { authUserData, ok := authorizedUserData.(*userdata.AuthorizedUserInfo) if !ok { log.Println("cannot read authorized user data") - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", nil) return } @@ -56,31 +45,20 @@ func (s *APIServer) deleteFile(w http.ResponseWriter, r *http.Request) { FileName: object, }); err != nil { log.Println("errors deleting file from DB: ", err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "delete_file_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "delete_file_error", nil) return } - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "success", - "code": http.StatusOK, + + pkg.WriteJSONResponse(w, http.StatusOK, "success", map[string]any{ "file_deleted": object, }) - } func (s *APIServer) deleteAccount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodDelete { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", nil) return } @@ -89,33 +67,23 @@ func (s *APIServer) deleteAccount(w http.ResponseWriter, r *http.Request) { authUserData, ok := authorizedUserData.(*userdata.AuthorizedUserInfo) if !ok { log.Println("cannot read authorized user data") - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", nil) return } deletedAccount, err := s.repository.Queries.DeleteAccount(ctx, authUserData.Id) if err != nil { log.Println("issues deleting object: ", err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "authorization_failed", nil) return } - - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "success", - "code": http.StatusOK, + + pkg.WriteJSONResponse(w, http.StatusOK, "success", map[string]any{ "account_deleted": map[string]any{ "id": deletedAccount.GoogleID, "email": deletedAccount.UserEmail, "user_name": deletedAccount.UserName.String, }, }) + } diff --git a/internal/api/data_get.go b/internal/api/data_get.go index 44bc79d..31105b5 100644 --- a/internal/api/data_get.go +++ b/internal/api/data_get.go @@ -4,57 +4,44 @@ import ( "net/http" "github.com/tscrond/dropper/internal/userdata" + "github.com/tscrond/dropper/pkg" ) func (s *APIServer) getUserData(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "bad_request") return } userData, ok := r.Context().Value(userdata.AuthorizedUserContextKey).(*userdata.AuthorizedUserInfo) // fmt.Println(userData) if !ok { - JSON(w, map[string]interface{}{ - "response": "access_denied", - "code": http.StatusForbidden, + pkg.WriteJSONResponse(w, http.StatusForbidden, "Access Denied", map[string]any{ "user_data": nil, }) return } - response := map[string]interface{}{ - "response": "ok", - "code": http.StatusOK, + response := map[string]any{ "user_data": userData, } - JSON(w, response) + pkg.WriteJSONResponse(w, http.StatusOK, "", response) } func (s *APIServer) getUserBucketData(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "bad_request") return } userData, ok := r.Context().Value(userdata.AuthorizedUserContextKey).(*userdata.AuthorizedUserInfo) // fmt.Println(userData) if !ok { - JSON(w, map[string]any{ - "response": "access_denied", - "code": http.StatusForbidden, + pkg.WriteJSONResponse(w, http.StatusForbidden, "access_denied", map[string]any{ "user_data": nil, }) return @@ -62,30 +49,20 @@ func (s *APIServer) getUserBucketData(w http.ResponseWriter, r *http.Request) { bucketData, err := s.bucketHandler.GetUserBucketData(ctx, userData.Id) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "internal_error", - "code": http.StatusInternalServerError, + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "internal_error", map[string]any{ "bucket_data": nil, }) return } - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "ok", - "code": http.StatusOK, + pkg.WriteJSONResponse(w, http.StatusOK, "internal_error", map[string]any{ "bucket_data": bucketData, }) } func (s *APIServer) getUserPrivateFileByName(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -95,17 +72,11 @@ func (s *APIServer) getUserPrivateFileByName(w http.ResponseWriter, r *http.Requ downloadToken, err := s.repository.Queries.GetPrivateDownloadTokenByFileName(ctx, fileName) if err != nil { - JSON(w, map[string]any{ - "response": "internal_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "internal_error", "") return } - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "ok", - "code": http.StatusOK, + pkg.WriteJSONResponse(w, http.StatusOK, "", map[string]any{ "private_download_token": downloadToken.String, }) } diff --git a/internal/api/notes_handler.go b/internal/api/notes_handler.go index 845dd11..11e839f 100644 --- a/internal/api/notes_handler.go +++ b/internal/api/notes_handler.go @@ -10,6 +10,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/tscrond/dropper/internal/repo/sqlc" "github.com/tscrond/dropper/internal/userdata" + pkg "github.com/tscrond/dropper/pkg" ) type NoteContent struct { @@ -30,11 +31,7 @@ func (s *APIServer) editFileNotes(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodPut { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -43,11 +40,7 @@ func (s *APIServer) editFileNotes(w http.ResponseWriter, r *http.Request) { authUserData, ok := authorizedUserData.(*userdata.AuthorizedUserInfo) if !ok { log.Println("cannot read authorized user data") - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", "") return } @@ -55,11 +48,7 @@ func (s *APIServer) editFileNotes(w http.ResponseWriter, r *http.Request) { var req NoteContent if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "no content", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "no_content", "") return } @@ -67,22 +56,14 @@ func (s *APIServer) editFileNotes(w http.ResponseWriter, r *http.Request) { id, err := s.repository.Queries.GetFileFromChecksum(ctx, md5Checksum) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "cannot get file", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "cannot_get_file", "") return } log.Println("Sanitized note content: ", sanitizedNoteContent) if utf8.RuneCountInString(sanitizedNoteContent) > 500 { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "too_many_characters", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "too_many_characters", "") return } @@ -94,30 +75,19 @@ func (s *APIServer) editFileNotes(w http.ResponseWriter, r *http.Request) { Content: sanitizedNoteContent, }, ); err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "cannot_update", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "cannot_update_resource", "") return } - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "created_note", - "code": http.StatusOK, - "note": sanitizedNoteContent, + pkg.WriteJSONResponse(w, http.StatusOK, "created_note", map[string]any{ + "note": sanitizedNoteContent, }) } func (s *APIServer) getFileNotes(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -127,30 +97,19 @@ func (s *APIServer) getFileNotes(w http.ResponseWriter, r *http.Request) { if !ok { log.Println("cannot read authorized user data") w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", "") return } md5Checksum := chi.URLParam(r, "checksum") if md5Checksum == "" { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "checksum_empty", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "checksum_empty", "") return } fileId, err := s.repository.Queries.GetFileFromChecksum(ctx, md5Checksum) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "error querying file id", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "error_query_file_id", "") return } @@ -159,17 +118,10 @@ func (s *APIServer) getFileNotes(w http.ResponseWriter, r *http.Request) { FileID: sql.NullInt32{Valid: true, Int32: fileId}, }) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "error getting note", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "error_get_note", "") return } - - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "code": http.StatusOK, + pkg.WriteJSONResponse(w, http.StatusOK, "", map[string]any{ "content": note.Content, }) } diff --git a/internal/api/sharing_get.go b/internal/api/sharing_get.go index 7c7f318..de6bfc3 100644 --- a/internal/api/sharing_get.go +++ b/internal/api/sharing_get.go @@ -13,6 +13,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/tscrond/dropper/internal/repo/sqlc" "github.com/tscrond/dropper/internal/userdata" + pkg "github.com/tscrond/dropper/pkg" ) func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http.Request) { @@ -26,11 +27,7 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -39,22 +36,15 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. authUserData, ok := authorizedUserData.(*userdata.AuthorizedUserInfo) if !ok { log.Println("cannot read authorized user data") - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "authorization_failed", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", "") return } token := chi.URLParam(r, "token") if token == "" { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "empty_token", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "empty_token", "") + return } mode := r.URL.Query().Get("mode") // "inline" or "download" @@ -62,11 +52,7 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. if mode == "inline" { disposition = "inline" } else if mode != "download" && mode != "" { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "invalid_download_mode", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "invalid_download_mode", "") return } @@ -74,38 +60,22 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. _, err := s.repository.Queries.GetFileIdFromToken(ctx, sql.NullString{Valid: true, String: token}) if err != nil { if errors.Is(err, sql.ErrNoRows) { - w.WriteHeader(http.StatusNotFound) - JSON(w, map[string]any{ - "response": "file_does_not_exist", - "code": http.StatusNotFound, - }) + pkg.WriteJSONResponse(w, http.StatusNotFound, "file_does_not_exist", "") return } else { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "token_check_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "token_check_error", "") return } } bucketAndObjectRow, err := s.repository.Queries.GetBucketObjectAndOwnerFromPrivateToken(ctx, sql.NullString{Valid: true, String: token}) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "cannot_get_bucket_data", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "cannot_get_bucket_data", "") return } if authUserData.Id != bucketAndObjectRow.OwnerGoogleID.String { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "access_denied", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "access_denied", "") return } @@ -114,11 +84,7 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. signedUrl, err := s.bucketHandler.GenerateSignedURL(ctx, bucket, object, time.Now().Add(1*time.Minute)) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "cannot_generate_url", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "cannot_generate_url", "") return } @@ -127,11 +93,7 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. // 4. stream the file contents to the writer resp, err := http.Get(signedUrl) if err != nil || resp.StatusCode != http.StatusOK { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "signed_url_fetch_failed", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "signed_url_fetch_failed", "") return } defer resp.Body.Close() @@ -144,17 +106,14 @@ func (s *APIServer) downloadThroughProxyPersonal(w http.ResponseWriter, r *http. w.WriteHeader(http.StatusOK) // Stream the body - _, err = io.Copy(w, resp.Body) + bytes_written, err := io.Copy(w, resp.Body) if err != nil { // log.Println("streaming error:", err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "streaming_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "streaming_error", "") return } + log.Printf("written %d bytes", bytes_written) } func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) { @@ -169,11 +128,7 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -182,11 +137,7 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) if mode == "inline" { disposition = "inline" } else if mode != "download" && mode != "" { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "invalid_download_mode", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "invalid_download_mode", "") return } @@ -196,20 +147,12 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) // 1.5 check token expiration times expiresAt, err := s.repository.Queries.GetTokenExpirationTime(ctx, sharingToken) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "error_checking_expiration", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "error_checking_expiration", "") return } if expiresAt.Before(time.Now()) { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "response": "past_expiration_time_or_does_not_exist", - "code": http.StatusForbidden, - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "past_expiration_time_or_does_not_exist", "") return } @@ -217,18 +160,10 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) _, err = s.repository.Queries.GetSharedFileIdFromToken(ctx, sharingToken) if err != nil { if errors.Is(err, sql.ErrNoRows) { - w.WriteHeader(http.StatusNotFound) - JSON(w, map[string]any{ - "response": "token_does_not_exist", - "code": http.StatusNotFound, - }) + pkg.WriteJSONResponse(w, http.StatusNotFound, "token_does_not_exist", "") return } else { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "token_check_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "token_check_error", "") return } } @@ -236,11 +171,7 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) // 2.5 get the bucket of shared resource + get the object name bucketAndObject, err := s.repository.Queries.GetBucketAndObjectFromToken(ctx, sharingToken) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "cannot_get_bucket_data", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "cannot_get_bucket_data", "") return } @@ -252,22 +183,14 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) ) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "signed_url_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "signed_url_error", "") return } // 4. stream the file contents to the writer resp, err := http.Get(signedUrl) if err != nil || resp.StatusCode != http.StatusOK { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "signed_url_fetch_failed", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "signed_url_fetch_failed", "") return } defer resp.Body.Close() @@ -280,27 +203,22 @@ func (s *APIServer) downloadThroughProxy(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusOK) // Stream the body - _, err = io.Copy(w, resp.Body) + bytes_written, err := io.Copy(w, resp.Body) if err != nil { - log.Println("streaming error:", err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "streaming_error", - "code": http.StatusInternalServerError, - }) + // log.Println("streaming error:", err) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "streaming_error", "") return } + + log.Printf("written %d bytes", bytes_written) } func (s *APIServer) getDataSharedForUser(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + status := http.StatusBadRequest + pkg.WriteJSONResponse(w, status, "bad_request", "") return } @@ -315,20 +233,13 @@ func (s *APIServer) getDataSharedForUser(w http.ResponseWriter, r *http.Request) filesShared, err := s.repository.Queries.GetFilesSharedWithUser(ctx, sharedFor) if err != nil { log.Println(err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "internal_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "internal_error", "") } filesSharedPrep := prepSharedFilesFormat(filesShared) - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "ok", - "code": http.StatusOK, - "files": filesSharedPrep, + pkg.WriteJSONResponse(w, http.StatusOK, "", map[string]any{ + "files": filesSharedPrep, }) } @@ -336,11 +247,7 @@ func (s *APIServer) getDataSharedByUser(w http.ResponseWriter, r *http.Request) ctx := r.Context() if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } @@ -355,20 +262,13 @@ func (s *APIServer) getDataSharedByUser(w http.ResponseWriter, r *http.Request) filesShared, err := s.repository.Queries.GetFilesSharedByUser(ctx, sharedFor) if err != nil { log.Println(err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "internal_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "internal_error", "") } filesSharedPrep := prepSharedByFilesFormat(filesShared) - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "response": "ok", - "code": http.StatusOK, - "files": filesSharedPrep, + pkg.WriteJSONResponse(w, http.StatusOK, "", map[string]any{ + "files": filesSharedPrep, }) } diff --git a/internal/api/sharing_post.go b/internal/api/sharing_post.go index 40ead4f..29d0340 100644 --- a/internal/api/sharing_post.go +++ b/internal/api/sharing_post.go @@ -26,11 +26,7 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if r.Method != http.MethodPost { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "bad_request") return } @@ -39,18 +35,15 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { authUserData, ok := authorizedUserData.(*userdata.AuthorizedUserInfo) if !ok { log.Println("cannot read authorized user data") - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "not_authorized", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusUnauthorized, "", "unauthorized") return } type ShareRequest struct { - ForUser string `json:"email"` - Objects []string `json:"objects"` - Duration string `json:"duration"` + ForUser string `json:"email"` + Objects []string `json:"objects"` + Duration string `json:"duration"` + SendEmail bool `json:"send_email"` } var req ShareRequest @@ -62,11 +55,7 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { // calculate expiry time expiryTime, err := pkg.CustomParseDuration(req.Duration) if err != nil { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "response": "invalid_duration", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "", "invalid duration parameter") return } @@ -99,13 +88,10 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { if err != nil { log.Println("error inserting new share entry: ", err) - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "response": "insert_share_error", - "code": http.StatusInternalServerError, - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "", "sharing_error") return } + sharingInfos = append(sharingInfos, map[string]any{ "file": objectName, "shared_for": share.SharedFor.String, @@ -113,13 +99,25 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { "checksum": sharedObjectData.Md5Checksum, "expires_at": share.ExpiresAt, "sharing_token": share.SharingToken, + "sharing_link": fmt.Sprintf("%s/d/%s", s.backendConfig.BackendEndpoint, share.SharingToken), }) + filesForMail = append(filesForMail, mailtypes.FileInfo{ FileName: objectName, DownloadURL: fmt.Sprintf("%s/d/%s?mode=inline", s.backendConfig.BackendEndpoint, share.SharingToken), }) } + if !req.SendEmail { + pkg.WriteJSONResponse(w, http.StatusOK, "", + map[string]any{ + "sharing_info": sharingInfos, + "notification_status": "not_sent", + }, + ) + return + } + mailNotifier := mail.NewMailNotifier(s.emailSender) mailErr := mailNotifier.SendSharingNotification( @@ -135,10 +133,10 @@ func (s *APIServer) shareWith(w http.ResponseWriter, r *http.Request) { mailStatus = "failed" } - JSON(w, map[string]any{ - "response": "ok", - "code": http.StatusOK, - "sharing_info": sharingInfos, - "notification_status": mailStatus, - }) + pkg.WriteJSONResponse(w, http.StatusOK, "", + map[string]any{ + "sharing_info": sharingInfos, + "notification_status": mailStatus, + }, + ) } diff --git a/internal/api/upload_post.go b/internal/api/upload_post.go index 078df83..6a96bdb 100644 --- a/internal/api/upload_post.go +++ b/internal/api/upload_post.go @@ -8,34 +8,24 @@ import ( "github.com/tscrond/dropper/internal/filedata" "github.com/tscrond/dropper/internal/userdata" + pkg "github.com/tscrond/dropper/pkg" ) func (s *APIServer) uploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { - JSON(w, map[string]interface{}{ - "response": "bad_request", - "code": http.StatusBadRequest, - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") return } verifiedUserData, ok := r.Context().Value(userdata.VerifiedUserContextKey).(*userdata.VerifiedUserInfo) if !ok { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "status": http.StatusInternalServerError, - "response": "Failed to retrieve verified user data", - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "failed_to_retrieve_user_data", "") return } authorizedUserData, ok := r.Context().Value(userdata.AuthorizedUserContextKey).(*userdata.AuthorizedUserInfo) if !ok { - w.WriteHeader(http.StatusForbidden) - JSON(w, map[string]any{ - "status": http.StatusForbidden, - "response": "Failed to retrieve authorized user data", - }) + pkg.WriteJSONResponse(w, http.StatusForbidden, "failed_to_retrieve_user_data", "") return } // fmt.Println("Authorized User:", authorizedUserData) @@ -43,11 +33,7 @@ func (s *APIServer) uploadHandler(w http.ResponseWriter, r *http.Request) { // Get file from request file, header, err := r.FormFile("file") if err != nil { - w.WriteHeader(http.StatusBadRequest) - JSON(w, map[string]any{ - "status": http.StatusBadRequest, - "response": "Failed to parse file from request", - }) + pkg.WriteJSONResponse(w, http.StatusBadRequest, "failed_parsing_files", "") log.Println(err) return } @@ -56,11 +42,7 @@ func (s *APIServer) uploadHandler(w http.ResponseWriter, r *http.Request) { // Create fileData object fileData := filedata.NewFileData(file, header) if fileData == nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "status": http.StatusInternalServerError, - "response": "Invalid file data", - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "invalid_file_data", "") return } @@ -69,18 +51,11 @@ func (s *APIServer) uploadHandler(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, userdata.AuthorizedUserContextKey, authorizedUserData) if err := s.bucketHandler.SendFileToBucket(ctx, fileData); err != nil { - w.WriteHeader(http.StatusInternalServerError) - JSON(w, map[string]any{ - "status": http.StatusInternalServerError, - "response": "Failed to send file to bucket", - }) + pkg.WriteJSONResponse(w, http.StatusInternalServerError, "bucket_upload_failed", "") return } msg := fmt.Sprintf("Files uploaded successfully: %+v\n", fileData.RequestHeaders.Filename) - w.WriteHeader(http.StatusOK) - JSON(w, map[string]any{ - "status": http.StatusOK, - "response": msg, - }) + + pkg.WriteJSONResponse(w, http.StatusOK, "", msg) } diff --git a/pkg/lib.go b/pkg/lib.go index 89c3d46..dfbaebf 100644 --- a/pkg/lib.go +++ b/pkg/lib.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "os" "strconv" "time" @@ -137,3 +138,30 @@ func trimSpaces(s string) string { } return s[start:end] } + +func JSON(w http.ResponseWriter, v any) { + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(v); err != nil { + w.WriteHeader(http.StatusInternalServerError) + JSON(w, map[string]any{ + "status": http.StatusInternalServerError, + "response": "Error encoding JSON", + }) + return + } +} + +func WriteJSONResponse(w http.ResponseWriter, responseStatus int, customMessage string, customResponse any) { + w.WriteHeader(responseStatus) + + resp := map[string]any{ + "status": responseStatus, + "response": customResponse, + } + if customMessage != "" { + resp["msg"] = customMessage + } + + JSON(w, resp) +}