Merge branch 'main' into fix/sse

This commit is contained in:
juancwu 2026-02-08 00:22:58 +00:00
commit 9044a64b10
17 changed files with 1118 additions and 35 deletions

View file

@ -20,6 +20,16 @@ type CreateExpenseDTO struct {
ItemIDs []string
}
type UpdateExpenseDTO struct {
ID string
SpaceID string
Description string
Amount int
Type model.ExpenseType
Date time.Time
TagIDs []string
}
type ExpenseService struct {
expenseRepo repository.ExpenseRepository
}
@ -84,3 +94,83 @@ func (s *ExpenseService) GetBalanceForSpace(spaceID string) (int, error) {
func (s *ExpenseService) GetExpensesByTag(spaceID string, fromDate, toDate time.Time) ([]*model.TagExpenseSummary, error) {
return s.expenseRepo.GetExpensesByTag(spaceID, fromDate, toDate)
}
func (s *ExpenseService) GetExpensesWithTagsForSpace(spaceID string) ([]*model.ExpenseWithTags, error) {
expenses, err := s.expenseRepo.GetBySpaceID(spaceID)
if err != nil {
return nil, err
}
ids := make([]string, len(expenses))
for i, e := range expenses {
ids[i] = e.ID
}
tagsMap, err := s.expenseRepo.GetTagsByExpenseIDs(ids)
if err != nil {
return nil, err
}
result := make([]*model.ExpenseWithTags, len(expenses))
for i, e := range expenses {
result[i] = &model.ExpenseWithTags{
Expense: *e,
Tags: tagsMap[e.ID],
}
}
return result, nil
}
func (s *ExpenseService) GetExpense(id string) (*model.Expense, error) {
return s.expenseRepo.GetByID(id)
}
func (s *ExpenseService) GetTagsByExpenseIDs(expenseIDs []string) (map[string][]*model.Tag, error) {
return s.expenseRepo.GetTagsByExpenseIDs(expenseIDs)
}
func (s *ExpenseService) UpdateExpense(dto UpdateExpenseDTO) (*model.Expense, error) {
if dto.Description == "" {
return nil, fmt.Errorf("expense description cannot be empty")
}
if dto.Amount <= 0 {
return nil, fmt.Errorf("amount must be positive")
}
existing, err := s.expenseRepo.GetByID(dto.ID)
if err != nil {
return nil, err
}
existing.Description = dto.Description
existing.AmountCents = dto.Amount
existing.Type = dto.Type
existing.Date = dto.Date
existing.UpdatedAt = time.Now()
if err := s.expenseRepo.Update(existing, dto.TagIDs); err != nil {
return nil, err
}
balance, _ := s.GetBalanceForSpace(dto.SpaceID)
s.eventBus.Publish(dto.SpaceID, "balance_changed", map[string]interface{}{
"balance": balance,
})
s.eventBus.Publish(dto.SpaceID, "expenses_updated", nil)
return existing, nil
}
func (s *ExpenseService) DeleteExpense(id string, spaceID string) error {
if err := s.expenseRepo.Delete(id); err != nil {
return err
}
balance, _ := s.GetBalanceForSpace(spaceID)
s.eventBus.Publish(spaceID, "balance_changed", map[string]interface{}{
"balance": balance,
})
s.eventBus.Publish(spaceID, "expenses_updated", nil)
return nil
}

View file

@ -94,6 +94,19 @@ func (s *InviteService) AcceptInvite(token, userID string) (string, error) {
return invite.SpaceID, s.inviteRepo.UpdateStatus(token, model.InvitationStatusAccepted)
}
func (s *InviteService) CancelInvite(token string) error {
invite, err := s.inviteRepo.GetByToken(token)
if err != nil {
return err
}
if invite.Status != model.InvitationStatusPending {
return errors.New("invitation is not pending")
}
return s.inviteRepo.Delete(token)
}
func (s *InviteService) GetPendingInvites(spaceID string) ([]*model.SpaceInvitation, error) {
// Filter for pending only in memory or repo?
// Repo returns all.

View file

@ -88,3 +88,25 @@ func (s *SpaceService) IsMember(userID, spaceID string) (bool, error) {
}
return isMember, nil
}
// GetMembers returns all members of a space with their profile info.
func (s *SpaceService) GetMembers(spaceID string) ([]*model.SpaceMemberWithProfile, error) {
members, err := s.spaceRepo.GetMembers(spaceID)
if err != nil {
return nil, fmt.Errorf("failed to get members: %w", err)
}
return members, nil
}
// RemoveMember removes a member from a space.
func (s *SpaceService) RemoveMember(spaceID, userID string) error {
return s.spaceRepo.RemoveMember(spaceID, userID)
}
// UpdateSpaceName updates the name of a space.
func (s *SpaceService) UpdateSpaceName(spaceID, name string) error {
if name == "" {
return fmt.Errorf("space name cannot be empty")
}
return s.spaceRepo.UpdateName(spaceID, name)
}