#13 - browse articles

This commit is contained in:
2025-03-11 16:08:34 +02:00
parent f561eb4c34
commit 80602bf04f
10 changed files with 265 additions and 1 deletions

View 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
}

View 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
}

View File

@@ -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

View 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
}

View File

@@ -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

View 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)
}

View File

@@ -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...]")

View 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 *;

View 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
View 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;