major update
This commit is contained in:
parent
a0403539ae
commit
7fd9d17f40
10
Makefile
10
Makefile
|
@ -3,6 +3,9 @@
|
|||
# Variables
|
||||
export PATH := $(shell pwd)/node_modules/.bin:$(shell pwd)/bin:$(PATH)
|
||||
export GOBIN := $(shell pwd)/bin
|
||||
export GOOSE_DRIVER=sqlite3
|
||||
export GOOSE_DBSTRING=./data.db
|
||||
export GOOSE_MIGRATION_DIR=db/migrations
|
||||
|
||||
start: dev-dependencies
|
||||
@air
|
||||
|
@ -40,3 +43,10 @@ build: build-dependencies
|
|||
tailwindcss -i src/styles.css -o public/styles.css
|
||||
templ generate
|
||||
go build -o redmage
|
||||
|
||||
migrate-new:
|
||||
@read -p "Name new migration: " name
|
||||
if [[ $$name ]]; then goose create "$$name" sql; fi
|
||||
|
||||
migrate-redo:
|
||||
@goose redo
|
51
api/api.go
51
api/api.go
|
@ -1,12 +1,59 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/teivah/broadcast"
|
||||
"github.com/tigorlazuardi/redmage/db/queries"
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
Queries *queries.Queries
|
||||
DB *sql.DB
|
||||
queries *queries.Queries
|
||||
db *sql.DB
|
||||
|
||||
scheduler *cron.Cron
|
||||
scheduleMap map[cron.EntryID]queries.Subreddit
|
||||
|
||||
downloadBroadcast *broadcast.Relay[DownloadStatusMessage]
|
||||
}
|
||||
|
||||
func New(q *queries.Queries, db *sql.DB) *API {
|
||||
return &API{
|
||||
queries: q,
|
||||
db: db,
|
||||
scheduler: cron.New(),
|
||||
scheduleMap: make(map[cron.EntryID]queries.Subreddit, 8),
|
||||
downloadBroadcast: broadcast.NewRelay[DownloadStatusMessage](),
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) StartScheduler(ctx context.Context) error {
|
||||
subreddits, err := api.queries.SubredditsGetAll(ctx)
|
||||
if err != nil {
|
||||
return errs.Wrapw(err, "failed to get all subreddits")
|
||||
}
|
||||
|
||||
for _, subreddit := range subreddits {
|
||||
id, err := api.scheduler.AddFunc(subreddit.Schedule, func() {
|
||||
// TODO: Add download
|
||||
})
|
||||
if err != nil {
|
||||
log.
|
||||
New(ctx).
|
||||
Err(err).
|
||||
Error(
|
||||
fmt.Sprintf("failed to start scheduler for subreddit '%s'", subreddit.Name),
|
||||
"subreddit", subreddit,
|
||||
)
|
||||
continue
|
||||
}
|
||||
api.scheduleMap[id] = subreddit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
9
api/broadcast_download_message.go
Normal file
9
api/broadcast_download_message.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package api
|
||||
|
||||
import "github.com/tigorlazuardi/redmage/db/queries"
|
||||
|
||||
type DownloadStatusMessage struct {
|
||||
Data queries.Image
|
||||
Progress float64
|
||||
Subreddit string
|
||||
}
|
|
@ -20,7 +20,7 @@ type ListSubredditsResult struct {
|
|||
|
||||
func (api *API) ListSubreddits(ctx context.Context, arg ListSubredditsParams) (result ListSubredditsResult, err error) {
|
||||
if arg.Name != "" {
|
||||
result.Data, err = api.Queries.SubredditsSearch(ctx, queries.SubredditsSearchParams{
|
||||
result.Data, err = api.queries.SubredditsSearch(ctx, queries.SubredditsSearchParams{
|
||||
Name: "%" + arg.Name + "%",
|
||||
Limit: arg.Limit,
|
||||
Offset: arg.Offset,
|
||||
|
@ -28,7 +28,7 @@ func (api *API) ListSubreddits(ctx context.Context, arg ListSubredditsParams) (r
|
|||
if err != nil {
|
||||
return result, errs.Wrapw(err, "failed to search subreddit", "query", arg)
|
||||
}
|
||||
result.Total, err = api.Queries.SubredditsSearchCount(ctx, queries.SubredditsSearchCountParams{
|
||||
result.Total, err = api.queries.SubredditsSearchCount(ctx, queries.SubredditsSearchCountParams{
|
||||
Name: "%" + arg.Name + "%",
|
||||
Limit: arg.Limit,
|
||||
Offset: arg.Offset,
|
||||
|
@ -39,14 +39,14 @@ func (api *API) ListSubreddits(ctx context.Context, arg ListSubredditsParams) (r
|
|||
return result, err
|
||||
}
|
||||
|
||||
result.Data, err = api.Queries.SubredditsList(ctx, queries.SubredditsListParams{
|
||||
result.Data, err = api.queries.SubredditsList(ctx, queries.SubredditsListParams{
|
||||
Limit: arg.Limit,
|
||||
Offset: arg.Offset,
|
||||
})
|
||||
if err != nil {
|
||||
return result, errs.Wrapw(err, "failed to list subreddit", "query", arg)
|
||||
}
|
||||
result.Total, err = api.Queries.SubredditsListCount(ctx)
|
||||
result.Total, err = api.queries.SubredditsListCount(ctx)
|
||||
if err != nil {
|
||||
return result, errs.Wrapw(err, "failed to count subreddit list", "query", arg)
|
||||
}
|
||||
|
|
|
@ -28,10 +28,7 @@ var serveCmd = &cobra.Command{
|
|||
|
||||
queries := queries.New(db)
|
||||
|
||||
api := &api.API{
|
||||
Queries: queries,
|
||||
DB: db,
|
||||
}
|
||||
api := api.New(queries, db)
|
||||
|
||||
server := server.New(cfg, api, PublicDir)
|
||||
|
||||
|
|
|
@ -8,6 +8,13 @@ CREATE TABLE subreddits (
|
|||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits (name);
|
||||
|
||||
CREATE TRIGGER update_subreddits_timestamp AFTER UPDATE ON subreddits FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE subreddits SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id;
|
||||
END;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits (name);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX idx_subreddits_name;
|
||||
-- +goose StatementEnd
|
32
db/migrations/20240409162347_create_images_table.sql
Normal file
32
db/migrations/20240409162347_create_images_table.sql
Normal file
|
@ -0,0 +1,32 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE images(
|
||||
id INTEGER PRIMARY KEY,
|
||||
subreddit_id INTEGER NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
post_id VARCHAR(50) NOT NULL,
|
||||
poster VARCHAR(50) NOT NULL,
|
||||
image_relative_path VARCHAR(255) NOT NULL,
|
||||
thumbnail_relative_path VARCHAR(255) NOT NULL DEFAULT '',
|
||||
image_original_url VARCHAR(255) NOT NULL,
|
||||
thumbnail_original_url VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT fk_subreddit_id
|
||||
FOREIGN KEY (subreddit_id)
|
||||
REFERENCES subreddits(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_images_timestamp AFTER UPDATE ON images FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE images SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id;
|
||||
END;
|
||||
|
||||
CREATE INDEX idx_subreddit_id ON images(subreddit_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS images;
|
||||
-- +goose StatementEnd
|
5
db/queries/images.sql
Normal file
5
db/queries/images.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
-- name: RecentlyAddedImages :many
|
||||
SELECT * FROM images
|
||||
WHERE created_at > ?
|
||||
ORDER BY created_at
|
||||
LIMIT ? OFFSET ?;
|
|
@ -1,3 +1,6 @@
|
|||
-- name: SubredditsGetAll :many
|
||||
SELECT * FROM subreddits;
|
||||
|
||||
-- name: SubredditsList :many
|
||||
SELECT * FROM subreddits
|
||||
ORDER BY name
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
templPkg
|
||||
go
|
||||
modd
|
||||
nodejs_21
|
||||
nodePackages_latest.nodejs
|
||||
goose
|
||||
sqlc
|
||||
air
|
||||
|
|
5
go.mod
5
go.mod
|
@ -28,14 +28,19 @@ require (
|
|||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.0 // indirect
|
||||
github.com/sethvargo/go-retry v0.2.4 // indirect
|
||||
github.com/teivah/broadcast v0.1.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -47,6 +47,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
|||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-co-op/gocron/v2 v2.2.9 h1:aoKosYWSSdXFLecjFWX1i8+R6V7XdZb8sB2ZKAY5Yis=
|
||||
github.com/go-co-op/gocron/v2 v2.2.9/go.mod h1:mZx3gMSlFnb97k3hRqX3+GdlG3+DUwTh6B8fnsTScXg=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
|
||||
|
@ -154,6 +156,10 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
|||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
|
||||
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
|
@ -175,6 +181,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/teivah/broadcast v0.1.0 h1:UMs1tn8w20Xlnod+VbLbwH3dzEH2zfJy4lxdzZjQLL0=
|
||||
github.com/teivah/broadcast v0.1.0/go.mod h1:mXEgvXdYz2xUkQFARxI+jyX1MfCBwMDiGjIKSAsEq1g=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898 h1:1MvEhzI5pvP27e9Dzz861mxk9WzXZLSJwzOU67cKTbU=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898/go.mod h1:9bKuHS7eZh/0mJndbUOrCx8Ej3PlsRDszj4L7oVYMPQ=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
|
@ -199,6 +207,8 @@ golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
|||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
|
|
|
@ -2,7 +2,7 @@ package components
|
|||
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
|
||||
templ Head(vc *views.Context) {
|
||||
templ Head(vc *views.Context, extras ...templ.Component) {
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="keywords" content="Reddit, Image, Downloader"/>
|
||||
|
@ -13,5 +13,12 @@ templ Head(vc *views.Context) {
|
|||
if vc.Config.Bool("http.hotreload") {
|
||||
<script src="/public/hot_reload.js"></script>
|
||||
}
|
||||
for _, extra := range extras {
|
||||
@extra
|
||||
}
|
||||
</head>
|
||||
}
|
||||
|
||||
templ HeadTitle(name string) {
|
||||
<title>{name}</title>
|
||||
}
|
|
@ -11,7 +11,7 @@ type Data struct {
|
|||
|
||||
templ Home(c *views.Context, data Data) {
|
||||
@components.Doctype() {
|
||||
@components.Head(c)
|
||||
@components.Head(c, components.HeadTitle("Redmage - Home"))
|
||||
@components.Body(c) {
|
||||
@components.Container() {
|
||||
if data.Error != nil {
|
||||
|
@ -26,6 +26,14 @@ templ Home(c *views.Context, data Data) {
|
|||
|
||||
templ home(c *views.Context, data Data) {
|
||||
<div class="prose">
|
||||
<section>
|
||||
<h1>Recently Added</h1>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Subreddits</h1>
|
||||
for _, subreddit := range data.SubredditsList.Data {
|
||||
<h3>{ subreddit.Name } - { subreddit.Schedule }</h3>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue