view: implemented subreddits list page

This commit is contained in:
Tigor Hutasuhut 2024-05-01 18:16:54 +07:00
parent 4ca2e854d4
commit e3bda647f3
8 changed files with 152 additions and 24 deletions

View file

@ -2,6 +2,8 @@ package api
import (
"context"
"strconv"
"strings"
"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
@ -11,16 +13,34 @@ import (
)
type ListSubredditsParams struct {
Name string `json:"name"`
Limit int64 `json:"limit"`
Offset int64 `json:"offset"`
OrderBy string `json:"order_by"`
Sort string `json:"sort"`
Q string
All bool
Limit int64
Offset int64
OrderBy string
Sort string
}
func (l *ListSubredditsParams) FillFromQuery(q Queryable) {
l.Q = q.Get("q")
l.All, _ = strconv.ParseBool(q.Get("all"))
l.Limit, _ = strconv.ParseInt(q.Get("limit"), 10, 64)
if l.Limit < 0 {
l.Limit = 25
} else if l.Limit > 100 {
l.Limit = 100
}
l.Offset, _ = strconv.ParseInt(q.Get("offset"), 10, 64)
if l.Offset < 0 {
l.Offset = 0
}
l.OrderBy = q.Get("order_by")
l.Sort = strings.ToLower(q.Get("sort"))
}
func (l ListSubredditsParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
if l.Name != "" {
expr = append(expr, models.SelectWhere.Subreddits.Name.Like("%"+l.Name+"%"))
if l.Q != "" {
expr = append(expr, models.SelectWhere.Subreddits.Name.Like("%"+l.Q+"%"))
}
if l.Limit > 0 {
expr = append(expr, sm.Limit(l.Limit))
@ -34,14 +54,16 @@ func (l ListSubredditsParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
} else {
expr = append(expr, sm.OrderBy(l.OrderBy).Asc())
}
} else {
expr = append(expr, sm.OrderBy(models.SubredditColumns.Name).Asc())
}
return expr
}
func (l ListSubredditsParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
if l.Name != "" {
expr = append(expr, models.SelectWhere.Subreddits.Name.Like("%"+l.Name+"%"))
if l.Q != "" {
expr = append(expr, models.SelectWhere.Subreddits.Name.Like("%"+l.Q+"%"))
}
return expr
@ -68,3 +90,33 @@ func (api *API) ListSubreddits(ctx context.Context, arg ListSubredditsParams) (r
return result, nil
}
// ListSubredditsWithCover returns list of subreddits with cover image.
//
// Image Relationship `R` struct will be nil if there is no cover image.
func (api *API) ListSubredditsWithCover(ctx context.Context, arg ListSubredditsParams) (result ListSubredditsResult, err error) {
ctx, span := tracer.Start(ctx, "api.ListSubredditsWithCover")
defer span.End()
result, err = api.ListSubreddits(ctx, arg)
if err != nil {
return result, errs.Wrapw(err, "failed to list subreddits with cover")
}
// Cannot do batch query because we cannot use GROUP BY with ORDER BY in consistent manner.
//
// The problem gets worse when using custom ORDER BY from client.
//
// For consistency, we query images one by one.
//
// Subreddit list is expected to be small, so this should be fine since SQLITE has no network latency.
for _, subreddit := range result.Data {
subreddit.R.Images, err = models.Images.Query(ctx, api.db,
models.SelectWhere.Images.Subreddit.EQ(subreddit.Name),
sm.Limit(1),
sm.OrderBy(models.ImageColumns.CreatedAt).Desc(),
).All()
}
return result, nil
}

View file

@ -0,0 +1,15 @@
POST http://localhost:8080/api/v1/devices HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"name": "Phone",
"slug": "phone",
"resolution_x": 1080,
"resolution_y": 2400,
"nsfw": 1,
"aspect_ratio_tolerance": 0.2,
"enable": 1,
"min_x": 1080,
"min_y": 2400
}

View file

@ -3,6 +3,8 @@ package routes
import (
"net/http"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/pkg/errs"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/views"
"github.com/tigorlazuardi/redmage/views/subredditsview"
@ -12,12 +14,26 @@ func (routes *Routes) PageSubreddits(rw http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "*Routes.PageSubreddits")
defer span.End()
data := subredditsview.Data{
// Subreddits: routes.API.SubredditsList(ctx),
}
c := views.NewContext(routes.Config, r)
var params api.ListSubredditsParams
params.FillFromQuery(r.URL.Query())
var data subredditsview.Data
var err error
data.Subreddits, err = routes.API.ListSubredditsWithCover(ctx, params)
if err != nil {
log.New(ctx).Err(err).Error("failed to list subreddits")
code, message := errs.HTTPMessage(err)
rw.WriteHeader(code)
data.Error = message
if err := subredditsview.Subreddit(c, data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render subreddits")
}
return
}
if err := subredditsview.Subreddit(c, data).Render(r.Context(), rw); err != nil {
log.New(ctx).Err(err).Error("failed to render subreddits view")
rw.WriteHeader(http.StatusInternalServerError)

View file

@ -26,7 +26,7 @@ func (r *Routes) SubredditsListAPI(rw http.ResponseWriter, req *http.Request) {
}
func parseSubredditListQuery(req *http.Request) (params api.ListSubredditsParams) {
params.Name = req.FormValue("name")
params.Q = req.FormValue("name")
params.Limit, _ = strconv.ParseInt(req.FormValue("limit"), 10, 64)
if params.Limit < 1 {
params.Limit = 10

View file

@ -87,7 +87,7 @@ templ Navbar(c *views.Context) {
<span class="font-bold">Redmage</span>
</div>
<div class="divider"></div>
<nav class="pt-4" hx-boost="true" hx-select="main" hx-target="main">
<nav class="pt-4">
@navList(c)
</nav>
<div class="flex-grow"></div>

View file

@ -18,14 +18,14 @@ const (
)
templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl min-w-[256px] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all">
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl min-w-[16rem] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all">
<figure>
<a
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
target="_blank"
>
<img
class="object-contain max-w-[256px] max-h-[256px]"
class="object-contain max-w-[16rem] max-h-[16rem]"
src={ fmt.Sprintf("/img/%s", data.ThumbnailRelativePath) }
alt={ data.PostTitle }
/>

View file

@ -2,6 +2,9 @@ package subredditsview
import "github.com/tigorlazuardi/redmage/views"
import "github.com/tigorlazuardi/redmage/views/components"
import "github.com/tigorlazuardi/redmage/models"
import "strconv"
import "fmt"
templ Subreddit(c *views.Context, data Data) {
@components.Doctype() {
@ -17,13 +20,52 @@ templ SubredditContent(c *views.Context, data Data) {
@components.Container() {
<h1>Subreddits</h1>
<div class="divider"></div>
if len(data.Subreddits) == 0 {
<h3>You have not added any subreddits yet.</h3>
if data.Subreddits.Total == 0 {
<h3>No Subreddits Found</h3>
<p>Click <a class="text-primary" href="/subreddits/add">here</a> to add a new subreddit.</p>
} else {
<h2>{ strconv.FormatInt(data.Subreddits.Total, 10) } Subreddits Registered</h2>
}
for _, subreddit := range data.Subreddits {
<p>{ subreddit.Name }</p>
<div class="flex flex-wrap gap-1">
for _, subreddit := range data.Subreddits.Data {
@SubredditCard(c, subreddit)
}
</div>
}
</main>
}
templ SubredditCard(c *views.Context, data *models.Subreddit) {
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl w-80 top-0 hover:-top-1 transition-all rounded-none">
if len(data.R.Images) > 0 {
<figure class="p-8">
<a class="flex content-center" href={ templ.URL(fmt.Sprintf("/subreddits/details/%s", data.Name)) }>
<img
class="object-contain max-w-[16rem] max-h-[16rem]"
src={ fmt.Sprintf("/img/%s", data.R.Images[0].ThumbnailRelativePath) }
alt={ data.Name }
/>
</a>
</figure>
} else {
<figure class="p-8">
<a href={ templ.URL(fmt.Sprintf("/subreddits/details/%s", data.Name)) }>
@imagePlaceholder()
</a>
</figure>
}
<div class="card-body">
<div class="flex-1"></div>
<a href={ templ.URL(fmt.Sprintf("/subreddits/details/%s", data.Name)) }>
<p class="text-center my-4 underline text-primary">{ data.Name }</p>
</a>
</div>
</div>
}
templ imagePlaceholder() {
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="252" height="252" style="fill:#DEDEDE;stroke:#555555;stroke-width:2"></rect>
<text x="50%" y="50%" font-size="18" text-anchor="middle" alignment-baseline="middle" font-family="monospace, sans-serif" fill="#555555">256×256</text>
</svg>
}

View file

@ -1,7 +1,10 @@
package subredditsview
import "github.com/tigorlazuardi/redmage/models"
import (
"github.com/tigorlazuardi/redmage/api"
)
type Data struct {
Subreddits models.SubredditSlice
Subreddits api.ListSubredditsResult
Error string
}