Skip to content

Commit

Permalink
[GEM-52] - Add Article to Collections Endpoint (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
nayyara-airlangga authored Sep 12, 2023
2 parents 3d3398c + 395458b commit d0a9298
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 3 deletions.
60 changes: 60 additions & 0 deletions app/article/collection_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,66 @@ func getOwnCollectionsHandler(w http.ResponseWriter, r *http.Request) {
app.WriteHttpBodyJson(w, http.StatusOK, collections)
}

func getAddedCollectionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

creator, ok := ctx.Value(auth.UserInfoCtx).(auth.User)
if !ok {
app.WriteHttpError(w, http.StatusUnauthorized, auth.ErrInvalidAccessToken)
return
}

articleId := chi.URLParam(r, "articleId")

collections, err := getAddedCollections(ctx, creator.Id, articleId)
if err != nil {
switch {
case errors.As(err, &ErrInvalidArticleId):
app.WriteHttpError(w, http.StatusBadRequest, err)
case errors.Is(err, ErrArticleDoesNotExist):
app.WriteHttpError(w, http.StatusNotFound, err)
default:
app.WriteHttpInternalServerError(w)
}
return
}

app.WriteHttpBodyJson(w, http.StatusOK, collections)
}

func addArticleToCollectionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

creator, ok := ctx.Value(auth.UserInfoCtx).(auth.User)
if !ok {
app.WriteHttpError(w, http.StatusUnauthorized, auth.ErrInvalidAccessToken)
return
}

articleId := chi.URLParam(r, "articleId")

var body addArticleToCollectionsReq
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
app.WriteHttpError(w, http.StatusBadRequest, err)
return
}

collections, err := addArticleToCollections(ctx, creator.Id, articleId, body.CollectionIds)
if err != nil {
switch {
case errors.As(err, &ErrInvalidArticleId):
app.WriteHttpError(w, http.StatusBadRequest, err)
case errors.Is(err, ErrArticleDoesNotExist):
app.WriteHttpError(w, http.StatusNotFound, err)
default:
app.WriteHttpInternalServerError(w)
}
return
}

app.WriteHttpBodyJson(w, http.StatusOK, collections)
}

func getPublicCollectionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

Expand Down
101 changes: 100 additions & 1 deletion app/article/collection_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package article
import (
"context"
"errors"
"fmt"
"time"

"github.com/georgysavva/scany/v2/pgxscan"
"github.com/jackc/pgx/v5"
Expand Down Expand Up @@ -138,6 +140,103 @@ func findCollectionsByCreatorId(ctx context.Context, tx pgx.Tx, creatorId ulid.U
return collections, nil
}

func findAddedCollectionsByArticleIdAndCreatorId(ctx context.Context, tx pgx.Tx, articleId, creatorId ulid.ULID) (collections []*Collection, err error) {
if _, err := findArticleById(ctx, tx, articleId); err != nil {
return collections, err
}

q := `
SELECT c.*
FROM collections c
WHERE EXISTS (
SELECT ca.id
FROM collection_articles ca
WHERE EXISTS (
SELECT a.id
FROM articles a
WHERE
a.id = $2 AND
a.id = ca.article_id AND
a.deleted_at IS NULL
) AND
c.id = ca.collection_id AND
ca.deleted_at IS NULL
) AND EXISTS (
SELECT u.id
FROM users u
WHERE
u.id = $1 AND
u.id = c.creator_id AND
u.deleted_at IS NULL
) AND
c.deleted_at IS NULL
ORDER BY c.created_at DESC
`

collections = []*Collection{}
if err = pgxscan.Select(ctx, tx, &collections, q, creatorId, articleId); err != nil {
log.Err(err).Msg("Failed to find added collections by article id and creator id")
return
}

return collections, nil
}

func createCollectionArticles(ctx context.Context, tx pgx.Tx, articleId, creatorId ulid.ULID, collectionIds []ulid.ULID) (collections []*Collection, err error) {
if _, err := findArticleById(ctx, tx, articleId); err != nil {
return collections, err
}

q := `
UPDATE collection_articles ca
SET deleted_at = $3
FROM collections c, users u
WHERE
ca.collection_id = c.id AND
ca.article_id = $1 AND
c.creator_id = u.id AND
u.id = $2 AND
ca.deleted_at IS NULL
`

now := time.Now()

if _, err = tx.Exec(ctx, q, articleId, creatorId, now); err != nil {
log.Err(err).Msg("Failed to create collection articles")
return
}

if len(collectionIds) > 0 {
q = "INSERT INTO collection_articles (id, collection_id, article_id, created_at) VALUES"

params := []any{articleId, now}
paramCount := 3
for i, collectionId := range collectionIds {
q += fmt.Sprintf("\n($%d, $%d, $1, $2)", paramCount, paramCount+1)
if i+1 < len(collectionIds) {
q += ","
}

params = append(params, ulid.Make(), collectionId)
paramCount += 2
}

q += "\nON CONFLICT DO NOTHING"

if _, err = tx.Exec(ctx, q, params...); err != nil {
log.Err(err).Msg("Failed to create collection articles")
return
}
}

collections, err = findAddedCollectionsByArticleIdAndCreatorId(ctx, tx, articleId, creatorId)
if err != nil {
return
}

return collections, nil
}

func findPublicCollections(ctx context.Context, tx pgx.Tx) (collections []*CollectionMetadata, err error) {
q := `
SELECT c.*, u.name creator_name, COUNT(1) number_of_articles
Expand All @@ -158,4 +257,4 @@ func findPublicCollections(ctx context.Context, tx pgx.Tx) (collections []*Colle
}

return collections, nil
}
}
54 changes: 54 additions & 0 deletions app/article/collection_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,60 @@ func getOwnCollections(ctx context.Context, creatorId ulid.ULID) (collections []
return collections, nil
}

func getAddedCollections(ctx context.Context, creatorId ulid.ULID, articleIdStr string) (collections []*Collection, err error) {
articleId, err := validateArticleId(articleIdStr)
if err != nil {
return
}

tx, err := pool.Begin(ctx)
if err != nil {
log.Err(err).Msg("Failed to get added collections")
return
}

defer tx.Rollback(ctx)

collections, err = findAddedCollectionsByArticleIdAndCreatorId(ctx, tx, articleId, creatorId)
if err != nil {
return
}

if err = tx.Commit(ctx); err != nil {
log.Err(err).Msg("Failed to get added collections")
return
}

return collections, nil
}

func addArticleToCollections(ctx context.Context, creatorId ulid.ULID, articleIdStr string, collectionIds []ulid.ULID) (collections []*Collection, err error) {
articleId, err := validateArticleId(articleIdStr)
if err != nil {
return
}

tx, err := pool.Begin(ctx)
if err != nil {
log.Err(err).Msg("Failed to add article to collections")
return
}

defer tx.Rollback(ctx)

collections, err = createCollectionArticles(ctx, tx, articleId, creatorId, collectionIds)
if err != nil {
return
}

if err = tx.Commit(ctx); err != nil {
log.Err(err).Msg("Failed to add article to collections")
return
}

return collections, nil
}

func getPublicCollections(ctx context.Context) (collections []*CollectionMetadata, err error) {
tx, err := pool.Begin(ctx)
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion app/article/request.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package article

import "gopkg.in/guregu/null.v4"
import (
"github.com/oklog/ulid/v2"
"gopkg.in/guregu/null.v4"
)

type createArticleCategoryReq struct {
Name string `json:"name"`
Expand Down Expand Up @@ -71,3 +74,7 @@ type updateCollectionReq struct {
Name null.String `json:"name"`
Visibility null.String `json:"visibility"`
}

type addArticleToCollectionsReq struct {
CollectionIds []ulid.ULID `json:"collection_ids"`
}
5 changes: 4 additions & 1 deletion app/article/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ func Router() *chi.Mux {

r.Get("/", getArticlesHandler)
r.Get("/{id}", getArticleByIdHandler)

r.Group(func(r chi.Router) {
r.Use(auth.UserAuthMiddleware)

r.Get("/{articleId}/collection", getAddedCollectionsHandler)
r.Post("/{articleId}/collection", addArticleToCollectionsHandler)

r.Post("/collection/new", createCollectionHandler)
r.Put("/collection/{collectionId}", updateCollectionHandler)
Expand Down

0 comments on commit d0a9298

Please sign in to comment.