feat: shared with me page
This commit is contained in:
parent
071f6c5855
commit
48cae4957e
5 changed files with 96 additions and 8 deletions
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ctxkeys"
|
"git.juancwu.dev/juancwu/budgit/internal/ctxkeys"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/service"
|
"git.juancwu.dev/juancwu/budgit/internal/service"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui"
|
"git.juancwu.dev/juancwu/budgit/internal/ui"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/blocks"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/blocks"
|
||||||
|
|
@ -30,27 +31,48 @@ func (h *spaceHandler) SpacesPage(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
spaces, err := h.spaceService.GetSpacesForUser(user.ID)
|
spaces, err := h.spaceService.GetOwnedSpaces(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to load spaces", "error", err, "user_id", user.ID)
|
slog.Error("failed to load spaces", "error", err, "user_id", user.ID)
|
||||||
ui.RenderError(w, r, "Failed to load spaces", http.StatusInternalServerError)
|
ui.RenderError(w, r, "Failed to load spaces", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cards := h.buildSpaceCards(spaces)
|
||||||
|
ui.Render(w, r, pages.Spaces(cards))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *spaceHandler) SharedSpacesPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := ctxkeys.User(r.Context())
|
||||||
|
if user == nil {
|
||||||
|
ui.RenderError(w, r, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spaces, err := h.spaceService.GetSharedSpaces(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to load shared spaces", "error", err, "user_id", user.ID)
|
||||||
|
ui.RenderError(w, r, "Failed to load shared spaces", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cards := h.buildSpaceCards(spaces)
|
||||||
|
ui.Render(w, r, pages.SharedSpaces(cards))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *spaceHandler) buildSpaceCards(spaces []*model.Space) []blocks.SpaceCardInfo {
|
||||||
cards := make([]blocks.SpaceCardInfo, 0, len(spaces))
|
cards := make([]blocks.SpaceCardInfo, 0, len(spaces))
|
||||||
for _, sp := range spaces {
|
for _, sp := range spaces {
|
||||||
memberCount, err := h.spaceService.GetMemberCount(sp.ID)
|
memberCount, err := h.spaceService.GetMemberCount(sp.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to get space member count", "error", err, "space_id", sp.ID)
|
slog.Error("failed to get space member count", "error", err, "space_id", sp.ID)
|
||||||
memberCount = 0
|
memberCount = 0
|
||||||
err = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, err := h.accountService.GetAccountsForSpace(sp.ID)
|
accounts, err := h.accountService.GetAccountsForSpace(sp.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to get space accounts", "error", err, "space_id", sp.ID)
|
slog.Error("failed to get space accounts", "error", err, "space_id", sp.ID)
|
||||||
accounts = nil
|
accounts = nil
|
||||||
err = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalBalance := decimal.Zero
|
totalBalance := decimal.Zero
|
||||||
|
|
@ -65,8 +87,7 @@ func (h *spaceHandler) SpacesPage(w http.ResponseWriter, r *http.Request) {
|
||||||
TotalBalance: totalBalance,
|
TotalBalance: totalBalance,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return cards
|
||||||
ui.Render(w, r, pages.Spaces(cards))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *spaceHandler) CreateSpacePage(w http.ResponseWriter, r *http.Request) {
|
func (h *spaceHandler) CreateSpacePage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ type SpaceRepository interface {
|
||||||
Create(space *model.Space) error
|
Create(space *model.Space) error
|
||||||
ByID(id string) (*model.Space, error)
|
ByID(id string) (*model.Space, error)
|
||||||
ByUserID(userID string) ([]*model.Space, error)
|
ByUserID(userID string) ([]*model.Space, error)
|
||||||
|
ByOwnerID(userID string) ([]*model.Space, error)
|
||||||
|
SharedWithUser(userID string) ([]*model.Space, error)
|
||||||
AddMember(spaceID, userID string, role model.Role) error
|
AddMember(spaceID, userID string, role model.Role) error
|
||||||
RemoveMember(spaceID, userID string) error
|
RemoveMember(spaceID, userID string) error
|
||||||
IsMember(spaceID, userID string) (bool, error)
|
IsMember(spaceID, userID string) (bool, error)
|
||||||
|
|
@ -91,6 +93,37 @@ func (r *spaceRepository) ByUserID(userID string) ([]*model.Space, error) {
|
||||||
return spaces, nil
|
return spaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *spaceRepository) ByOwnerID(userID string) ([]*model.Space, error) {
|
||||||
|
var spaces []*model.Space
|
||||||
|
query := `
|
||||||
|
SELECT s.*
|
||||||
|
FROM spaces s
|
||||||
|
WHERE s.owner_id = $1
|
||||||
|
ORDER BY s.created_at DESC;
|
||||||
|
`
|
||||||
|
err := r.db.Select(&spaces, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *spaceRepository) SharedWithUser(userID string) ([]*model.Space, error) {
|
||||||
|
var spaces []*model.Space
|
||||||
|
query := `
|
||||||
|
SELECT s.*
|
||||||
|
FROM spaces s
|
||||||
|
JOIN space_members sm ON s.id = sm.space_id
|
||||||
|
WHERE sm.user_id = $1 AND s.owner_id != $1
|
||||||
|
ORDER BY s.created_at DESC;
|
||||||
|
`
|
||||||
|
err := r.db.Select(&spaces, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *spaceRepository) AddMember(spaceID, userID string, role model.Role) error {
|
func (r *spaceRepository) AddMember(spaceID, userID string, role model.Role) error {
|
||||||
query := `INSERT INTO space_members (space_id, user_id, role, joined_at) VALUES ($1, $2, $3, $4);`
|
query := `INSERT INTO space_members (space_id, user_id, role, joined_at) VALUES ($1, $2, $3, $4);`
|
||||||
_, err := r.db.Exec(query, spaceID, userID, role, time.Now())
|
_, err := r.db.Exec(query, spaceID, userID, role, time.Now())
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
})
|
})
|
||||||
|
|
||||||
g.SubGroup("/shared-with-me", func(g *router.Group) {
|
g.SubGroup("/shared-with-me", func(g *router.Group) {
|
||||||
g.Get("", spaceH.SpacesPage).Name("page.app.shared-with-me")
|
g.Get("", spaceH.SharedSpacesPage).Name("page.app.shared-with-me")
|
||||||
})
|
})
|
||||||
|
|
||||||
g.SubGroup("/settings", func(g *router.Group) {
|
g.SubGroup("/settings", func(g *router.Group) {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,24 @@ func (s *SpaceService) GetSpacesForUser(userID string) ([]*model.Space, error) {
|
||||||
return spaces, nil
|
return spaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOwnedSpaces returns spaces owned by the user.
|
||||||
|
func (s *SpaceService) GetOwnedSpaces(userID string) ([]*model.Space, error) {
|
||||||
|
spaces, err := s.spaceRepo.ByOwnerID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get owned spaces: %w", err)
|
||||||
|
}
|
||||||
|
return spaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSharedSpaces returns spaces shared with the user (not owned by them).
|
||||||
|
func (s *SpaceService) GetSharedSpaces(userID string) ([]*model.Space, error) {
|
||||||
|
spaces, err := s.spaceRepo.SharedWithUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get shared spaces: %w", err)
|
||||||
|
}
|
||||||
|
return spaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetSpace retrieves a single space by its ID.
|
// GetSpace retrieves a single space by its ID.
|
||||||
func (s *SpaceService) GetSpace(spaceID string) (*model.Space, error) {
|
func (s *SpaceService) GetSpace(spaceID string) (*model.Space, error) {
|
||||||
space, err := s.spaceRepo.ByID(spaceID)
|
space, err := s.spaceRepo.ByID(spaceID)
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ templ Spaces(spaces []blocks.SpaceCardInfo) {
|
||||||
<div class="container px-6 py-8 mx-auto">
|
<div class="container px-6 py-8 mx-auto">
|
||||||
<div class="mb-8 w-full flex justify-between items-center">
|
<div class="mb-8 w-full flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Spaces</h1>
|
<h1 class="text-3xl font-bold">My Spaces</h1>
|
||||||
<p class="text-muted-foreground mt-2">Manage and monitor your expenses across collaborative spaces.</p>
|
<p class="text-muted-foreground mt-2">Manage and monitor your expenses.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@button.Button(button.Props{
|
@button.Button(button.Props{
|
||||||
|
|
@ -32,3 +32,19 @@ templ Spaces(spaces []blocks.SpaceCardInfo) {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ SharedSpaces(spaces []blocks.SpaceCardInfo) {
|
||||||
|
@layouts.App("Shared with me") {
|
||||||
|
<div class="container px-6 py-8 mx-auto">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold">Shared with me</h1>
|
||||||
|
<p class="text-muted-foreground mt-2">Spaces that others have shared with you.</p>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
for _, space := range spaces {
|
||||||
|
@blocks.SpaceCard(space)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue