Styled the post list and added updating of posts
This commit is contained in:
parent
e77cac2fd5
commit
aef3bb6a1e
|
|
@ -1,6 +1,6 @@
|
||||||
root = "."
|
root = "."
|
||||||
testdata_dir = "testdata"
|
testdata_dir = "testdata"
|
||||||
tmp_dir = "tmp"
|
tmp_dir = "build/tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = []
|
args_bin = []
|
||||||
|
|
@ -14,7 +14,7 @@ tmp_dir = "tmp"
|
||||||
follow_symlink = false
|
follow_symlink = false
|
||||||
full_bin = ""
|
full_bin = ""
|
||||||
include_dir = []
|
include_dir = []
|
||||||
include_ext = ["go", "tpl", "tmpl", "html", "css", "js"]
|
include_ext = ["go", "tpl", "tmpl", "html", "css", "scss", "js"]
|
||||||
include_file = []
|
include_file = []
|
||||||
kill_delay = "0s"
|
kill_delay = "0s"
|
||||||
log = "build-errors.log"
|
log = "build-errors.log"
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -13,7 +13,7 @@ clean:
|
||||||
.Phony: frontend
|
.Phony: frontend
|
||||||
frontend:
|
frontend:
|
||||||
npm install
|
npm install
|
||||||
npx esbuild --bundle ./assets/css/main.css --outfile=./static/assets/main.css
|
node esbuild.mjs
|
||||||
|
|
||||||
.Phony: gen
|
.Phony: gen
|
||||||
gen:
|
gen:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ title: First Post
|
||||||
date: 2026-02-18T11:17:00Z
|
date: 2026-02-18T11:17:00Z
|
||||||
tags: []
|
tags: []
|
||||||
slug: /2026/02/18/first-post
|
slug: /2026/02/18/first-post
|
||||||
|
|
||||||
---
|
---
|
||||||
Hello World!
|
Hello World!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ title: About a DB
|
||||||
date: 2026-02-19T11:17:00Z
|
date: 2026-02-19T11:17:00Z
|
||||||
tags: []
|
tags: []
|
||||||
slug: /2026/02/19/about-a-db
|
slug: /2026/02/19/about-a-db
|
||||||
|
|
||||||
---
|
---
|
||||||
Hello again.
|
Hello again.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ date: 2026-02-20T22:48:58Z
|
||||||
tags: []
|
tags: []
|
||||||
slug: /2026/02/21/first-attempt-at
|
slug: /2026/02/21/first-attempt-at
|
||||||
---
|
---
|
||||||
If you're seeing this, then posting from the UI works.
|
If you're seeing this, then posting from the UI works.
|
||||||
|
|
||||||
The UI is using service-side HTTP rendering, so there's nothing fancy going on here. I've chosen to use Boostrap for the frontend. It's a little traditional but hey, it looks good. I can't post a screenshot of the UI as Weiro doesn't have attachments yet, but I will once I have attachments working.
|
The UI is using service-side HTTP rendering, so there's nothing fancy going on here. I've chosen to use Boostrap for the frontend. It's a little traditional but hey, it looks good. I can't post a screenshot of the UI as Weiro doesn't have attachments yet, but I will once I have attachments working.
|
||||||
|
|
||||||
The new post should have been saved in the Sqlite3 database, which should trigger a rebuild of the site and a publish to Netlify. The existing posts should still be there too. At the moment, the publishing is done inline, which won't be the case for long, but I want to make sure the publishing flow is working properly.
|
The new post should have been saved in the Sqlite3 database, which should trigger a rebuild of the site and a publish to Netlify. The existing posts should still be there too. At the moment, the publishing is done inline, which won't be the case for long, but I want to make sure the publishing flow is working properly.
|
||||||
|
|
||||||
Okay, here we go.
|
Okay, here we go.
|
||||||
|
|
@ -4,7 +4,6 @@ title: Direct Publish To Netlify
|
||||||
date: 2026-02-20T06:36:00Z
|
date: 2026-02-20T06:36:00Z
|
||||||
tags: []
|
tags: []
|
||||||
slug: /2026/02/20/netlify
|
slug: /2026/02/20/netlify
|
||||||
|
|
||||||
---
|
---
|
||||||
Just a quick one right now. Integrated the Netlify client allowing direct publish to Netlify.
|
Just a quick one right now. Integrated the Netlify client allowing direct publish to Netlify.
|
||||||
Previous attempts were using the Netlify CLI, but after learning that Go has a Netlify client,
|
Previous attempts were using the Netlify CLI, but after learning that Go has a Netlify client,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ title: Success!
|
||||||
date: 2026-02-20T22:59:18Z
|
date: 2026-02-20T22:59:18Z
|
||||||
tags: []
|
tags: []
|
||||||
slug: /2026/02/21/success
|
slug: /2026/02/21/success
|
||||||
|
|
||||||
---
|
---
|
||||||
Okay, publishing from the frontend works.
|
Okay, publishing from the frontend works.
|
||||||
|
|
||||||
|
|
|
||||||
10
_test-site/posts/2026/02/21-have-got-the.md
Normal file
10
_test-site/posts/2026/02/21-have-got-the.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
id: oV-tykrLgCoo
|
||||||
|
title: ""
|
||||||
|
date: 2026-02-21T23:08:52Z
|
||||||
|
tags: []
|
||||||
|
slug: /2026/02/22/have-got-the
|
||||||
|
---
|
||||||
|
Have got the post list looking decent. Although the edit links don't go anywhere.
|
||||||
|
|
||||||
|
But maybe if I try to update this post everything will work out okay.
|
||||||
8
_test-site/posts/2026/02/21-with-a-bit.md
Normal file
8
_test-site/posts/2026/02/21-with-a-bit.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
id: OlI4xZu7SSS2
|
||||||
|
title: ""
|
||||||
|
date: 2026-02-21T22:11:28Z
|
||||||
|
tags: []
|
||||||
|
slug: /2026/02/22/with-a-bit
|
||||||
|
---
|
||||||
|
With a bit of luck, this should only be published locally, and not to Netlify.
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
@import "bootstrap/dist/css/bootstrap.css";
|
// Bootstrap customizations
|
||||||
|
|
||||||
|
$container-max-widths: (
|
||||||
|
sm: 540px,
|
||||||
|
md: 720px,
|
||||||
|
lg: 960px,
|
||||||
|
xl: 960px,
|
||||||
|
xxl: 960px
|
||||||
|
);
|
||||||
|
|
||||||
|
@import "bootstrap/scss/bootstrap.scss";
|
||||||
|
|
||||||
|
// Local classes
|
||||||
|
|
||||||
.post-form {
|
.post-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
9
esbuild.mjs
Normal file
9
esbuild.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
import {sassPlugin} from 'esbuild-sass-plugin'
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ['./assets/css/main.scss'],
|
||||||
|
bundle: true,
|
||||||
|
plugins: [sassPlugin()],
|
||||||
|
outfile: './static/assets/main.css',
|
||||||
|
});
|
||||||
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"lmika.dev/lmika/weiro/models"
|
"lmika.dev/lmika/weiro/models"
|
||||||
|
|
@ -13,12 +14,43 @@ type PostsHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph PostsHandler) Index(c fiber.Ctx) error {
|
func (ph PostsHandler) Index(c fiber.Ctx) error {
|
||||||
return c.Render("posts/index", fiber.Map{})
|
posts, err := ph.PostService.ListPosts(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render("posts/index", fiber.Map{
|
||||||
|
"posts": posts,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph PostsHandler) New(c fiber.Ctx) error {
|
func (ph PostsHandler) New(c fiber.Ctx) error {
|
||||||
return c.Render("posts/new", fiber.Map{
|
p := models.Post{
|
||||||
"guid": models.NewNanoID(),
|
GUID: models.NewNanoID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render("posts/edit", fiber.Map{
|
||||||
|
"post": p,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph PostsHandler) Edit(c fiber.Ctx) error {
|
||||||
|
postIDStr := c.Params("postID")
|
||||||
|
if postIDStr == "" {
|
||||||
|
return fiber.ErrBadRequest
|
||||||
|
}
|
||||||
|
postID, err := strconv.ParseInt(postIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := ph.PostService.GetPost(c.Context(), postID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render("posts/edit", fiber.Map{
|
||||||
|
"post": post,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
24
main.go
24
main.go
|
|
@ -1,11 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"github.com/gofiber/fiber/v3/middleware/static"
|
"github.com/gofiber/fiber/v3/middleware/static"
|
||||||
"github.com/gofiber/template/html/v3"
|
fiber_html "github.com/gofiber/template/html/v3"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
"lmika.dev/lmika/weiro/handlers"
|
"lmika.dev/lmika/weiro/handlers"
|
||||||
"lmika.dev/lmika/weiro/handlers/middleware"
|
"lmika.dev/lmika/weiro/handlers/middleware"
|
||||||
"lmika.dev/lmika/weiro/providers/db"
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
|
|
@ -36,8 +41,22 @@ func main() {
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
fiberTemplate := fiber_html.New("./views", ".html")
|
||||||
|
fiberTemplate.Funcmap["markdown"] = func() func(s string) template.HTML {
|
||||||
|
mdParser := goldmark.New(
|
||||||
|
goldmark.WithExtensions(extension.GFM),
|
||||||
|
)
|
||||||
|
return func(s string) template.HTML {
|
||||||
|
var sb strings.Builder
|
||||||
|
if err := mdParser.Convert([]byte(s), &sb); err != nil {
|
||||||
|
return template.HTML("Markdown error: " + html.EscapeString(err.Error()))
|
||||||
|
}
|
||||||
|
return template.HTML(sb.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
Views: html.New("./views", ".html"),
|
Views: fiberTemplate,
|
||||||
ViewsLayout: "layouts/main",
|
ViewsLayout: "layouts/main",
|
||||||
PassLocalsToViews: true,
|
PassLocalsToViews: true,
|
||||||
})
|
})
|
||||||
|
|
@ -48,6 +67,7 @@ func main() {
|
||||||
|
|
||||||
siteGroup.Get("/posts", ph.Index)
|
siteGroup.Get("/posts", ph.Index)
|
||||||
siteGroup.Get("/posts/new", ph.New)
|
siteGroup.Get("/posts/new", ph.New)
|
||||||
|
siteGroup.Get("/posts/:postID/edit", ph.Edit)
|
||||||
siteGroup.Post("/posts", ph.Update)
|
siteGroup.Post("/posts", ph.Update)
|
||||||
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ type Site struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SitePublishTarget struct {
|
type SitePublishTarget struct {
|
||||||
ID int64
|
ID int64
|
||||||
SiteID int64
|
SiteID int64
|
||||||
|
Enabled bool
|
||||||
|
|
||||||
BaseURL string
|
BaseURL string
|
||||||
TargetType string
|
TargetType string
|
||||||
|
|
|
||||||
965
package-lock.json
generated
965
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,7 @@
|
||||||
"esbuild": "0.27.3"
|
"esbuild": "0.27.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8"
|
"bootstrap": "^5.3.8",
|
||||||
|
"esbuild-sass-plugin": "^3.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ type PublishTarget struct {
|
||||||
ID int64
|
ID int64
|
||||||
SiteID int64
|
SiteID int64
|
||||||
TargetType string
|
TargetType string
|
||||||
|
Enabled int64
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
TargetRef string
|
TargetRef string
|
||||||
TargetKey string
|
TargetKey string
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,26 @@ func (q *Queries) InsertPost(ctx context.Context, arg InsertPostParams) (int64,
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectPost = `-- name: SelectPost :one
|
||||||
|
SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE id = ? LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) SelectPost(ctx context.Context, id int64) (Post, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, selectPost, id)
|
||||||
|
var i Post
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Guid,
|
||||||
|
&i.Title,
|
||||||
|
&i.Body,
|
||||||
|
&i.Slug,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.PublishedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const selectPostByGUID = `-- name: SelectPostByGUID :one
|
const selectPostByGUID = `-- name: SelectPostByGUID :one
|
||||||
SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE guid = ? LIMIT 1
|
SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE guid = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,18 @@ const insertPublishTarget = `-- name: InsertPublishTarget :one
|
||||||
INSERT INTO publish_targets (
|
INSERT INTO publish_targets (
|
||||||
site_id,
|
site_id,
|
||||||
target_type,
|
target_type,
|
||||||
|
enabled,
|
||||||
base_url,
|
base_url,
|
||||||
target_ref,
|
target_ref,
|
||||||
target_key
|
target_key
|
||||||
) VALUES (?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertPublishTargetParams struct {
|
type InsertPublishTargetParams struct {
|
||||||
SiteID int64
|
SiteID int64
|
||||||
TargetType string
|
TargetType string
|
||||||
|
Enabled int64
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
TargetRef string
|
TargetRef string
|
||||||
TargetKey string
|
TargetKey string
|
||||||
|
|
@ -32,6 +34,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg
|
||||||
row := q.db.QueryRowContext(ctx, insertPublishTarget,
|
row := q.db.QueryRowContext(ctx, insertPublishTarget,
|
||||||
arg.SiteID,
|
arg.SiteID,
|
||||||
arg.TargetType,
|
arg.TargetType,
|
||||||
|
arg.Enabled,
|
||||||
arg.BaseUrl,
|
arg.BaseUrl,
|
||||||
arg.TargetRef,
|
arg.TargetRef,
|
||||||
arg.TargetKey,
|
arg.TargetKey,
|
||||||
|
|
@ -42,7 +45,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectPublishTargetsOfSite = `-- name: SelectPublishTargetsOfSite :many
|
const selectPublishTargetsOfSite = `-- name: SelectPublishTargetsOfSite :many
|
||||||
SELECT id, site_id, target_type, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ?
|
SELECT id, site_id, target_type, enabled, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) {
|
func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) {
|
||||||
|
|
@ -58,6 +61,7 @@ func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64)
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.SiteID,
|
&i.SiteID,
|
||||||
&i.TargetType,
|
&i.TargetType,
|
||||||
|
&i.Enabled,
|
||||||
&i.BaseUrl,
|
&i.BaseUrl,
|
||||||
&i.TargetRef,
|
&i.TargetRef,
|
||||||
&i.TargetKey,
|
&i.TargetKey,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,15 @@ func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64) ([]*mod
|
||||||
return posts, nil
|
return posts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Provider) SelectPost(ctx context.Context, postID int64) (*models.Post, error) {
|
||||||
|
row, err := db.queries.SelectPost(ctx, postID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbPostToPost(row), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Provider) SelectPostByGUID(ctx context.Context, guid string) (*models.Post, error) {
|
func (db *Provider) SelectPostByGUID(ctx context.Context, guid string) (*models.Post, error) {
|
||||||
row, err := db.queries.SelectPostByGUID(ctx, guid)
|
row, err := db.queries.SelectPostByGUID(ctx, guid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64
|
||||||
targets[i] = models.SitePublishTarget{
|
targets[i] = models.SitePublishTarget{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
SiteID: row.SiteID,
|
SiteID: row.SiteID,
|
||||||
|
Enabled: row.Enabled != 0,
|
||||||
TargetType: row.TargetType,
|
TargetType: row.TargetType,
|
||||||
BaseURL: row.BaseUrl,
|
BaseURL: row.BaseUrl,
|
||||||
TargetRef: row.TargetRef,
|
TargetRef: row.TargetRef,
|
||||||
|
|
@ -28,10 +29,16 @@ func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Provider) SavePublishTarget(ctx context.Context, target *models.SitePublishTarget) error {
|
func (db *Provider) SavePublishTarget(ctx context.Context, target *models.SitePublishTarget) error {
|
||||||
|
var enabled int64
|
||||||
|
if target.Enabled {
|
||||||
|
enabled = 1
|
||||||
|
}
|
||||||
|
|
||||||
if target.ID == 0 {
|
if target.ID == 0 {
|
||||||
newID, err := db.queries.InsertPublishTarget(ctx, sqlgen.InsertPublishTargetParams{
|
newID, err := db.queries.InsertPublishTarget(ctx, sqlgen.InsertPublishTargetParams{
|
||||||
SiteID: target.SiteID,
|
SiteID: target.SiteID,
|
||||||
TargetType: target.TargetType,
|
TargetType: target.TargetType,
|
||||||
|
Enabled: enabled,
|
||||||
BaseUrl: target.BaseURL,
|
BaseUrl: target.BaseURL,
|
||||||
TargetRef: target.TargetRef,
|
TargetRef: target.TargetRef,
|
||||||
TargetKey: target.TargetKey,
|
TargetKey: target.TargetKey,
|
||||||
|
|
|
||||||
65
services/posts/create.go
Normal file
65
services/posts/create.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package posts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lmika.dev/lmika/weiro/models"
|
||||||
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatePostParams struct {
|
||||||
|
GUID string `form:"guid" json:"guid"`
|
||||||
|
Title string `form:"title" json:"title"`
|
||||||
|
Body string `form:"body" json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) PublishPost(ctx context.Context, params CreatePostParams) (*models.Post, error) {
|
||||||
|
site, ok := models.GetSite(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, models.SiteRequiredError
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := s.fetchOrCreatePost(ctx, site, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Title = params.Title
|
||||||
|
post.Body = params.Body
|
||||||
|
post.PublishedAt = time.Now()
|
||||||
|
post.Slug = post.BestSlug()
|
||||||
|
|
||||||
|
if err := s.db.SavePost(ctx, post); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do on separate thread
|
||||||
|
if err := s.publisher.Publish(ctx, site); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return post, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) fetchOrCreatePost(ctx context.Context, site models.Site, params CreatePostParams) (*models.Post, error) {
|
||||||
|
post, err := s.db.SelectPostByGUID(ctx, params.GUID)
|
||||||
|
if err == nil {
|
||||||
|
if post.SiteID != site.ID {
|
||||||
|
return nil, models.NotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
return post, nil
|
||||||
|
} else if !db.ErrorIsNoRows(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
post = &models.Post{
|
||||||
|
SiteID: site.ID,
|
||||||
|
GUID: params.GUID,
|
||||||
|
Title: params.Title,
|
||||||
|
Body: params.Body,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
return post, nil
|
||||||
|
}
|
||||||
37
services/posts/list.go
Normal file
37
services/posts/list.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package posts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"lmika.dev/lmika/weiro/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) ListPosts(ctx context.Context) ([]*models.Post, error) {
|
||||||
|
site, ok := models.GetSite(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, models.SiteRequiredError
|
||||||
|
}
|
||||||
|
|
||||||
|
posts, err := s.db.SelectPostsOfSite(ctx, site.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPost(ctx context.Context, pid int64) (*models.Post, error) {
|
||||||
|
site, ok := models.GetSite(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, models.SiteRequiredError
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := s.db.SelectPost(ctx, pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if post.SiteID != site.ID {
|
||||||
|
return nil, models.NotFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
return post, nil
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
package posts
|
package posts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"lmika.dev/lmika/weiro/models"
|
|
||||||
"lmika.dev/lmika/weiro/providers/db"
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
"lmika.dev/lmika/weiro/services/publisher"
|
"lmika.dev/lmika/weiro/services/publisher"
|
||||||
)
|
)
|
||||||
|
|
@ -20,59 +16,3 @@ func New(db *db.Provider, publisher *publisher.Publisher) *Service {
|
||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePostParams struct {
|
|
||||||
GUID string `form:"guid" json:"guid"`
|
|
||||||
Title string `form:"title" json:"title"`
|
|
||||||
Body string `form:"body" json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) PublishPost(ctx context.Context, params CreatePostParams) (*models.Post, error) {
|
|
||||||
site, ok := models.GetSite(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, models.SiteRequiredError
|
|
||||||
}
|
|
||||||
|
|
||||||
post, err := s.fetchOrCreatePost(ctx, site, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
post.Title = params.Title
|
|
||||||
post.Body = params.Body
|
|
||||||
post.PublishedAt = time.Now()
|
|
||||||
post.Slug = post.BestSlug()
|
|
||||||
|
|
||||||
if err := s.db.SavePost(ctx, post); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: do on separate thread
|
|
||||||
if err := s.publisher.Publish(ctx, site); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) fetchOrCreatePost(ctx context.Context, site models.Site, params CreatePostParams) (*models.Post, error) {
|
|
||||||
post, err := s.db.SelectPostByGUID(ctx, params.GUID)
|
|
||||||
if err == nil {
|
|
||||||
if post.SiteID != site.ID {
|
|
||||||
return nil, models.NotFoundError
|
|
||||||
}
|
|
||||||
|
|
||||||
return post, nil
|
|
||||||
} else if !db.ErrorIsNoRows(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
post = &models.Post{
|
|
||||||
SiteID: site.ID,
|
|
||||||
GUID: params.GUID,
|
|
||||||
Title: params.Title,
|
|
||||||
Body: params.Body,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
return post, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
|
if !target.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
pubSite := pubmodel.Site{
|
pubSite := pubmodel.Site{
|
||||||
Site: site,
|
Site: site,
|
||||||
Posts: posts,
|
Posts: posts,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
-- name: SelectPostsOfSite :many
|
-- name: SelectPostsOfSite :many
|
||||||
SELECT * FROM posts WHERE site_id = ? ORDER BY created_at DESC LIMIT 10;
|
SELECT * FROM posts WHERE site_id = ? ORDER BY created_at DESC LIMIT 10;
|
||||||
|
|
||||||
|
-- name: SelectPost :one
|
||||||
|
SELECT * FROM posts WHERE id = ? LIMIT 1;
|
||||||
|
|
||||||
-- name: SelectPostByGUID :one
|
-- name: SelectPostByGUID :one
|
||||||
SELECT * FROM posts WHERE guid = ? LIMIT 1;
|
SELECT * FROM posts WHERE guid = ? LIMIT 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ SELECT * FROM publish_targets WHERE site_id = ?;
|
||||||
INSERT INTO publish_targets (
|
INSERT INTO publish_targets (
|
||||||
site_id,
|
site_id,
|
||||||
target_type,
|
target_type,
|
||||||
|
enabled,
|
||||||
base_url,
|
base_url,
|
||||||
target_ref,
|
target_ref,
|
||||||
target_key
|
target_key
|
||||||
) VALUES (?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
|
|
@ -19,6 +19,7 @@ CREATE TABLE publish_targets (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
site_id INTEGER NOT NULL,
|
site_id INTEGER NOT NULL,
|
||||||
target_type TEXT NOT NULL,
|
target_type TEXT NOT NULL,
|
||||||
|
enabled INT NOT NULL,
|
||||||
base_url TEXT NOT NULL,
|
base_url TEXT NOT NULL,
|
||||||
target_ref TEXT NOT NULL,
|
target_ref TEXT NOT NULL,
|
||||||
target_key TEXT NOT NULL
|
target_key TEXT NOT NULL
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,13 @@
|
||||||
<a class="nav-link active" aria-current="page" href="#">Posts</a>
|
<a class="nav-link active" aria-current="page" href="#">Posts</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<form class="d-flex" role="search">
|
<form class="d-flex align-items-center" role="search">
|
||||||
|
<!--
|
||||||
|
<div class="spinner-border text-secondary me-2" role="status" title="Publishing...">
|
||||||
|
<span class="visually-hidden">Publishing...</span>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
|
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<main class="flex-grow-1 position-relative">
|
<main class="flex-grow-1 position-relative">
|
||||||
<form action="/sites/{{.site.ID}}/posts" method="post" class="container-fluid post-form p-2">
|
<form action="/sites/{{.site.ID}}/posts" method="post" class="container-fluid post-form p-2">
|
||||||
<input type="hidden" name="guid" value="{{ .guid }}">
|
<input type="hidden" name="guid" value="{{ .post.GUID }}">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<input type="text" name="title" class="form-control" placeholder="Title">
|
<input type="text" name="title" class="form-control" placeholder="Title" value="{{ .post.Title }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<textarea name="body" class="form-control" rows="3"></textarea>
|
<textarea name="body" class="form-control" rows="3">{{.post.Body}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" class="btn btn-primary mt-2" value="Publish">
|
<input type="submit" class="btn btn-primary mt-2" value="Publish">
|
||||||
|
|
@ -1 +1,17 @@
|
||||||
<h1>Posts go here</h1>
|
<main class="container">
|
||||||
|
<div class="my-4">
|
||||||
|
<a href="/sites/{{ .site.ID }}/posts/new" class="btn btn-success">New Post</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ range $i, $p := .posts }}
|
||||||
|
{{ if gt $i 0 }}<hr>{{ end }}
|
||||||
|
<div class="my-4">
|
||||||
|
{{ if $p.Title }}<h4 class="mb-3">{{ $p.Title }}</h4>{{ end }}
|
||||||
|
{{ $p.Body | markdown }}
|
||||||
|
<div class="mb-3">
|
||||||
|
<a href="/sites/{{ $.site.ID }}/posts/{{ $p.ID }}/edit"
|
||||||
|
class="link-secondary link-offset-2 link-underline link-underline-opacity-0 link-underline-opacity-75-hover">Edit</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</main>
|
||||||
Loading…
Reference in a new issue