refac: stale test entries, failing web tests, Postgre backup bug, Hardcoded MySQL table count, Stale Postgre connection in test, own-scope visibility filter, GetAll ignoring its error, JSON tags in use-case types, 31 copies of DESKTOP_QUERY, ProfileView endpoint paths

This commit is contained in:
Florian Sylvain committed 2026-10-28 17:54:10 +01:00
1 parent efecdee200
commit 5d6f378fce
87 files changed
+696 -373

No files matched your search

@@ -72,7 +72,11 @@ func (a *AcceptInviteUseCase) recordRefusal(ctx context.Context, event audit.Eve
}
func (a *AcceptInviteUseCase) accept(command AcceptInviteCommand, pending invite.Invite, assigned role.Role, now time.Time) (user.User, error) {
if a.emailHasAccount(pending.Email) {
hasAccount, err := a.emailHasAccount(pending.Email)
if err != nil {
return user.User{}, err
}
if hasAccount {
if _, err := findUsableInvite(a.inviteRepository, a.hasher, command.Code, now); err != nil {
return user.User{}, err
}
@@ -122,13 +126,17 @@ func (a *AcceptInviteUseCase) inviterOf(pending invite.Invite) (uint32, error) {
return assigner.ID, nil
}
func (a *AcceptInviteUseCase) emailHasAccount(email string) bool {
for _, existingUser := range a.userRepository.GetAll() {
func (a *AcceptInviteUseCase) emailHasAccount(email string) (bool, error) {
users, err := a.userRepository.GetAll()
if err != nil {
return false, err
}
for _, existingUser := range users {
if strings.EqualFold(existingUser.Email, email) {
return true
return true, nil
}
}
return false
return false, nil
}
func (a *AcceptInviteUseCase) ensureUsernameFree(username string) error {
@@ -98,7 +98,11 @@ func (g *CreateInviteUseCase) create(command CreateInviteCommand) (invite.Invite
return invite.Invite{}, role.Role{}, err
}
if g.emailHasAccount(email) {
hasAccount, err := g.emailHasAccount(email)
if err != nil {
return invite.Invite{}, role.Role{}, err
}
if hasAccount {
return invite.Invite{}, role.Role{}, invite.ErrEmailHasAccount
}
@@ -133,17 +137,25 @@ func (g *CreateInviteUseCase) create(command CreateInviteCommand) (invite.Invite
return createdInvite, assigned, nil
}
func (g *CreateInviteUseCase) emailHasAccount(email string) bool {
for _, existingUser := range g.userRepository.GetAll() {
func (g *CreateInviteUseCase) emailHasAccount(email string) (bool, error) {
users, err := g.userRepository.GetAll()
if err != nil {
return false, err
}
for _, existingUser := range users {
if strings.EqualFold(existingUser.Email, email) {
return true
return true, nil
}
}
return false
return false, nil
}
func (g *CreateInviteUseCase) discardOpenInvite(email string, now time.Time) error {
for _, existing := range g.inviteRepository.GetAll() {
invites, err := g.inviteRepository.GetAll()
if err != nil {
return err
}
for _, existing := range invites {
if existing.Email != email {
continue
}
+16 -7
View File
@@ -53,7 +53,11 @@ func (g *CreateUserUseCase) CreateUser(ctx context.Context, createUser CreateUse
}
func (g *CreateUserUseCase) create(createUser CreateUserCommand) (user.User, error) {
if g.registrationClosed() {
closed, err := g.registrationClosed()
if err != nil {
return user.User{}, err
}
if closed {
return user.User{}, user.ErrRegistrationClosed
}
@@ -108,15 +112,20 @@ func (g *CreateUserUseCase) create(createUser CreateUserCommand) (user.User, err
return createdUser, nil
}
func (g *CreateUserUseCase) IsRegistrationOpen() bool {
return !g.registrationClosed()
func (g *CreateUserUseCase) IsRegistrationOpen() (bool, error) {
closed, err := g.registrationClosed()
return !closed, err
}
func (g *CreateUserUseCase) registrationClosed() bool {
for _, existingUser := range g.userRepository.GetAll() {
func (g *CreateUserUseCase) registrationClosed() (bool, error) {
users, err := g.userRepository.GetAll()
if err != nil {
return false, err
}
for _, existingUser := range users {
if existingUser.IsVerified {
return true
return true, nil
}
}
return false
return false, nil
}
@@ -34,7 +34,11 @@ func (e *EnforceRetentionUseCase) Tick(now time.Time) (retention.Result, error)
func (e *EnforceRetentionUseCase) deleteStaleUnverifiedUsers(now time.Time) (int, error) {
removed := 0
for _, candidate := range e.users.GetAll() {
users, err := e.users.GetAll()
if err != nil {
return 0, err
}
for _, candidate := range users {
if !candidate.IsStaleUnverifiedAt(now) {
continue
}
@@ -48,7 +52,11 @@ func (e *EnforceRetentionUseCase) deleteStaleUnverifiedUsers(now time.Time) (int
func (e *EnforceRetentionUseCase) deleteStaleInvites(now time.Time) (int, error) {
removed := 0
for _, candidate := range e.invites.GetAll() {
invites, err := e.invites.GetAll()
if err != nil {
return 0, err
}
for _, candidate := range invites {
if !candidate.IsStaleAt(now) {
continue
}
@@ -24,7 +24,11 @@ func findUsableInvite(inviteRepository invite.Repository, hasher hash.Hasher, co
return invite.Invite{}, invite.ErrInviteInvalid
}
for _, candidate := range inviteRepository.GetAll() {
invites, err := inviteRepository.GetAll()
if err != nil {
return invite.Invite{}, err
}
for _, candidate := range invites {
if candidate.AcceptedAt != nil {
continue
}
@@ -42,20 +42,42 @@ type ArticlePage struct {
func (u *ListArticlesUseCase) ListArticlesPage(command ListArticlesCommand) (ArticlePage, error) {
switch command.Caller.Grants.ScopeFor(role.KeyContentRead, builtintype.KeyArticle) {
case role.ScopeAll:
return u.listVisibleByRepository(command, false)
return u.listVisibleByRepository(command, articleVisibility{})
case role.ScopeOwn:
return u.listOwnedAndOnline(command)
return u.listVisibleByRepository(command, articleVisibility{userID: &command.Caller.UserID})
default:
return u.listVisibleByRepository(command, true)
return u.listVisibleByRepository(command, articleVisibility{onlineOnly: true})
}
}
func (u *ListArticlesUseCase) listVisibleByRepository(command ListArticlesCommand, onlineOnly bool) (ArticlePage, error) {
counts, err := u.articleRepository.CountByVisibility(onlineOnly)
type articleVisibility struct {
onlineOnly bool
userID *uint32
}
func (v articleVisibility) apply(filters article.Filters) article.Filters {
if v.onlineOnly {
online := true
filters.IsOnline = &online
}
filters.VisibleTo = v.userID
return filters
}
func (u *ListArticlesUseCase) countVisible(visibility articleVisibility) (article.Counts, error) {
if visibility.userID != nil {
return u.articleRepository.CountVisibleTo(*visibility.userID)
}
return u.articleRepository.CountByVisibility(visibility.onlineOnly)
}
func (u *ListArticlesUseCase) listVisibleByRepository(command ListArticlesCommand, visibility articleVisibility) (ArticlePage, error) {
onlineOnly := visibility.onlineOnly
counts, err := u.countVisible(visibility)
if err != nil {
return ArticlePage{}, err
}
summaries, err := u.summarizeVisible(onlineOnly)
summaries, err := u.summarizeVisible(visibility)
if err != nil {
return ArticlePage{}, err
}
@@ -64,7 +86,7 @@ func (u *ListArticlesUseCase) listVisibleByRepository(command ListArticlesComman
if onlineOnly && command.Status == article.StatusDraft {
return page, nil
}
filters := pageFilters(command, onlineOnly)
filters := visibility.apply(pageFilters(command))
if command.SiteKey != nil {
siteID, known, err := u.siteIDOf(*command.SiteKey)
if err != nil {
@@ -98,71 +120,6 @@ func emptyArticlePage(command ListArticlesCommand, counts article.Counts) Articl
return ArticlePage{Items: []article.Article{}, Page: command.Page, Size: command.Size, Counts: counts, SEO: map[uint32]article.SEOSummary{}}
}
func (u *ListArticlesUseCase) listOwnedAndOnline(command ListArticlesCommand) (ArticlePage, error) {
everything, err := u.articleRepository.FindPage(article.Filters{Unpaged: true})
if err != nil {
return ArticlePage{}, err
}
visible := visibleTo(command.Caller, everything.Items)
summaries, err := u.summarize(visible)
if err != nil {
return ArticlePage{}, err
}
counts := tally(visible)
counts.Suggestions = countSuggestions(summaries)
page := emptyArticlePage(command, counts)
filters := pageFilters(command, false)
filters.Unpaged = true
if command.SiteKey != nil {
siteID, known, err := u.siteIDOf(*command.SiteKey)
if err != nil {
return ArticlePage{}, err
}
if !known {
return page, nil
}
filters.SiteID = &siteID
}
found, err := u.articleRepository.FindPage(filters)
if err != nil {
return ArticlePage{}, err
}
matching := visibleTo(command.Caller, found.Items)
if command.SEO != "" {
matching = withSEOStatus(matching, summaries, command.SEO)
}
rendered, err := renderArticles(u.bodyRenderer, pageSlice(matching, command.Page, command.Size), command.Format)
if err != nil {
return ArticlePage{}, err
}
page.Items = rendered
page.Total = len(matching)
page.SEO = summaries
return page, nil
}
func visibleTo(caller ContentCaller, items []article.Article) []article.Article {
visible := make([]article.Article, 0, len(items))
for _, item := range items {
if caller.seesArticle(item) {
visible = append(visible, item)
}
}
return visible
}
func tally(visible []article.Article) article.Counts {
counts := article.Counts{All: len(visible)}
for _, item := range visible {
if item.IsOnline {
counts.Online++
} else {
counts.Draft++
}
}
return counts
}
func withSEOStatus(items []article.Article, summaries map[uint32]article.SEOSummary, status string) []article.Article {
matching := make([]article.Article, 0, len(items))
for _, item := range items {
@@ -187,13 +144,8 @@ func pageSlice[T any](items []T, page int, size int) []T {
return items[start:min(start+size, len(items))]
}
func (u *ListArticlesUseCase) summarizeVisible(onlineOnly bool) (map[uint32]article.SEOSummary, error) {
filters := article.Filters{Unpaged: true}
if onlineOnly {
online := true
filters.IsOnline = &online
}
found, err := u.articleRepository.FindPage(filters)
func (u *ListArticlesUseCase) summarizeVisible(visibility articleVisibility) (map[uint32]article.SEOSummary, error) {
found, err := u.articleRepository.FindPage(visibility.apply(article.Filters{Unpaged: true}))
if err != nil {
return nil, err
}
@@ -245,11 +197,6 @@ func (u *ListArticlesUseCase) siteIDOf(key string) (uint32, bool, error) {
return found.ID, true, nil
}
func pageFilters(command ListArticlesCommand, onlineOnly bool) article.Filters {
filters := article.Filters{Page: command.Page, Size: command.Size, Q: command.Q, Status: command.Status, Sort: command.Sort}
if onlineOnly {
online := true
filters.IsOnline = &online
}
return filters
func pageFilters(command ListArticlesCommand) article.Filters {
return article.Filters{Page: command.Page, Size: command.Size, Q: command.Q, Status: command.Status, Sort: command.Sort}
}
@@ -70,7 +70,14 @@ func (u *ListEntriesUseCase) page(owner contenttype.ContentType, command ListEnt
if query == "" && readScope == role.ScopeAll {
return u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Page: command.Page, Size: command.Size, Sort: command.Sort})
}
all, err := u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Unpaged: true, Sort: command.Sort})
if query == "" && readScope == role.ScopeOwn {
return u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, CreatedBy: &command.Caller.UserID, Page: command.Page, Size: command.Size, Sort: command.Sort})
}
filters := entry.Filters{ContentTypeID: owner.ID, Unpaged: true, Sort: command.Sort}
if readScope == role.ScopeOwn {
filters.CreatedBy = &command.Caller.UserID
}
all, err := u.entries.ListPage(filters)
if err != nil {
return entry.PageResult{}, err
}
@@ -10,6 +10,6 @@ func NewListUsersUseCase(userRepository user.Repository) *ListUsersUseCase {
return &ListUsersUseCase{userRepository}
}
func (g *ListUsersUseCase) ListUsers() []user.User {
func (g *ListUsersUseCase) ListUsers() ([]user.User, error) {
return g.userRepository.GetAll()
}
@@ -21,8 +21,8 @@ type ListUsersAndInvitesUseCase struct {
}
type RoleAssigner struct {
ID uint32 `json:"id"`
Name string `json:"name"`
ID uint32
Name string
}
func newRoleAssigner(account user.User) *RoleAssigner {
@@ -89,7 +89,10 @@ func (g *ListUsersAndInvitesUseCase) ListUsersAndInvites() ([]DirectoryEntry, er
now := time.Now()
entries := []DirectoryEntry{}
users := g.userRepository.GetAll()
users, err := g.userRepository.GetAll()
if err != nil {
return nil, err
}
loaded := make(map[uint32]user.User, len(users))
for _, existingUser := range users {
loaded[existingUser.ID] = existingUser
@@ -98,7 +101,11 @@ func (g *ListUsersAndInvitesUseCase) ListUsersAndInvites() ([]DirectoryEntry, er
entries = append(entries, userEntry(existingUser, lookupRoleRef(refs, existingUser.RoleID), assignerAmong(loaded, existingUser.RoleAssignedByID)))
}
for _, existingInvite := range g.inviteRepository.GetAll() {
invites, err := g.inviteRepository.GetAll()
if err != nil {
return nil, err
}
for _, existingInvite := range invites {
status := existingInvite.StatusAt(now)
if status == invite.StatusAccepted {
continue
@@ -21,19 +21,19 @@ type TransferOwnershipCommand struct {
}
type OwnershipSuccessor struct {
ID uint32 `json:"id"`
Name string `json:"name"`
ID uint32
Name string
}
type OwnershipPredecessor struct {
ID uint32 `json:"id"`
Name string `json:"name"`
Role RoleRef `json:"role"`
ID uint32
Name string
Role RoleRef
}
type OwnershipTransfer struct {
Owner OwnershipSuccessor `json:"owner"`
FormerOwner OwnershipPredecessor `json:"former_owner"`
Owner OwnershipSuccessor
FormerOwner OwnershipPredecessor
}
func NewTransferOwnershipUseCase(users user.Repository, roles role.Repository, recorder audit.Recorder) *TransferOwnershipUseCase {
@@ -3,7 +3,11 @@ package useCases
import "RenewCMS/internal/domain/invite"
func findManageableInvite(inviteRepository invite.Repository, id uint32) (invite.Invite, error) {
for _, existing := range inviteRepository.GetAll() {
invites, err := inviteRepository.GetAll()
if err != nil {
return invite.Invite{}, err
}
for _, existing := range invites {
if existing.ID != id {
continue
}
+4 -4
View File
@@ -11,10 +11,10 @@ type InviteView struct {
}
type RoleRef struct {
ID uint32 `json:"id"`
Name string `json:"name"`
BuiltIn bool `json:"built_in"`
Locked bool `json:"locked"`
ID uint32
Name string
BuiltIn bool
Locked bool
}
func NewRoleRef(found role.Role) RoleRef {
+5 -5
View File
@@ -9,15 +9,15 @@ import (
)
type RoleEditor struct {
ID uint32 `json:"id"`
Name string `json:"name"`
ID uint32
Name string
}
type RoleView struct {
role.Role
Members int `json:"members"`
UpdatedAt *time.Time `json:"updated_at"`
UpdatedBy *RoleEditor `json:"updated_by"`
Members int
UpdatedAt *time.Time
UpdatedBy *RoleEditor
}
type roleViewer struct {