Skip to content

Commit

Permalink
basic member list
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Dec 18, 2023
1 parent 1092ee4 commit 7d6b6f2
Show file tree
Hide file tree
Showing 17 changed files with 1,052 additions and 196 deletions.
42 changes: 42 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,21 @@ paths:
# 888
#

/v1/profiles:
get:
operationId: ProfileList
description: Query and search profiles.
tags: [profiles]
security: []
parameters:
- $ref: "#/components/parameters/SearchQuery"
- $ref: "#/components/parameters/PaginationQuery"
responses:
default: { $ref: "#/components/responses/InternalServerError" }
"404": { $ref: "#/components/responses/NotFound" }
"401": { $ref: "#/components/responses/Unauthorised" }
"200": { $ref: "#/components/responses/ProfileListOK" }

/v1/profiles/{account_handle}:
get:
operationId: ProfileGet
Expand Down Expand Up @@ -1482,6 +1497,13 @@ components:
type: string
format: binary

ProfileListOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/PublicProfileListResult"

ProfileGetOK:
description: OK
content:
Expand Down Expand Up @@ -2432,6 +2454,26 @@ components:
interests:
$ref: "#/components/schemas/TagList"

PublicProfileListResult:
type: object
required: [page_size, results, total_pages, current_page, profiles]
properties:
page_size:
type: integer
results:
type: integer
total_pages:
type: integer
current_page:
type: integer
next_page:
type: integer
profiles: { $ref: "#/components/schemas/PublicProfileList" }

PublicProfileList:
type: array
items: { $ref: "#/components/schemas/PublicProfile" }

#
# .d8888b. 888
# d88P Y88b 888
Expand Down
6 changes: 5 additions & 1 deletion app/resources/profile/dto.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package profile

import (
"time"

"github.com/Southclaws/dt"

"github.com/Southclaws/storyden/app/resources/account"
Expand All @@ -9,7 +11,8 @@ import (
)

type Profile struct {
ID account.AccountID
ID account.AccountID
Created time.Time

Handle string
Name string
Expand All @@ -28,6 +31,7 @@ func FromModel(a *ent.Account) (*Profile, error) {

return &Profile{
ID: account.AccountID(a.ID),
Created: a.CreatedAt,
Handle: a.Handle,
Name: a.Name,
Bio: a.Bio,
Expand Down
90 changes: 90 additions & 0 deletions app/resources/profile_search/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package profile_search

import (
"context"

"github.com/Southclaws/dt"
"github.com/Southclaws/fault"
"github.com/Southclaws/fault/fctx"
"github.com/Southclaws/opt"

"github.com/Southclaws/storyden/app/resources/profile"
"github.com/Southclaws/storyden/internal/ent"
"github.com/Southclaws/storyden/internal/ent/account"
)

type Filter func(*ent.AccountQuery)

type Result struct {
PageSize int
Results int
TotalPages int
CurrentPage int
NextPage opt.Optional[int]
Profiles []*profile.Profile
}

type Repository interface {
Search(ctx context.Context, page int, pageSize int, opts ...Filter) (*Result, error)
}

func WithDisplayNameContains(q string) Filter {
return func(pq *ent.AccountQuery) {
pq.Where(account.And(
account.NameContainsFold(q),
))
}
}

func WithHandleContains(q string) Filter {
return func(pq *ent.AccountQuery) {
pq.Where(account.And(
account.HandleContainsFold(q),
))
}
}

type database struct {
db *ent.Client
}

func New(db *ent.Client) Repository {
return &database{db}
}

func (d *database) Search(ctx context.Context, page int, size int, filters ...Filter) (*Result, error) {
total, err := d.db.Account.Query().Count(ctx)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

q := d.db.Account.Query().
Limit(size + 1).
Offset(page * size).
Order(ent.Asc(account.FieldCreatedAt))

for _, fn := range filters {
fn(q)
}

r, err := q.All(ctx)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

nextPage := opt.NewSafe(page+1, len(r) >= size)

profiles, err := dt.MapErr(r, profile.FromModel)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

return &Result{
PageSize: size,
Results: len(profiles),
TotalPages: total / size,
CurrentPage: page,
NextPage: nextPage,
Profiles: profiles,
}, nil
}
2 changes: 2 additions & 0 deletions app/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/Southclaws/storyden/app/resources/link_graph"
"github.com/Southclaws/storyden/app/resources/notification"
"github.com/Southclaws/storyden/app/resources/post_search"
"github.com/Southclaws/storyden/app/resources/profile_search"
"github.com/Southclaws/storyden/app/resources/rbac"
"github.com/Southclaws/storyden/app/resources/react"
"github.com/Southclaws/storyden/app/resources/reply"
Expand Down Expand Up @@ -46,6 +47,7 @@ func Build() fx.Option {
item_search.New,
link.New,
link_graph.New,
profile_search.New,
),
)
}
65 changes: 63 additions & 2 deletions app/transports/openapi/bindings/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ package bindings
import (
"context"
"fmt"
"strconv"
"time"

"github.com/Southclaws/dt"
"github.com/Southclaws/fault"
"github.com/Southclaws/fault/fctx"
"github.com/Southclaws/opt"

account_repo "github.com/Southclaws/storyden/app/resources/account"
"github.com/Southclaws/storyden/app/resources/profile"
"github.com/Southclaws/storyden/app/resources/profile_search"
"github.com/Southclaws/storyden/app/services/account"
"github.com/Southclaws/storyden/internal/config"
"github.com/Southclaws/storyden/internal/openapi"
Expand All @@ -19,10 +24,55 @@ type Profiles struct {
apiAddress string
as account.Service
ar account_repo.Repository
ps profile_search.Repository
}

func NewProfiles(cfg config.Config, as account.Service, ar account_repo.Repository) Profiles {
return Profiles{cfg.PublicWebAddress, as, ar}
func NewProfiles(cfg config.Config, as account.Service, ar account_repo.Repository, ps profile_search.Repository) Profiles {
return Profiles{cfg.PublicWebAddress, as, ar, ps}
}

func (p *Profiles) ProfileList(ctx context.Context, request openapi.ProfileListRequestObject) (openapi.ProfileListResponseObject, error) {
pageSize := 50

page := opt.NewPtrMap(request.Params.Page, func(s string) int {
v, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0
}

return max(1, int(v))

Check failure on line 43 in app/transports/openapi/bindings/profiles.go

View workflow job for this annotation

GitHub Actions / backend-test

undefined: max
}).Or(1)

opts := []profile_search.Filter{}

if request.Params.Q != nil {
opts = append(opts,
profile_search.WithDisplayNameContains(*request.Params.Q),
profile_search.WithHandleContains(*request.Params.Q),
)
}

// API is 1-indexed, internally it's 0-indexed.
page = max(0, page-1)

Check failure on line 56 in app/transports/openapi/bindings/profiles.go

View workflow job for this annotation

GitHub Actions / backend-test

undefined: max

result, err := p.ps.Search(ctx, page, pageSize, opts...)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

// API is 1-indexed, internally it's 0-indexed.
page = result.CurrentPage + 1

return openapi.ProfileList200JSONResponse{
ProfileListOKJSONResponse: openapi.ProfileListOKJSONResponse{
PageSize: pageSize,
Results: result.Results,
TotalPages: result.TotalPages,
CurrentPage: page,
NextPage: result.NextPage.Ptr(),
Profiles: dt.Map(result.Profiles, serialiseProfile),
},
}, nil
}

func (p *Profiles) ProfileGet(ctx context.Context, request openapi.ProfileGetRequestObject) (openapi.ProfileGetResponseObject, error) {
Expand Down Expand Up @@ -50,3 +100,14 @@ func (p *Profiles) ProfileGet(ctx context.Context, request openapi.ProfileGetReq
},
}, nil
}

func serialiseProfile(in *profile.Profile) openapi.PublicProfile {
return openapi.PublicProfile{
Id: openapi.Identifier(in.ID.String()),
Bio: &in.Bio,
Handle: in.Handle,
// Image: &avatarURL,
Name: in.Name,
CreatedAt: in.Created.Format(time.RFC3339),
}
}
Loading

1 comment on commit 7d6b6f2

@vercel
Copy link

@vercel vercel bot commented on 7d6b6f2 Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.