diff --git a/api/avatar.go b/api/avatar.go index 8c3b1965..778f64ec 100644 --- a/api/avatar.go +++ b/api/avatar.go @@ -1,26 +1 @@ package api - -import ( - "context" - "fmt" - "net/http" - - nhttp "github.com/Gleipnir-Technology/nidus-sync/http" - "github.com/Gleipnir-Technology/nidus-sync/platform" - "github.com/Gleipnir-Technology/nidus-sync/platform/file" -) - -func avatarPost(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) { - if len(uploads) == 0 { - return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found") - } - if len(uploads) != 1 { - return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time") - } - upload := uploads[0] - err := platform.AvatarCreate(r.Context(), u, upload) - if err != nil { - return "", nhttp.NewErrorStatus(http.StatusBadRequest, "Create avatar: %w", err) - } - return fmt.Sprintf("/avatar/%s", upload.UUID.String()), nil -} diff --git a/api/district.go b/api/district.go index 6f47ab28..5386836a 100644 --- a/api/district.go +++ b/api/district.go @@ -73,7 +73,7 @@ func apiGetDistrictLogo(w http.ResponseWriter, r *http.Request) { http.Error(w, "Logo not found", http.StatusNotFound) return } - file.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet()) + file.ImageFileToWriter(file.CollectionLogo, org.LogoUUID.MustGet(), w) return default: http.Error(w, "Too many organizations, this is a programmer error", http.StatusInternalServerError) diff --git a/api/handler.go b/api/handler.go index a102f8c4..0bca11c5 100644 --- a/api/handler.go +++ b/api/handler.go @@ -13,12 +13,34 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/file" "github.com/Gleipnir-Technology/nidus-sync/resource" + "github.com/google/uuid" "github.com/gorilla/schema" "github.com/rs/zerolog/log" ) var decoder = schema.NewDecoder() +type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus) + +func authenticatedHandlerGetImage(f handlerFunctionGetImage) http.Handler { + return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) { + ctx := r.Context() + collection, uid, e := f(ctx, r, u) + if e != nil { + log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api") + body, err := json.Marshal(ErrorAPI{Message: e.Error()}) + if err != nil { + log.Error().Err(err).Msg("failed to marshal error") + http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError) + return + } + http.Error(w, string(body), e.Status) + return + } + file.ImageFileToWriter(collection, uid, w) + }) +} + type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus) type wrappedHandler func(http.ResponseWriter, *http.Request) type contentAuthenticated[T any] struct { diff --git a/api/image.go b/api/image.go index b326af0b..477f141f 100644 --- a/api/image.go +++ b/api/image.go @@ -62,7 +62,7 @@ func apiImageContentGet(w http.ResponseWriter, r *http.Request, u platform.User) log.Error().Err(err).Msg("Failed to parse image UUID") http.Error(w, "Failed to parse image UUID", http.StatusBadRequest) } - file.PublicImageFileToResponse(w, imageUUID) + file.ImageFileToWriter(file.CollectionPublicImage, imageUUID, w) w.WriteHeader(http.StatusOK) } func apiImageContentPost(w http.ResponseWriter, r *http.Request, u platform.User) { @@ -73,7 +73,7 @@ func apiImageContentPost(w http.ResponseWriter, r *http.Request, u platform.User log.Error().Err(err).Msg("Failed to parse image UUID") http.Error(w, "Failed to parse image UUID", http.StatusBadRequest) } - err = file.ImageFileContentWrite(imageUUID, r.Body) + err = file.ImageFileFromReader(file.CollectionImageRaw, imageUUID, r.Body) if err != nil { renderShim(w, r, errRender(err)) return diff --git a/api/routes.go b/api/routes.go index 366e58ea..8d8c48ae 100644 --- a/api/routes.go +++ b/api/routes.go @@ -16,7 +16,9 @@ func AddRoutes(r *mux.Router) { // Authenticated endpoints r.Handle("/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost)).Methods("POST") r.Handle("/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost)).Methods("POST") - r.Handle("/avatar", authenticatedHandlerPostMultipart(avatarPost, file.CollectionAvatar)).Methods("POST") + avatar := resource.Avatar(router) + r.Handle("/avatar/{uuid}", authenticatedHandlerGetImage(avatar.ByIDGet)).Methods("GET").Name("avatar.ByIDGet") + r.Handle("/avatar", authenticatedHandlerPostMultipart(avatar.Create, file.CollectionAvatar)).Methods("POST") r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET") communication := resource.Communication(r) r.Handle("/communication", authenticatedHandlerJSON(communication.List)).Methods("GET") diff --git a/platform/file/image.go b/platform/file/image.go index 0760e1de..1f6d3e58 100644 --- a/platform/file/image.go +++ b/platform/file/image.go @@ -10,8 +10,8 @@ import ( "github.com/rs/zerolog/log" ) -func ImageFileContentWrite(uid uuid.UUID, body io.Reader) error { - filepath := fileContentPath(CollectionImageRaw, uid) +func ImageFileFromReader(collection Collection, uid uuid.UUID, body io.Reader) error { + filepath := fileContentPath(collection, uid) // Create file in configured directory dst, err := os.Create(filepath) @@ -25,36 +25,10 @@ func ImageFileContentWrite(uid uuid.UUID, body io.Reader) error { if err != nil { return fmt.Errorf("Unable to save file %s: %w", filepath, err) } + log.Info().Str("filepath", filepath).Int("collection", int(collection)).Msg("Saved image file content to collection") return nil } -func ImageFileContentWriteLogo(w http.ResponseWriter, uid uuid.UUID) { - //image_path := imageFileContentPathLogoPng(uid.String()) - image_path := fileContentPath(CollectionLogo, uid) - writeFileContent(w, image_path) -} - -func PublicImageFileContentWrite(uid uuid.UUID, body io.Reader) error { - // Create file in configured directory - //filepath := PublicImageFileContentPathRaw(uid.String()) - filepath := fileContentPath(CollectionPublicImage, uid) - dst, err := os.Create(filepath) - if err != nil { - log.Error().Err(err).Str("filepath", filepath).Msg("Failed to create public image file") - return fmt.Errorf("Failed to create public image file at %s: %v", filepath, err) - } - defer dst.Close() - - // Copy rest of request body to file - _, err = io.Copy(dst, body) - if err != nil { - return fmt.Errorf("Unable to save file to create audio file at %s: %v", filepath, err) - } - log.Info().Str("filepath", filepath).Msg("Saved public report image file content") - return nil -} - -func PublicImageFileToResponse(w http.ResponseWriter, uid uuid.UUID) { - //image_path := PublicImageFileContentPathRaw(uid) - image_path := fileContentPath(CollectionPublicImage, uid) +func ImageFileToWriter(collection Collection, uid uuid.UUID, w http.ResponseWriter) { + image_path := fileContentPath(collection, uid) writeFileContent(w, image_path) } diff --git a/resource/avatar.go b/resource/avatar.go new file mode 100644 index 00000000..2fcefd6d --- /dev/null +++ b/resource/avatar.go @@ -0,0 +1,47 @@ +package resource + +import ( + "context" + "fmt" + "net/http" + + nhttp "github.com/Gleipnir-Technology/nidus-sync/http" + "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/platform/file" + "github.com/google/uuid" + "github.com/gorilla/mux" +) + +func Avatar(r *router) *avatarR { + return &avatarR{ + router: r, + } +} + +type avatarR struct { + router *router +} + +func (res *avatarR) ByIDGet(ctx context.Context, r *http.Request, u platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus) { + vars := mux.Vars(r) + uid_str := vars["uuid"] + uid, err := uuid.Parse(uid_str) + if err != nil { + return file.CollectionAvatar, uuid.UUID{}, nhttp.NewErrorStatus(http.StatusBadRequest, "parse uuid: %w", err) + } + return file.CollectionAvatar, uid, nil +} +func (res *avatarR) Create(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) { + if len(uploads) == 0 { + return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found") + } + if len(uploads) != 1 { + return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time") + } + upload := uploads[0] + err := platform.AvatarCreate(r.Context(), u, upload) + if err != nil { + return "", nhttp.NewErrorStatus(http.StatusBadRequest, "Create avatar: %w", err) + } + return fmt.Sprintf("/avatar/%s", upload.UUID.String()), nil +} diff --git a/resource/router.go b/resource/router.go new file mode 100644 index 00000000..83368a7f --- /dev/null +++ b/resource/router.go @@ -0,0 +1,47 @@ +package resource + +import ( + "fmt" + "strconv" + + "github.com/google/uuid" + "github.com/gorilla/mux" +) + +type router struct { + router *mux.Router +} + +func NewRouter(r *mux.Router) *router { + return &router{ + router: r, + } +} +func (r *router) IDToURI(route string, id int) (string, error) { + i := strconv.FormatInt(int64(id), 10) + handler := r.router.Get(route) + if handler == nil { + return "", fmt.Errorf("nil handler '%s'", route) + } + uri, err := handler.URL("id", i) + if err != nil { + return "", fmt.Errorf("build uri: %w", err) + } + return uri.String(), nil +} + +func (r *router) UUIDToURI(route string, u *uuid.UUID) (*string, error) { + if u == nil { + return nil, nil + } + handler := r.router.Get(route) + if handler == nil { + return nil, fmt.Errorf("nil handler '%s'", route) + } + uri, err := handler.URL("uuid", u.String()) + if err != nil { + return nil, fmt.Errorf("build uri: %w", err) + } + result := uri.String() + return &result, nil +} diff --git a/rmo/image-upload.go b/rmo/image-upload.go index 6232bb54..ef45e356 100644 --- a/rmo/image-upload.go +++ b/rmo/image-upload.go @@ -39,7 +39,7 @@ func extractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpl if err != nil { return upload, fmt.Errorf("Failed to create quick report photo uuid", err) } - err = file.PublicImageFileContentWrite(u, bytes.NewReader(file_bytes)) + err = file.ImageFileFromReader(file.CollectionPublicImage, u, bytes.NewReader(file_bytes)) if err != nil { return upload, fmt.Errorf("Failed to write image file to disk: %w", err) } diff --git a/rmo/image.go b/rmo/image.go index faa64a04..0566298d 100644 --- a/rmo/image.go +++ b/rmo/image.go @@ -21,5 +21,5 @@ func getImageByUUID(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to parse uuid", http.StatusBadRequest) return } - file.PublicImageFileToResponse(w, uid) + file.ImageFileToWriter(file.CollectionPublicImage, uid, w) }