Add authenticated content editing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
fac7588d23
commit
fd2877da9e
15 changed files with 581 additions and 37 deletions
|
|
@ -72,6 +72,22 @@ func (s *AuthService) AdminLogin(ctx context.Context, email string) (domain.Admi
|
|||
func (s *AuthService) AdminAuthenticate(ctx context.Context, t string) (domain.Session, error) {
|
||||
return s.adminSessions.FindValid(ctx, t)
|
||||
}
|
||||
func (s *AuthService) UserByID(ctx context.Context, id uint) (domain.User, error) {
|
||||
if lookup, ok := s.users.(repository.IUserLookup); ok {
|
||||
return lookup.FindByID(ctx, id)
|
||||
}
|
||||
return domain.User{}, errors.New("user lookup unavailable")
|
||||
}
|
||||
func (s *AuthService) CanEdit(ctx context.Context, session domain.Session) bool {
|
||||
if session.AdminID != nil {
|
||||
return true
|
||||
}
|
||||
if session.UserID == nil {
|
||||
return false
|
||||
}
|
||||
u, err := s.UserByID(ctx, *session.UserID)
|
||||
return err == nil && (u.Editor || u.Role == "editor" || u.Role == "admin")
|
||||
}
|
||||
func newToken() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, e := rand.Read(b); e != nil {
|
||||
|
|
|
|||
73
internal/service/content.go
Normal file
73
internal/service/content.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/example/cms/internal/domain"
|
||||
"github.com/example/cms/internal/repository"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IContentService interface {
|
||||
List(context.Context, int, int) ([]domain.Content, error)
|
||||
Get(context.Context, string) (domain.Content, error)
|
||||
Update(context.Context, domain.Content, domain.Session) (domain.Content, error)
|
||||
}
|
||||
type ContentService struct {
|
||||
repo repository.IContentRepository
|
||||
auth *AuthService
|
||||
}
|
||||
|
||||
func NewContentService(r repository.IContentRepository, a *AuthService) *ContentService {
|
||||
return &ContentService{repo: r, auth: a}
|
||||
}
|
||||
func (s *ContentService) List(ctx context.Context, limit, offset int) ([]domain.Content, error) {
|
||||
if limit < 1 {
|
||||
limit = 20
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
return s.repo.List(ctx, limit, offset)
|
||||
}
|
||||
func (s *ContentService) Get(ctx context.Context, slug string) (domain.Content, error) {
|
||||
if !validSlug(slug) {
|
||||
return domain.Content{}, errors.New("invalid slug")
|
||||
}
|
||||
return s.repo.FindBySlug(ctx, slug)
|
||||
}
|
||||
func (s *ContentService) Update(ctx context.Context, c domain.Content, session domain.Session) (domain.Content, error) {
|
||||
if s.auth == nil || !s.auth.CanEdit(ctx, session) {
|
||||
return domain.Content{}, errors.New("forbidden")
|
||||
}
|
||||
if !validSlug(c.Slug) || strings.TrimSpace(c.Title) == "" || strings.TrimSpace(c.Body) == "" {
|
||||
return domain.Content{}, errors.New("invalid content")
|
||||
}
|
||||
old, err := s.repo.FindBySlug(ctx, c.Slug)
|
||||
if err != nil {
|
||||
return domain.Content{}, err
|
||||
}
|
||||
c.ID = old.ID
|
||||
c.CreatedAt = old.CreatedAt
|
||||
c.CreatedBy = old.CreatedBy
|
||||
c.UpdatedAt = time.Now()
|
||||
if session.UserID != nil {
|
||||
c.UpdatedBy = *session.UserID
|
||||
}
|
||||
snapshot, _ := json.Marshal(c)
|
||||
h := domain.ContentHistory{ContentID: c.ID, Operation: "updated", CreatedAt: c.UpdatedAt, Snapshot: string(snapshot), ActorID: session.UserID, ActorAdminID: session.AdminID}
|
||||
if err = s.repo.Update(ctx, c, h); err != nil {
|
||||
return domain.Content{}, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var slugRE = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
|
||||
|
||||
func validSlug(s string) bool { return len(s) <= 200 && slugRE.MatchString(s) }
|
||||
Loading…
Add table
Add a link
Reference in a new issue