Add avatar resource

This commit is contained in:
Eli Ribble 2026-04-01 21:23:28 +00:00
parent 0a7a2512d4
commit a89a4fbec5
No known key found for this signature in database
10 changed files with 129 additions and 62 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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

View file

@ -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")

View file

@ -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)
}

47
resource/avatar.go Normal file
View file

@ -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
}

47
resource/router.go Normal file
View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}