#13 - browse articles
This commit is contained in:
73
internal/database/createPost.sql.go
Normal file
73
internal/database/createPost.sql.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: createPost.sql
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const createPost = `-- name: CreatePost :one
|
||||
INSERT INTO posts (id, created_at, updated_at, url, title, description, feed_id, published_at)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8
|
||||
) ON CONFLICT (url) DO UPDATE
|
||||
SET
|
||||
id = $1,
|
||||
created_at = $2,
|
||||
updated_at = $3,
|
||||
title = $5,
|
||||
description = $6,
|
||||
feed_id = $7,
|
||||
published_at = $8
|
||||
WHERE posts.url = $4
|
||||
RETURNING id, created_at, updated_at, title, url, description, published_at, feed_id
|
||||
`
|
||||
|
||||
type CreatePostParams struct {
|
||||
ID uuid.UUID
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Url string
|
||||
Title string
|
||||
Description string
|
||||
FeedID uuid.UUID
|
||||
PublishedAt time.Time
|
||||
}
|
||||
|
||||
func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) (Post, error) {
|
||||
row := q.db.QueryRowContext(ctx, createPost,
|
||||
arg.ID,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
arg.Url,
|
||||
arg.Title,
|
||||
arg.Description,
|
||||
arg.FeedID,
|
||||
arg.PublishedAt,
|
||||
)
|
||||
var i Post
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
&i.Url,
|
||||
&i.Description,
|
||||
&i.PublishedAt,
|
||||
&i.FeedID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
54
internal/database/getPostsForUser.sql.go
Normal file
54
internal/database/getPostsForUser.sql.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: getPostsForUser.sql
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const getPostsForUser = `-- name: GetPostsForUser :many
|
||||
SELECT id, created_at, updated_at, title, url, description, published_at, feed_id FROM posts WHERE feed_id IN (SELECT feed_follows.feed_id FROM feed_follows WHERE feed_follows.user_id = $1)
|
||||
ORDER BY published_at DESC LIMIT $2
|
||||
`
|
||||
|
||||
type GetPostsForUserParams struct {
|
||||
UserID uuid.UUID
|
||||
Limit int32
|
||||
}
|
||||
|
||||
func (q *Queries) GetPostsForUser(ctx context.Context, arg GetPostsForUserParams) ([]Post, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getPostsForUser, arg.UserID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Post
|
||||
for rows.Next() {
|
||||
var i Post
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
&i.Url,
|
||||
&i.Description,
|
||||
&i.PublishedAt,
|
||||
&i.FeedID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@@ -29,6 +29,17 @@ type FeedFollow struct {
|
||||
FeedID uuid.UUID
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
ID uuid.UUID
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Title string
|
||||
Url string
|
||||
Description string
|
||||
PublishedAt time.Time
|
||||
FeedID uuid.UUID
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
CreatedAt time.Time
|
||||
|
||||
36
internal/handlers/browseHandler.go
Normal file
36
internal/handlers/browseHandler.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/config"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/database"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func BrowseHandler(s *config.State, cmd config.Command, user database.User) error {
|
||||
var limit int32
|
||||
if len(cmd.Args) < 1 {
|
||||
limit = 2
|
||||
} else {
|
||||
l, err := strconv.Atoi(cmd.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
limit = int32(l)
|
||||
}
|
||||
|
||||
posts, err := s.DB.GetPostsForUser(context.Background(), database.GetPostsForUserParams{
|
||||
UserID: user.ID,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, post := range posts {
|
||||
fmt.Printf("Title: %s\nURL: %s\nArticle: %sPublished: %s\n\n", post.Title, post.Url, post.Description, post.PublishedAt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,8 +3,12 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/config"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/database"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/parser"
|
||||
"github.com/rdarius/boot-dev-blog-aggregator/internal/rss"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ScrapeFeedsHandler(s *config.State, cmd config.Command) error {
|
||||
@@ -29,7 +33,24 @@ func ScrapeFeedsHandler(s *config.State, cmd config.Command) error {
|
||||
fmt.Println("Feed Title: " + feedData.Channel.Title)
|
||||
|
||||
for _, i := range feedData.Channel.Item {
|
||||
fmt.Println("Item Title: " + i.Title)
|
||||
|
||||
pubTime, err := parser.ParseTimestamp(i.PubDate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.DB.CreatePost(ctx, database.CreatePostParams{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
FeedID: nextFeed.ID,
|
||||
Url: i.Link,
|
||||
Title: i.Title,
|
||||
Description: i.Description,
|
||||
PublishedAt: pubTime,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
29
internal/parser/TimestampParser.go
Normal file
29
internal/parser/TimestampParser.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ParseTimestamp(str string) (time.Time, error) {
|
||||
layouts := []string{
|
||||
time.RFC1123, // "Mon, 02 Jan 2006 15:04:05 MST"
|
||||
time.RFC1123Z, // "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
time.RFC3339, // "2006-01-02T15:04:05Z07:00"
|
||||
time.RFC3339Nano, // "2006-01-02T15:04:05.999999999Z07:00"
|
||||
"2006-01-02 15:04:05", // Common SQL format
|
||||
"02 Jan 2006 15:04:05", // "02 Jan 2025 15:04:05"
|
||||
"02-Jan-2006 15:04:05", // "02-Jan-2025 15:04:05"
|
||||
"02/01/2006 15:04:05", // "02/01/2025 15:04:05"
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
var err error
|
||||
for _, layout := range layouts {
|
||||
t, err = time.Parse(layout, str)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("unknown timestamp format: %s", str)
|
||||
}
|
||||
1
main.go
1
main.go
@@ -42,6 +42,7 @@ func main() {
|
||||
commands.Register("follow", middlewareLoggedIn(handlers.FollowFeedHandler))
|
||||
commands.Register("unfollow", middlewareLoggedIn(handlers.UnfollowFeedHandler))
|
||||
commands.Register("following", middlewareLoggedIn(handlers.GetFeedFollowsByUserHandler))
|
||||
commands.Register("browse", middlewareLoggedIn(handlers.BrowseHandler))
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("usage: boot-dev-blog-aggregator <command> [args...]")
|
||||
|
||||
22
sql/queries/createPost.sql
Normal file
22
sql/queries/createPost.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- name: CreatePost :one
|
||||
INSERT INTO posts (id, created_at, updated_at, url, title, description, feed_id, published_at)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8
|
||||
) ON CONFLICT (url) DO UPDATE
|
||||
SET
|
||||
id = $1,
|
||||
created_at = $2,
|
||||
updated_at = $3,
|
||||
title = $5,
|
||||
description = $6,
|
||||
feed_id = $7,
|
||||
published_at = $8
|
||||
WHERE posts.url = $4
|
||||
RETURNING *;
|
||||
3
sql/queries/getPostsForUser.sql
Normal file
3
sql/queries/getPostsForUser.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- name: GetPostsForUser :many
|
||||
SELECT * FROM posts WHERE feed_id IN (SELECT feed_follows.feed_id FROM feed_follows WHERE feed_follows.user_id = $1)
|
||||
ORDER BY published_at DESC LIMIT $2;
|
||||
14
sql/schema/0005_posts.sql
Normal file
14
sql/schema/0005_posts.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE posts(
|
||||
id UUID PRIMARY KEY,
|
||||
created_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP not null,
|
||||
title VARCHAR(128) not null,
|
||||
url VARCHAR(512) not null UNIQUE,
|
||||
description TEXT not null,
|
||||
published_at TIMESTAMP not null ,
|
||||
feed_id UUID not null REFERENCES feeds(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE posts;
|
||||
Reference in New Issue
Block a user