weiro/services/imgedit/service.go

154 lines
3.4 KiB
Go
Raw Normal View History

2026-03-25 11:35:53 +00:00
package imgedit
import (
"context"
"encoding/json"
"io"
"time"
"lmika.dev/lmika/weiro/models"
"lmika.dev/lmika/weiro/services/uploads"
)
type Service struct {
scratchDir string
uploadService *uploads.Service
sessionStore *sessionStore
}
func New(
uploadService *uploads.Service,
scratchDir string,
) *Service {
return &Service{
scratchDir: scratchDir,
uploadService: uploadService,
sessionStore: &sessionStore{baseDir: scratchDir},
}
}
func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (*models.ImageEditSession, error) {
2026-03-25 11:35:53 +00:00
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return nil, err
2026-03-25 11:35:53 +00:00
}
upload, _, err := s.uploadService.OpenUpload(ctx, baseUploadID)
if err != nil {
return nil, err
2026-03-25 11:35:53 +00:00
}
var ext string
switch upload.MIMEType {
case "image/jpeg":
ext = "jpg"
case "image/png":
ext = "png"
default:
return nil, models.UnsupportedImageFormat
2026-03-25 11:35:53 +00:00
}
newSession := models.ImageEditSession{
GUID: models.NewNanoID(),
SiteID: site.ID,
UserID: user.ID,
BaseUploadID: baseUploadID,
ImageExt: ext,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
Processors: []models.ImageEditProcessor{
{
ID: models.NewNanoID(),
2026-03-25 11:35:53 +00:00
Type: "copy-upload",
Props: mustToJSON(models.CopyUploadProps{UploadID: baseUploadID}),
},
},
}
newSession.RecalcVersionIDs()
if err := s.sessionStore.save(&newSession); err != nil {
return nil, err
2026-03-25 11:35:53 +00:00
}
if _, err := s.reprocess(ctx, &newSession); err != nil {
return nil, err
2026-03-25 11:35:53 +00:00
}
return &newSession, nil
}
func (s *Service) LoadImageVersion(ctx context.Context, sessionID string, versionID string) (mimeType string, rw func() (io.ReadCloser, error), err error) {
session, err := s.loadAndVerifySession(ctx, sessionID)
if err != nil {
return "", nil, err
}
return s.sessionStore.getImage(session, versionID+"."+session.ImageExt)
}
type AddProcessorReq struct {
Type string `json:"type"`
}
func (s *Service) AddProcessor(ctx context.Context, sessionID string, req AddProcessorReq) (*models.ImageEditSession, error) {
session, err := s.loadAndVerifySession(ctx, sessionID)
if err != nil {
return nil, err
}
// TODO: verify processor, etc.
session.Processors = append(session.Processors, models.ImageEditProcessor{
ID: models.NewNanoID(),
Type: req.Type,
})
session.RecalcVersionIDs()
if err := s.sessionStore.save(session); err != nil {
return nil, err
}
if _, err := s.reprocess(ctx, session); err != nil {
return nil, err
}
return session, nil
}
func (s *Service) loadAndVerifySession(ctx context.Context, sessionID string) (*models.ImageEditSession, error) {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return nil, err
}
session, err := s.sessionStore.get(sessionID)
if err != nil {
return nil, err
} else if session.SiteID != site.ID || session.UserID != user.ID {
return nil, models.PermissionError
}
return session, nil
2026-03-25 11:35:53 +00:00
}
func (s *Service) fetchSiteAndUser(ctx context.Context) (models.Site, models.User, error) {
user, ok := models.GetUser(ctx)
if !ok {
return models.Site{}, models.User{}, models.UserRequiredError
}
site, ok := models.GetSite(ctx)
if !ok {
return models.Site{}, models.User{}, models.SiteRequiredError
}
if site.OwnerID != user.ID {
return models.Site{}, models.User{}, models.PermissionError
}
return site, user, nil
}
func mustToJSON(a any) json.RawMessage {
b, _ := json.Marshal(a)
return b
}