Skip to content

Commit

Permalink
add update category endpoint and unique slug+metadata to categories
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Sep 30, 2023
1 parent d4dcd1f commit 9bfed31
Show file tree
Hide file tree
Showing 25 changed files with 1,155 additions and 197 deletions.
58 changes: 58 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,19 @@ paths:
default: { $ref: "#/components/responses/InternalServerError" }
"200": { $ref: "#/components/responses/CategoryListOK" }

/v1/categories/{category_id}:
patch:
operationId: CategoryUpdate
description: Create a category for organising posts.
tags: [categories]
parameters: [$ref: "#/components/parameters/CategoryIDParam"]
requestBody: { $ref: "#/components/requestBodies/CategoryUpdate" }
responses:
default: { $ref: "#/components/responses/InternalServerError" }
"400": { $ref: "#/components/responses/BadRequest" }
"401": { $ref: "#/components/responses/Unauthorised" }
"200": { $ref: "#/components/responses/CategoryUpdateOK" }

#
# 888 888 888
# 888 888 888
Expand Down Expand Up @@ -849,6 +862,14 @@ components:
schema:
type: string

CategoryIDParam:
description: Unique category ID.
name: category_id
in: path
required: true
schema:
$ref: "#/components/schemas/Identifier"

CollectionIDParam:
description: Unique collection ID.
name: collection_id
Expand Down Expand Up @@ -927,6 +948,11 @@ components:
application/json:
schema: { $ref: "#/components/schemas/CategoryIdentifierList" }

CategoryUpdate:
content:
application/json:
schema: { $ref: "#/components/schemas/CategoryMutableProps" }

ThreadCreate:
content:
application/json:
Expand Down Expand Up @@ -1098,6 +1124,13 @@ components:
schema:
$ref: "#/components/schemas/Category"

CategoryUpdateOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Category"

CategoryListOK:
description: OK
content:
Expand Down Expand Up @@ -1896,6 +1929,8 @@ components:
properties:
name:
$ref: "#/components/schemas/CategoryName"
slug:
$ref: "#/components/schemas/CategorySlug"
description:
type: string
colour:
Expand All @@ -1904,24 +1939,47 @@ components:
type: integer
admin:
type: boolean
meta: { $ref: "#/components/schemas/Metadata" }

CategoryInitialProps:
type: object
required: [name, description, colour, admin]
properties:
name:
$ref: "#/components/schemas/CategoryName"
slug:
$ref: "#/components/schemas/CategorySlug"
description:
type: string
colour:
type: string
admin:
type: boolean
meta: { $ref: "#/components/schemas/Metadata" }

CategoryMutableProps:
type: object
properties:
name:
$ref: "#/components/schemas/CategoryName"
slug:
$ref: "#/components/schemas/CategorySlug"
description:
type: string
colour:
type: string
admin:
type: boolean
meta: { $ref: "#/components/schemas/Metadata" }

CategoryName:
description: A category's user-facing name.
type: string

CategorySlug:
description: A category's URL-safe slug.
type: string

CategoryNameList:
description: A list of category names.
type: array
Expand Down
63 changes: 20 additions & 43 deletions app/resources/category/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (
"github.com/Southclaws/fault/fctx"
"github.com/Southclaws/fault/fmsg"
"github.com/Southclaws/fault/ftag"
"github.com/gosimple/slug"
"github.com/rs/xid"
"go.uber.org/multierr"

"github.com/Southclaws/storyden/internal/ent"
"github.com/Southclaws/storyden/internal/ent/category"
"github.com/Southclaws/storyden/internal/ent/post"
"github.com/Southclaws/storyden/internal/ent/predicate"
"github.com/Southclaws/storyden/internal/utils"
)

type database struct {
Expand All @@ -27,27 +27,22 @@ func New(db *ent.Client) Repository {
return &database{db}
}

func (d *database) CreateCategory(ctx context.Context, name, desc, colour string, sort int, admin bool, opts ...option) (*Category, error) {
insert := Category{
Name: name,
Description: desc,
Colour: colour,
Sort: sort,
Admin: admin,
}
func (d *database) CreateCategory(ctx context.Context, name, desc, colour string, sort int, admin bool, opts ...Option) (*Category, error) {
create := d.db.Category.Create()
mutation := create.Mutation()

mutation.SetName(name)
mutation.SetSlug(slug.Make(name))
mutation.SetDescription(desc)
mutation.SetColour(colour)
mutation.SetSort(sort)
mutation.SetAdmin(admin)

for _, v := range opts {
v(&insert)
for _, fn := range opts {
fn(mutation)
}

id, err := d.db.Category.
Create().
SetName(insert.Name).
SetDescription(insert.Description).
SetColour(insert.Colour).
SetSort(insert.Sort).
SetAdmin(insert.Admin).
SetNillableID(utils.OptionalID(xid.ID(insert.ID))).
id, err := create.
OnConflictColumns(category.FieldID).
UpdateNewValues().
ID(ctx)
Expand Down Expand Up @@ -178,33 +173,15 @@ func (d *database) Reorder(ctx context.Context, ids []CategoryID) ([]*Category,
return newcats, nil
}

func (d *database) UpdateCategory(ctx context.Context, id CategoryID, name, desc, colour *string, sort *int, admin *bool) (*Category, error) {
u := d.db.Category.UpdateOneID(xid.ID(id))

// TODO: Write a less explicit, more ergonomic way to do this:

//nocheck:wsl
if name != nil {
u.SetName(*name)
}

if desc != nil {
u.SetDescription(*desc)
}

if colour != nil {
u.SetColour(*colour)
}

if sort != nil {
u.SetSort(*sort)
}
func (d *database) UpdateCategory(ctx context.Context, id CategoryID, opts ...Option) (*Category, error) {
update := d.db.Category.UpdateOneID(xid.ID(id))
mutation := update.Mutation()

if admin != nil {
u.SetAdmin(*admin)
for _, fn := range opts {
fn(mutation)
}

c, err := u.Save(ctx)
c, err := update.Save(ctx)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx), ftag.With(ftag.Internal))
}
Expand Down
4 changes: 4 additions & 0 deletions app/resources/category/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ type PostMeta struct {
type Category struct {
ID CategoryID
Name string
Slug string
Description string
Colour string
Sort int
Admin bool
Recent []PostMeta
PostCount int
Metadata map[string]any
}

func PostMetaFromModel(p *ent.Post) *PostMeta {
Expand Down Expand Up @@ -55,10 +57,12 @@ func FromModel(c *ent.Category) *Category {
return &Category{
ID: CategoryID(c.ID),
Name: c.Name,
Slug: c.Slug,
Description: c.Description,
Colour: c.Colour,
Sort: c.Sort,
Admin: c.Admin,
Recent: recent,
Metadata: c.Metadata,
}
}
52 changes: 46 additions & 6 deletions app/resources/category/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package category

import (
"context"

"github.com/rs/xid"

"github.com/Southclaws/storyden/internal/ent"
)

type option func(*Category)
type Option func(*ent.CategoryMutation)

type Repository interface {
CreateCategory(ctx context.Context,
Expand All @@ -13,16 +17,52 @@ type Repository interface {
colour string,
sort int,
admin bool,
opts ...option) (*Category, error)
opts ...Option) (*Category, error)

GetCategories(ctx context.Context, admin bool) ([]*Category, error)
Reorder(ctx context.Context, ids []CategoryID) ([]*Category, error)
UpdateCategory(ctx context.Context, id CategoryID, name, desc, colour *string, sort *int, admin *bool) (*Category, error)
UpdateCategory(ctx context.Context, id CategoryID, opts ...Option) (*Category, error)
DeleteCategory(ctx context.Context, id CategoryID, moveto CategoryID) (*Category, error)
}

func WithID(id CategoryID) option {
return func(c *Category) {
c.ID = id
func WithID(id CategoryID) Option {
return func(cm *ent.CategoryMutation) {
cm.SetID(xid.ID(id))
}
}

func WithName(v string) Option {
return func(cm *ent.CategoryMutation) {
cm.SetName(v)
}
}

func WithSlug(v string) Option {
return func(cm *ent.CategoryMutation) {
cm.SetSlug(v)
}
}

func WithDescription(v string) Option {
return func(cm *ent.CategoryMutation) {
cm.SetDescription(v)
}
}

func WithColour(v string) Option {
return func(cm *ent.CategoryMutation) {
cm.SetColour(v)
}
}

func WithAdmin(v bool) Option {
return func(cm *ent.CategoryMutation) {
cm.SetAdmin(v)
}
}

func WithMeta(v map[string]any) Option {
return func(cm *ent.CategoryMutation) {
cm.SetMetadata(v)
}
}
33 changes: 33 additions & 0 deletions app/services/category/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import (
type Service interface {
Create(ctx context.Context, name string, description string, colour string, admin bool) (*category.Category, error)
Reorder(ctx context.Context, ids []category.CategoryID) ([]*category.Category, error)
Update(ctx context.Context, id category.CategoryID, partial Partial) (*category.Category, error)
}

type Partial struct {
Name opt.Optional[string]
Slug opt.Optional[string]
Description opt.Optional[string]
Colour opt.Optional[string]
Admin opt.Optional[bool]
Meta opt.Optional[map[string]any]
}

func Build() fx.Option {
Expand Down Expand Up @@ -80,6 +83,36 @@ func (s *service) Reorder(ctx context.Context, ids []category.CategoryID) ([]*ca
return cats, nil
}

func (s *service) Update(ctx context.Context, id category.CategoryID, partial Partial) (*category.Category, error) {
opts := []category.Option{}

if v, ok := partial.Name.Get(); ok {
opts = append(opts, category.WithName(v))
}
if v, ok := partial.Slug.Get(); ok {
opts = append(opts, category.WithSlug(v))
}
if v, ok := partial.Description.Get(); ok {
opts = append(opts, category.WithDescription(v))
}
if v, ok := partial.Colour.Get(); ok {
opts = append(opts, category.WithColour(v))
}
if v, ok := partial.Admin.Get(); ok {
opts = append(opts, category.WithAdmin(v))
}
if v, ok := partial.Meta.Get(); ok {
opts = append(opts, category.WithMeta(v))
}

cat, err := s.category_repo.UpdateCategory(ctx, id, opts...)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

return cat, nil
}

func (s *service) authorise(ctx context.Context) error {
aid, err := authentication.GetAccountID(ctx)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions app/transports/openapi/bindings/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ func (c Categories) CategoryUpdateOrder(ctx context.Context, request openapi.Cat
},
}, nil
}

func (c Categories) CategoryUpdate(ctx context.Context, request openapi.CategoryUpdateRequestObject) (openapi.CategoryUpdateResponseObject, error) {
cat, err := c.category_svc.Update(ctx, category.CategoryID(deserialiseID(request.CategoryId)), category_svc.Partial{
Name: []string{},
Description: []string{},
Colour: []string{},
Admin: []bool{},
})
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

return openapi.CategoryUpdate200JSONResponse{
CategoryUpdateOKJSONResponse: openapi.CategoryUpdateOKJSONResponse(serialiseCategory(cat)),
}, nil
}
Loading

2 comments on commit 9bfed31

@vercel
Copy link

@vercel vercel bot commented on 9bfed31 Sep 30, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on 9bfed31 Sep 30, 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.