diff --git a/api/avatar.go b/api/avatar.go new file mode 100644 index 00000000..8c3b1965 --- /dev/null +++ b/api/avatar.go @@ -0,0 +1,26 @@ +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/routes.go b/api/routes.go index 881eb568..269adb52 100644 --- a/api/routes.go +++ b/api/routes.go @@ -16,6 +16,7 @@ func AddRoutes(r chi.Router) { // Authenticated endpoints r.Method("POST", "/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost)) r.Method("POST", "/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost)) + r.Method("POST", "/avatar", authenticatedHandlerPostMultipart(avatarPost, file.CollectionAvatar)) r.Method("GET", "/client/ios", auth.NewEnsureAuth(handleClientIos)) r.Method("GET", "/communication", authenticatedHandlerJSON(listCommunication)) r.Method("POST", "/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)) diff --git a/api/user.go b/api/user.go index 22da9056..91c01593 100644 --- a/api/user.go +++ b/api/user.go @@ -11,6 +11,7 @@ import ( ) type contentURLAPI struct { + Avatar string `json:"avatar"` Communication string `json:"communication"` PublicreportMessage string `json:"publicreport_message"` ReviewTask string `json:"review_task"` @@ -44,6 +45,7 @@ func getUserSelf(ctx context.Context, r *http.Request, user platform.User, query Self: user, URLs: contentURLs{ API: contentURLAPI{ + Avatar: config.MakeURLNidus("/api/avatar"), Communication: urls.API.Communication, PublicreportMessage: urls.API.Publicreport.Message, ReviewTask: config.MakeURLNidus("/api/review-task"), diff --git a/platform/avatar.go b/platform/avatar.go new file mode 100644 index 00000000..ba83f138 --- /dev/null +++ b/platform/avatar.go @@ -0,0 +1,40 @@ +package platform + +import ( + "bytes" + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/platform/file" + "github.com/disintegration/imaging" + "github.com/rs/zerolog/log" +) + +func AvatarCreate(ctx context.Context, u User, upload file.Upload) error { + f, err := file.NewFileReader(file.CollectionAvatar, upload.UUID) + // Decode image (supports PNG, JPG, GIF) + img, err := imaging.Decode(f) + if err != nil { + return fmt.Errorf("decode: %w", err) + } + + // Resize to 200x200, maintaining aspect ratio + avatar := imaging.Fill(img, 200, 200, imaging.Center, imaging.Lanczos) + + // Save or encode + //filename := fmt.Sprintf("avatar-%s.jpg", upload.UUID.String()) + //err = imaging.Save(avatar, filename) + //log.Info().Str("filename", filename).Msg("wrote avatar file") + // Or encode to buffer: imaging.Encode(writer, avatar, imaging.JPEG) + writer := &bytes.Buffer{} + err = imaging.Encode(writer, avatar, imaging.PNG) + if err != nil { + return fmt.Errorf("encode: %w", err) + } + err = file.FileContentWrite(writer, file.CollectionAvatar, upload.UUID) + if err != nil { + return fmt.Errorf("write content: %w", err) + } + log.Info().Str("uuid", upload.UUID.String()).Msg("wrote avatar file") + return nil +} diff --git a/platform/file/base.go b/platform/file/base.go index 90140b8b..1b94cebf 100644 --- a/platform/file/base.go +++ b/platform/file/base.go @@ -19,6 +19,7 @@ var collectionToExtension map[Collection]string = map[Collection]string{ CollectionAudioNormalized: "ogg", CollectionAudioRaw: "raw", CollectionAudioTranscoded: "ogg", + CollectionAvatar: "png", CollectionCSV: "csv", CollectionLogo: "png", CollectionPublicImage: "img", @@ -28,6 +29,7 @@ var collectionToSubdir map[Collection]string = map[Collection]string{ CollectionAudioNormalized: "audio-normalized", CollectionAudioRaw: "audio-raw", CollectionAudioTranscoded: "audio-transcoded", + CollectionAvatar: "avatar", CollectionCSV: "csv", CollectionLogo: "logo", CollectionPublicImage: "public-image", diff --git a/platform/file/enum.go b/platform/file/enum.go index e7d16c60..1bdf19d9 100644 --- a/platform/file/enum.go +++ b/platform/file/enum.go @@ -6,6 +6,7 @@ const ( CollectionAudioRaw Collection = iota CollectionAudioNormalized CollectionAudioTranscoded + CollectionAvatar CollectionCSV CollectionImageRaw CollectionLogo diff --git a/ts/view/configuration/UserEdit.vue b/ts/view/configuration/UserEdit.vue index 6836a038..f4f91ce8 100644 --- a/ts/view/configuration/UserEdit.vue +++ b/ts/view/configuration/UserEdit.vue @@ -40,7 +40,7 @@ pre {