Skip to content

Commit

Permalink
feat(): add Article repository
Browse files Browse the repository at this point in the history
  • Loading branch information
ncarlier committed Mar 15, 2019
1 parent 4af960b commit b502102
Show file tree
Hide file tree
Showing 8 changed files with 370 additions and 83 deletions.
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
module github.com/ncarlier/reader

require (
github.com/brianvoe/gofakeit v3.17.0+incompatible
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448 // indirect
github.com/getsentry/raven-go v0.2.0
github.com/goadesign/goa v1.4.1 // indirect
github.com/graphql-go/graphql v0.7.7
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.0.0
github.com/ncarlier/webhookd v1.6.1
github.com/pkg/errors v0.8.1 // indirect
github.com/rs/zerolog v1.12.0
github.com/tdewolff/minify/v2 v2.3.8
miniflux.app v0.0.0-20190312032319-fc473f1d11a2
)
39 changes: 7 additions & 32 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/brianvoe/gofakeit v3.17.0+incompatible h1:C1+30+c0GtjgGDtRC+iePZeP1WMiwsWCELNJhmc7aIc=
github.com/brianvoe/gofakeit v3.17.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448 h1:8tNk6SPXzLDnATTrWoI5Bgw9s/x4uf0kmBpk21NZgI4=
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/goadesign/goa v1.4.1 h1:7klkZZ3eCXewU3E1//C2spxle0dzRRUVdeny/vdKrz4=
github.com/goadesign/goa v1.4.1/go.mod h1:d/9lpuZBK7HFi/7O0oXfwvdoIl+nx2bwKqctZe/lQao=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/graphql-go/graphql v0.7.7 h1:nwEsJGwPq9N6cElOO+NYyoWuELAQZ4GuJks0Rlco5og=
github.com/graphql-go/graphql v0.7.7/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/ncarlier/webhookd v1.6.1 h1:3JcnibSfel0/EaMfy8Xt4Nznio1wIjFQQA73y1C3DsA=
github.com/ncarlier/webhookd v1.6.1/go.mod h1:ZRPYyvVcgm7Bosnv4vLbugL6WslDfnVBe2YMf5eUi2s=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c=
github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/tdewolff/minify/v2 v2.3.8 h1:Eyv23Tu+Rb5Q2vyxmvzUgtHetgneqAsaGv3950s1EeA=
github.com/tdewolff/minify/v2 v2.3.8/go.mod h1:DD1stRlSx6JsHfl1+E/HVMQeXiec9rD1UQ0epklIZLc=
github.com/tdewolff/parse/v2 v2.3.5 h1:/uS8JfhwVJsNkEh769GM5ENv6L9LOh2Z9uW3tCdlhs0=
github.com/tdewolff/parse/v2 v2.3.5/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk=
github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181208175041-ad97f365e150/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
miniflux.app v0.0.0-20190312032319-fc473f1d11a2 h1:exqt4s0x/9SAl1AWNtTK82NdxqY854yFczHnYrzoN04=
miniflux.app v0.0.0-20190312032319-fc473f1d11a2/go.mod h1:bJ+xO1f6GiNnkt4z5yyZd64M2dEBhlHXRbTRhnCMTI0=
4 changes: 2 additions & 2 deletions pkg/db/article.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import "github.com/ncarlier/reader/pkg/model"

// ArticleRepository is the repository interface to manage Articles
type ArticleRepository interface {
GetArticles() ([]model.Article, error)
GetArticlesByUserID(userID uint32) ([]model.Article, error)
GetArticleByID(id uint32) (*model.Article, error)
CreateOrUpdateArticle(article model.Article) (*model.Article, error)
DeleteArticle(article model.Article) (*model.Article, error)
DeleteArticle(article model.Article) error
}
219 changes: 178 additions & 41 deletions pkg/db/postgres/article.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,152 @@
package postgres

import (
"database/sql"
"errors"
"fmt"

"github.com/ncarlier/reader/pkg/model"
)

// GetArticles returns articles from DB
func (pg *DB) GetArticles() ([]model.Article, error) {
rows, err := pg.db.Query(`
SELECT
id,
category_id,
title,
text,
html,
url,
image,
hash,
status,
published_at,
created_at,
updated_at
const articleColumns = `
id,
user_id,
category_id,
title,
text,
html,
url,
image,
hash,
status,
published_at,
created_at,
updated_at
`

func mapRowToArticle(row *sql.Row, article *model.Article) error {
return row.Scan(
&article.ID,
&article.UserID,
&article.CategoryID,
&article.Title,
&article.Text,
&article.HTML,
&article.URL,
&article.Image,
&article.Hash,
&article.Status,
&article.PublishedAt,
&article.CreatedAt,
&article.UpdatedAt,
)
}

func mapRowsToArticle(rows *sql.Rows, article *model.Article) error {
return rows.Scan(
&article.ID,
&article.UserID,
&article.CategoryID,
&article.Title,
&article.Text,
&article.HTML,
&article.URL,
&article.Image,
&article.Hash,
&article.Status,
&article.PublishedAt,
&article.CreatedAt,
&article.UpdatedAt,
)
}

func (pg *DB) createArticle(article model.Article) (*model.Article, error) {
row := pg.db.QueryRow(fmt.Sprintf(`
INSERT INTO articles (
user_id,
category_id,
title,
text,
html,
url,
image,
hash,
status,
published_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING %s
`, articleColumns),
article.UserID,
article.CategoryID,
article.Title,
article.Text,
article.HTML,
article.URL,
article.Image,
article.Hash,
article.Status,
article.PublishedAt,
)
result := &model.Article{}

if err := mapRowToArticle(row, result); err != nil {
return nil, err
}
return result, nil
}

func (pg *DB) updateArticle(article model.Article) (*model.Article, error) {
row := pg.db.QueryRow(fmt.Sprintf(`
UPDATE article SET
category_id = $3,
title = $4,
text = $5,
html = $6,
url = $7,
image = $8,
hash = $9,
status = $10,
published_at = $11,
updated_at=NOW()
WHERE id=$1 AND user_id=$2
RETURNING %s
`, articleColumns),
article.ID,
article.UserID,
article.CategoryID,
article.Title,
article.Text,
article.HTML,
article.URL,
article.Image,
article.Hash,
article.Status,
article.PublishedAt,
)

result := &model.Article{}

if err := mapRowToArticle(row, result); err != nil {
return nil, err
}
return result, nil
}

// CreateOrUpdateArticle creates or updates a article into the DB
func (pg *DB) CreateOrUpdateArticle(article model.Article) (*model.Article, error) {
if article.ID != nil {
return pg.updateArticle(article)
}
return pg.createArticle(article)
}

// GetArticlesByUserID returns user's articles from DB
func (pg *DB) GetArticlesByUserID(userID uint32) ([]model.Article, error) {
rows, err := pg.db.Query(fmt.Sprintf(`
SELECT %s
FROM articles
ORDER BY created_at DESC`)
WHERE user_id=$1
ORDER BY created_at DESC`, articleColumns), userID)
if err != nil {
return nil, err
}
Expand All @@ -32,25 +155,11 @@ func (pg *DB) GetArticles() ([]model.Article, error) {
var result []model.Article

for rows.Next() {
article := model.Article{}
err = rows.Scan(
&article.ID,
&article.CategoryID,
&article.Title,
&article.Text,
&article.HTML,
&article.URL,
&article.Image,
&article.Hash,
&article.Status,
&article.PublishedAt,
&article.CreatedAt,
&article.UpdatedAt,
)
if err != nil {
article := &model.Article{}
if err := mapRowsToArticle(rows, article); err != nil {
return nil, err
}
result = append(result, article)
result = append(result, *article)
}
err = rows.Err()
if err != nil {
Expand All @@ -61,15 +170,43 @@ func (pg *DB) GetArticles() ([]model.Article, error) {

// GetArticleByID returns an article by its ID from DB
func (pg *DB) GetArticleByID(id uint32) (*model.Article, error) {
return nil, fmt.Errorf("Not yet implemented")
}
row := pg.db.QueryRow(fmt.Sprintf(`
SELECT %s
FROM articles
WHERE id = $1`, articleColumns),
id,
)

// CreateOrUpdateArticle creates an article into the DB
func (pg *DB) CreateOrUpdateArticle(article model.Article) (*model.Article, error) {
return nil, fmt.Errorf("Not yet implemented")
result := &model.Article{}
err := mapRowToArticle(row, result)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return result, nil
}

// DeleteArticle remove an article from the DB
func (pg *DB) DeleteArticle(article model.Article) (*model.Article, error) {
return nil, fmt.Errorf("Not yet implemented")
func (pg *DB) DeleteArticle(article model.Article) error {
result, err := pg.db.Exec(`
DELETE FROM articles
WHERE ID=$1
`,
article.ID,
)
if err != nil {
return err
}

count, err := result.RowsAffected()
if err != nil {
return err
}

if count == 0 {
return errors.New("no article has been removed")
}

return nil
}
Loading

0 comments on commit b502102

Please sign in to comment.