sqlite: date based columns migrated to int64
This commit is contained in:
parent
6c973cdf1f
commit
2b4df20754
13
Makefile
13
Makefile
|
@ -47,6 +47,16 @@ build-dependencies:
|
|||
echo "Dayjs Relative Time not found, installing it"
|
||||
curl -o public/dayjs-relativeTime-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/relativeTime.min.js
|
||||
fi
|
||||
@if [ ! -f "public/dayjs-utc-1.11.10.min.js" ]; then
|
||||
mkdir -p public
|
||||
echo "Dayjs UTC plugin not found, installing it"
|
||||
curl -o public/dayjs-utc-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/utc.min.js
|
||||
fi
|
||||
@if [ ! -f "public/dayjs-timezone-1.11.10.min.js" ]; then
|
||||
mkdir -p public
|
||||
echo "Dayjs Timezone plugin not found, installing it"
|
||||
curl -o public/dayjs-timezone-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/timezone.min.js
|
||||
fi
|
||||
@if [ ! -f "public/theme-change-2.0.2.min.js" ]; then
|
||||
mkdir -p public
|
||||
echo "Theme change not found, installing it"
|
||||
|
@ -71,5 +81,8 @@ migrate-new:
|
|||
migrate-redo:
|
||||
@goose redo
|
||||
|
||||
migrate-down:
|
||||
@goose down
|
||||
|
||||
migrate-up:
|
||||
@goose up
|
||||
|
|
|
@ -3,7 +3,9 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"github.com/tigorlazuardi/redmage/models"
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
|
@ -11,11 +13,27 @@ import (
|
|||
|
||||
type DeviceCreateParams = models.DeviceSetter
|
||||
|
||||
func (api *API) DevicesCreate(ctx context.Context, params *DeviceCreateParams) (*models.Device, error) {
|
||||
func (api *API) DevicesCreate(ctx context.Context, params *models.Device) (*models.Device, error) {
|
||||
ctx, span := tracer.Start(ctx, "*API.DevicesCreate")
|
||||
defer span.End()
|
||||
|
||||
device, err := models.Devices.Insert(ctx, api.db, params)
|
||||
now := time.Now()
|
||||
device, err := models.Devices.Insert(ctx, api.db, &models.DeviceSetter{
|
||||
Slug: omit.From(params.Slug),
|
||||
Name: omit.From(params.Name),
|
||||
ResolutionX: omit.From(params.ResolutionX),
|
||||
ResolutionY: omit.From(params.ResolutionY),
|
||||
AspectRatioTolerance: omit.From(params.AspectRatioTolerance),
|
||||
MinX: omit.From(params.MinX),
|
||||
MinY: omit.From(params.MinY),
|
||||
MaxX: omit.From(params.MaxX),
|
||||
MaxY: omit.From(params.MaxY),
|
||||
NSFW: omit.From(params.NSFW),
|
||||
WindowsWallpaperMode: omit.From(params.WindowsWallpaperMode),
|
||||
Enable: omit.From(params.Enable),
|
||||
CreatedAt: omit.From(now.Unix()),
|
||||
UpdatedAt: omit.From(now.Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
var sqliteErr sqlite3.Error
|
||||
if errors.As(err, &sqliteErr) {
|
||||
|
|
|
@ -186,6 +186,7 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
|
|||
}
|
||||
|
||||
var many []*models.ImageSetter
|
||||
now := time.Now()
|
||||
for _, device := range devices {
|
||||
var nsfw int32
|
||||
if post.IsNSFW() {
|
||||
|
@ -197,7 +198,7 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
|
|||
Title: omit.From(post.GetTitle()),
|
||||
PostID: omit.From(post.GetID()),
|
||||
PostURL: omit.From(post.GetPostURL()),
|
||||
PostCreated: omit.From(post.GetCreated().Format(time.RFC3339)),
|
||||
PostCreated: omit.From(post.GetCreated().Unix()),
|
||||
PostName: omit.From(post.GetName()),
|
||||
Poster: omit.From(post.GetAuthor()),
|
||||
PosterURL: omit.From(post.GetAuthorURL()),
|
||||
|
@ -206,6 +207,8 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
|
|||
ImageOriginalURL: omit.From(post.GetImageURL()),
|
||||
ThumbnailOriginalURL: omit.From(post.GetThumbnailURL()),
|
||||
NSFW: omit.From(nsfw),
|
||||
CreatedAt: omit.From(now.Unix()),
|
||||
UpdatedAt: omit.From(now.Unix()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,9 @@ func (ilp *ImageListParams) FillFromQuery(query Queryable) {
|
|||
createdAtint, _ := strconv.ParseInt(query.Get("created_at"), 10, 64)
|
||||
if createdAtint > 0 {
|
||||
ilp.CreatedAt = time.Unix(createdAtint, 0)
|
||||
} else if createdAtint < 0 {
|
||||
// Negative value means relative to now.
|
||||
ilp.CreatedAt = time.Now().Add(time.Duration(createdAtint) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +79,7 @@ func (ilp ImageListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
|
|||
}
|
||||
|
||||
if !ilp.CreatedAt.IsZero() {
|
||||
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(ilp.CreatedAt.Format(time.RFC3339)))
|
||||
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(ilp.CreatedAt.Unix()))
|
||||
}
|
||||
|
||||
return expr
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
|
@ -15,12 +16,15 @@ func (api *API) SubredditsCreate(ctx context.Context, params *models.Subreddit)
|
|||
ctx, span := tracer.Start(ctx, "*API.SubredditsCreate")
|
||||
defer span.End()
|
||||
|
||||
now := time.Now()
|
||||
set := &models.SubredditSetter{
|
||||
Name: omit.From(params.Name),
|
||||
EnableSchedule: omit.From(params.EnableSchedule),
|
||||
Subtype: omit.From(params.Subtype),
|
||||
Schedule: omit.From(params.Schedule),
|
||||
Countback: omit.From(params.Countback),
|
||||
CreatedAt: omit.From(now.Unix()),
|
||||
UpdatedAt: omit.From(now.Unix()),
|
||||
}
|
||||
|
||||
subreddit, err = models.Subreddits.Insert(ctx, api.db, set)
|
||||
|
|
|
@ -5,10 +5,10 @@ CREATE TABLE subreddits (
|
|||
name VARCHAR(30) NOT NULL,
|
||||
enable_schedule INT NOT NULL DEFAULT 1,
|
||||
subtype INT NOT NULL DEFAULT 0,
|
||||
schedule VARCHAR(20) NOT NULL DEFAULT '0 0 * * *',
|
||||
schedule VARCHAR(20) NOT NULL DEFAULT '@daily',
|
||||
countback INT NOT NULL DEFAULT 100,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
created_at BIGINT DEFAULT 0 NOT NULL,
|
||||
updated_at BIGINT DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits (name);
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
INSERT INTO subreddits (name, subtype, schedule) VALUES
|
||||
('wallpaper', 0, '0 0 * * *'), -- every day at midnight
|
||||
('wallpapers', 0, '0 0 * * *'); -- every day at midnight
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DELETE FROM subreddits WHERE name IN ('wallpaper', 'wallpapers');
|
||||
-- +goose StatementEnd
|
|
@ -14,18 +14,13 @@ CREATE TABLE devices(
|
|||
max_y INTEGER NOT NULL DEFAULT 0,
|
||||
nsfw INTEGER NOT NULL DEFAULT 0,
|
||||
windows_wallpaper_mode INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
created_at BIGINT DEFAULT 0 NOT NULL,
|
||||
updated_at BIGINT DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_devices_name ON devices(slug);
|
||||
|
||||
CREATE INDEX idx_devices_enable ON devices(enable);
|
||||
|
||||
CREATE TRIGGER update_devices_timestamp AFTER UPDATE ON devices FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE devices SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id;
|
||||
END;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
|
|
|
@ -7,7 +7,7 @@ CREATE TABLE images(
|
|||
title VARCHAR(255) NOT NULL,
|
||||
post_id VARCHAR(50) NOT NULL,
|
||||
post_url VARCHAR(255) NOT NULL,
|
||||
post_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
post_created BIGINT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
post_name VARCHAR(255) NOT NULL,
|
||||
poster VARCHAR(50) NOT NULL,
|
||||
poster_url VARCHAR(255) NOT NULL,
|
||||
|
@ -16,8 +16,8 @@ CREATE TABLE images(
|
|||
image_original_url VARCHAR(255) NOT NULL,
|
||||
thumbnail_original_url VARCHAR(255) NOT NULL DEFAULT '',
|
||||
nsfw INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
created_at BIGINT DEFAULT 0 NOT NULL,
|
||||
updated_at BIGINT DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT fk_subreddit_id
|
||||
FOREIGN KEY (subreddit_id)
|
||||
REFERENCES subreddits(id)
|
||||
|
@ -28,18 +28,10 @@ CREATE TABLE images(
|
|||
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 TRIGGER update_subreddits_timestamp_on_insert AFTER INSERT ON images FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE subreddits SET updated_at = CURRENT_TIMESTAMP WHERE id = new.subreddit_id;
|
||||
END;
|
||||
|
||||
CREATE INDEX idx_subreddit_id ON images(subreddit_id);
|
||||
CREATE INDEX idx_nsfw ON images(nsfw);
|
||||
CREATE INDEX idx_subreddit_id_images ON images(subreddit_id);
|
||||
CREATE INDEX idx_nsfw_images ON images(nsfw);
|
||||
CREATE INDEX idx_images_created_at ON images(created_at DESC);
|
||||
CREATE INDEX idx_unique_device_images_id ON images(device_id, post_id DESC);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
|
|
|
@ -4,13 +4,13 @@ Content-Type: application/json
|
|||
Content-Length: 211
|
||||
|
||||
{
|
||||
"name": "S20FE",
|
||||
"slug": "s20fe",
|
||||
"resolution_x": 1080,
|
||||
"resolution_y": 2400,
|
||||
"name": "Laptop",
|
||||
"slug": "laptop",
|
||||
"resolution_x": 2560,
|
||||
"resolution_y": 1440,
|
||||
"nsfw": 1,
|
||||
"aspect_ratio_tolerance": 0.2,
|
||||
"enable": 1,
|
||||
"min_x": 1080,
|
||||
"min_y": 2400
|
||||
"min_x": 2560,
|
||||
"min_y": 1440
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ POST http://localhost:8080/api/v1/subreddits HTTP/1.1
|
|||
Host: localhost:8080
|
||||
|
||||
{
|
||||
"name": "AnimeLandscapes",
|
||||
"name": "wallpapers",
|
||||
"enable_schedule": 1,
|
||||
"schedule": "@daily",
|
||||
"countback": 300
|
||||
|
|
|
@ -3,5 +3,5 @@ Host: localhost:8080
|
|||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"subreddit": "AnimeLandscapes"
|
||||
"subreddit": "wallpaper"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/tigorlazuardi/redmage/models"
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||
|
@ -19,7 +18,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
|||
ctx, span := tracer.Start(r.Context(), "*Routes.APIDeviceCreate")
|
||||
defer func() { telemetry.EndWithStatus(span, err) }()
|
||||
|
||||
var body models.Device
|
||||
var body *models.Device
|
||||
|
||||
if err = json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to decode json body")
|
||||
|
@ -34,20 +33,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
device, err := routes.API.DevicesCreate(ctx, &models.DeviceSetter{
|
||||
Slug: omit.From(body.Slug),
|
||||
Name: omit.From(body.Name),
|
||||
ResolutionX: omit.From(body.ResolutionX),
|
||||
ResolutionY: omit.From(body.ResolutionY),
|
||||
AspectRatioTolerance: omit.From(body.AspectRatioTolerance),
|
||||
MinX: omit.From(body.MinX),
|
||||
MinY: omit.From(body.MinY),
|
||||
MaxX: omit.From(body.MaxX),
|
||||
MaxY: omit.From(body.MaxY),
|
||||
NSFW: omit.From(body.NSFW),
|
||||
WindowsWallpaperMode: omit.From(body.WindowsWallpaperMode),
|
||||
Enable: omit.From(body.Enable),
|
||||
})
|
||||
device, err := routes.API.DevicesCreate(ctx, body)
|
||||
if err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to create device", "body", body)
|
||||
code, message := errs.HTTPMessage(err)
|
||||
|
@ -64,7 +50,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var slugRegex = regexp.MustCompile(`^[a-z0-9-]+$`)
|
||||
|
||||
func validateCreateDevice(params models.Device) error {
|
||||
func validateCreateDevice(params *models.Device) error {
|
||||
if params.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -59,6 +60,7 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) {
|
|||
MaxY: omit.FromPtr(body.MaxY),
|
||||
NSFW: omit.FromPtr(body.NSFW),
|
||||
WindowsWallpaperMode: omit.FromPtr(body.WindowsWallpaperMode),
|
||||
UpdatedAt: omit.From(time.Now().Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
code, message := errs.HTTPMessage(err)
|
||||
|
|
|
@ -31,7 +31,9 @@ func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
imageListParams := api.ImageListParams{}
|
||||
imageListParams.FillFromQuery(r.URL.Query())
|
||||
imageListParams.CreatedAt = time.Now().Add(-time.Hour * 24) // images in the last 24 hours
|
||||
if imageListParams.CreatedAt.IsZero() {
|
||||
imageListParams.CreatedAt = time.Now().Add(-time.Hour * 24) // images in the last 24 hours
|
||||
}
|
||||
imageListParams.Limit = 0
|
||||
|
||||
imageList, err := routes.API.ImagesListWithDevicesAndSubreddits(ctx, imageListParams)
|
||||
|
@ -49,6 +51,8 @@ func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) {
|
|||
data := homeview.Data{
|
||||
SubredditsList: list,
|
||||
RecentlyAddedImages: homeview.NewRecentlyAddedImages(imageList.Images),
|
||||
Now: time.Now(),
|
||||
TotalImages: imageList.Total,
|
||||
}
|
||||
|
||||
if err := homeview.Home(vc, data).Render(ctx, rw); err != nil {
|
||||
|
|
|
@ -12,7 +12,13 @@ templ Head(vc *views.Context, extras ...templ.Component) {
|
|||
<script src="/public/htmx-response-targets-1.9.11.min.js"></script>
|
||||
<script src="/public/dayjs-1.11.10.min.js"></script>
|
||||
<script src="/public/dayjs-relativeTime-1.11.10.min.js"></script>
|
||||
<script>dayjs.extend(window.dayjs_plugin_relativeTime)</script>
|
||||
<script src="/public/dayjs-utc-1.11.10.min.js"></script>
|
||||
<script src="/public/dayjs-timezone-1.11.10.min.js"></script>
|
||||
<script>
|
||||
dayjs.extend(window.dayjs_plugin_relativeTime)
|
||||
dayjs.extend(window.dayjs_plugin_utc)
|
||||
dayjs.extend(window.dayjs_plugin_timezone)
|
||||
</script>
|
||||
<script src="/public/theme-change-2.0.2.min.js"></script>
|
||||
if vc.Config.Bool("http.hotreload") {
|
||||
<script src="/public/hot_reload.js"></script>
|
||||
|
|
|
@ -88,20 +88,7 @@ templ Navbar(c *views.Context) {
|
|||
</div>
|
||||
<div class="divider"></div>
|
||||
<nav class="pt-4">
|
||||
<ul class="flex flex-col flex-wrap">
|
||||
<a href="/" class={ classForNavItem(c, "/") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Home</li>
|
||||
</a>
|
||||
<a href="/about" class={ classForNavItem(c, "/about") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">About</li>
|
||||
</a>
|
||||
<a href="/config" class={ classForNavItem(c, "/config") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Config</li>
|
||||
</a>
|
||||
<a href="/browse" class={ classForNavItem(c, "/browse") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Browse</li>
|
||||
</a>
|
||||
</ul>
|
||||
@navList(c)
|
||||
</nav>
|
||||
<div class="flex-grow"></div>
|
||||
@SelectThemeInput()
|
||||
|
@ -109,6 +96,23 @@ templ Navbar(c *views.Context) {
|
|||
</header>
|
||||
}
|
||||
|
||||
templ navList(c *views.Context) {
|
||||
<ul class="flex flex-col flex-wrap">
|
||||
<a href="/" class={ classForNavItem(c, "/") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Home</li>
|
||||
</a>
|
||||
<a href="/about" class={ classForNavItem(c, "/about") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">About</li>
|
||||
</a>
|
||||
<a href="/config" class={ classForNavItem(c, "/config") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Config</li>
|
||||
</a>
|
||||
<a href="/subreddits" class={ classForNavItem(c, "/subreddits") }>
|
||||
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Subreddits</li>
|
||||
</a>
|
||||
</ul>
|
||||
}
|
||||
|
||||
templ SelectThemeInput() {
|
||||
<select class="select select-ghost select-bordered w-full" data-choose-theme>
|
||||
<option value="light">Light (Default)</option>
|
||||
|
|
|
@ -3,7 +3,7 @@ package homeview
|
|||
import "github.com/tigorlazuardi/redmage/views/components"
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||
import "time"
|
||||
import "strconv"
|
||||
|
||||
templ Home(c *views.Context, data Data) {
|
||||
@components.Doctype() {
|
||||
|
@ -17,18 +17,42 @@ templ Home(c *views.Context, data Data) {
|
|||
// HomeContent returns the main content of the home page.
|
||||
//
|
||||
// Use this template if request is HX-Boosted.
|
||||
templ HomeContent(_ *views.Context, data Data) {
|
||||
<main id="main" class="prose">
|
||||
templ HomeContent(c *views.Context, data Data) {
|
||||
<main id="main" class="prose min-w-full">
|
||||
@components.Container() {
|
||||
if data.Error != "" {
|
||||
@components.ErrorToast(data.Error)
|
||||
} else {
|
||||
<section class="mb-4 mx-auto">
|
||||
<h1>Recently Added</h1>
|
||||
<div class="flex content-center gap-8">
|
||||
<h1>
|
||||
Recently Added -
|
||||
{ strconv.FormatInt(data.TotalImages, 10) } Images
|
||||
</h1>
|
||||
<select
|
||||
hx-trigger="change"
|
||||
hx-get="/"
|
||||
name="created_at"
|
||||
hx-params="*"
|
||||
class="select select-ghost"
|
||||
hx-target="body"
|
||||
hx-push-url="true"
|
||||
>
|
||||
@recentlyRangeOption(c, "-10800", "3 Hours")
|
||||
@recentlyRangeOption(c, "-21600", "6 Hours")
|
||||
@recentlyRangeOption(c, "-43200", "12 Hours")
|
||||
@recentlyRangeOption(c, "-86400", "1 Day")
|
||||
@recentlyRangeOption(c, "-172800", "2 Days")
|
||||
@recentlyRangeOption(c, "-259200", "3 Days")
|
||||
@recentlyRangeOption(c, "-604800", "7 Days")
|
||||
@recentlyRangeOption(c, "-2592000", "30 Days")
|
||||
</select>
|
||||
</div>
|
||||
for _, recently := range data.RecentlyAddedImages {
|
||||
<h2>{ recently.Device.Name }</h2>
|
||||
<div class="divider"></div>
|
||||
<h2 class="mt-4">{ recently.Device.Name }</h2>
|
||||
for _, subreddit := range recently.Subreddits {
|
||||
<h4>{ subreddit.Subreddit.Name }</h4>
|
||||
<h4>{ subreddit.Subreddit.Name } - { strconv.Itoa(len(subreddit.Images)) } images</h4>
|
||||
@RecentlyAddedImageList(subreddit.Images, 0)
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +62,7 @@ templ HomeContent(_ *views.Context, data Data) {
|
|||
for _, subreddit := range data.SubredditsList.Data {
|
||||
<h3>
|
||||
{ subreddit.Name } -
|
||||
@utils.RelativeTimeNode(subreddit.Name, utils.NextScheduleTime(subreddit.Schedule).Format(time.RFC3339))
|
||||
@utils.RelativeTimeNode(subreddit.Name, utils.NextScheduleTime(subreddit.Schedule).Unix())
|
||||
</h3>
|
||||
}
|
||||
</section>
|
||||
|
@ -46,3 +70,13 @@ templ HomeContent(_ *views.Context, data Data) {
|
|||
}
|
||||
</main>
|
||||
}
|
||||
|
||||
templ recentlyRangeOption(c *views.Context, value, text string) {
|
||||
if c.Request.URL.Query().Get("created_at") == "" && value == "-86400" {
|
||||
<option selected="selected" value={ value }>{ text }</option>
|
||||
} else if c.Request.URL.Query().Get("created_at") == value {
|
||||
<option selected="selected" value={ value }>{ text }</option>
|
||||
} else {
|
||||
<option value={ value }>{ text }</option>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package homeview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tigorlazuardi/redmage/api"
|
||||
"github.com/tigorlazuardi/redmage/models"
|
||||
|
@ -11,16 +13,13 @@ import (
|
|||
type Data struct {
|
||||
SubredditsList api.ListSubredditsResult
|
||||
RecentlyAddedImages RecentlyAddedImages
|
||||
TotalImages int64
|
||||
Error string
|
||||
Now time.Time
|
||||
}
|
||||
|
||||
type RecentlyAddedImages = []RecentlyAddedImage
|
||||
|
||||
type subredditMapValue struct {
|
||||
subreddit *models.Subreddit
|
||||
images []*models.Image
|
||||
}
|
||||
|
||||
type RecentlyAddedImage struct {
|
||||
Device *models.Device
|
||||
Subreddits []Subreddit
|
||||
|
@ -33,6 +32,9 @@ type Subreddit struct {
|
|||
|
||||
func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
|
||||
r := make(RecentlyAddedImages, 0, len(images))
|
||||
|
||||
var count int
|
||||
|
||||
for _, image := range images {
|
||||
if image.R.Device == nil || image.R.Subreddit == nil {
|
||||
continue
|
||||
|
@ -46,6 +48,7 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
|
|||
if subreddit.Subreddit.ID == image.R.Subreddit.ID {
|
||||
subredditFound = true
|
||||
r[i].Subreddits[j].Images = append(r[i].Subreddits[j].Images, image)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if !subredditFound {
|
||||
|
@ -53,10 +56,12 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
|
|||
Subreddit: image.R.Subreddit,
|
||||
Images: models.ImageSlice{image},
|
||||
})
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
if !deviceFound {
|
||||
count++
|
||||
r = append(r, RecentlyAddedImage{
|
||||
Device: image.R.Device,
|
||||
Subreddits: []Subreddit{
|
||||
|
@ -83,5 +88,7 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
|
|||
return strings.Compare(leftName, rightName)
|
||||
})
|
||||
|
||||
fmt.Println("image count", count)
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
|
|||
<figure>
|
||||
<a
|
||||
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
class="object-contain w-[256px] h-[256px]"
|
||||
|
@ -57,7 +58,7 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
|
|||
}
|
||||
|
||||
templ RecentlyAddedImageList(images models.ImageSlice, opts ImageCardOption) {
|
||||
<div class="w-[80vw] overflow-x-scroll flex gap-4 p-8 shadow-inner bg-base-300">
|
||||
<div class="overflow-x-auto flex gap-4 p-6 shadow-inner bg-base-300 rounded-2xl w-[85vw] md:w-full">
|
||||
for _, data := range images {
|
||||
@RecentlyAddedImageCard(data, opts)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package utils
|
||||
|
||||
import "strings"
|
||||
import "strconv"
|
||||
|
||||
// RelativeTimeText updates the text content of the element to be a relative time text.
|
||||
//
|
||||
// Every second it updates the text content to be the relative time text of the input string.
|
||||
|
||||
script RelativeFromTimeText(id string, time string, inter int) {
|
||||
script RelativeFromTimeText(id string, time int64, inter int) {
|
||||
const el = document.getElementById(id)
|
||||
el.parentNode.dataset.tip = dayjs.unix(time).tz(dayjs.tz.guess()).format('ddd, D MMM YYYY HH:mm:ss Z')
|
||||
|
||||
const timeText = dayjs(time).fromNow()
|
||||
const timeText = dayjs.unix(time).fromNow()
|
||||
el.textContent = timeText
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const timeText = dayjs(time).fromNow()
|
||||
const timeText = dayjs.unix(time).fromNow()
|
||||
el.textContent = timeText
|
||||
}, inter)
|
||||
|
||||
|
@ -31,43 +33,12 @@ script RelativeFromTimeText(id string, time string, inter int) {
|
|||
obs.observe(el.parentNode, { childList: true })
|
||||
}
|
||||
|
||||
script RelativeTimeToNowText(id, time string, inter int) {
|
||||
const el = document.getElementById(id)
|
||||
|
||||
const timeText = dayjs(time).toNow()
|
||||
el.textContent = timeText
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const timeText = dayjs(time).toNow()
|
||||
el.textContent = timeText
|
||||
}, 1000)
|
||||
|
||||
const obs = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
for (const removed of mutation.removedNodes) {
|
||||
if (el === removed) {
|
||||
clearInterval(interval)
|
||||
obs.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
obs.observe(el.parentNode, { childList: true })
|
||||
}
|
||||
|
||||
templ RelativeTimeNode(id string, time string, class ...string) {
|
||||
<span
|
||||
id={ id }
|
||||
class={ strings.Join(class, " ") }
|
||||
>{ time }</span>
|
||||
templ RelativeTimeNode(id string, time int64, class ...string) {
|
||||
<div class="tooltip z-10" data-tip={ strconv.FormatInt(time, 10) }>
|
||||
<span
|
||||
id={ id }
|
||||
class={ strings.Join(class, " ") }
|
||||
>{ strconv.FormatInt(time, 10) }</span>
|
||||
</div>
|
||||
@RelativeFromTimeText(id, time, 10000)
|
||||
}
|
||||
|
||||
templ RelativeTimeToNowNode(id, time string, class ...string) {
|
||||
<span
|
||||
id={ id }
|
||||
class={ strings.Join(class, " ") }
|
||||
>{ time }</span>
|
||||
@RelativeTimeToNowText(id, time, 10000)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue