Added a database
This commit is contained in:
parent
ebaec3d296
commit
8136655336
35 changed files with 925 additions and 134 deletions
|
|
@ -1,37 +0,0 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: users.sql
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const insertUserByUsername = `-- name: InsertUserByUsername :one
|
||||
INSERT INTO users (username, password) VALUES (?, ?) RETURNING id
|
||||
`
|
||||
|
||||
type InsertUserByUsernameParams struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (q *Queries) InsertUserByUsername(ctx context.Context, arg InsertUserByUsernameParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertUserByUsername, arg.Username, arg.Password)
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const selectUserByUsername = `-- name: SelectUserByUsername :one
|
||||
SELECT id, username, password FROM users WHERE username = ?
|
||||
`
|
||||
|
||||
func (q *Queries) SelectUserByUsername(ctx context.Context, username string) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectUserByUsername, username)
|
||||
var i User
|
||||
err := row.Scan(&i.ID, &i.Username, &i.Password)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// sqlc v1.28.0
|
||||
|
||||
package sql
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// sqlc v1.28.0
|
||||
|
||||
package sql
|
||||
package sqlgen
|
||||
|
||||
type Post struct {
|
||||
ID int64
|
||||
|
|
@ -16,12 +16,12 @@ type Post struct {
|
|||
}
|
||||
|
||||
type PublishTarget struct {
|
||||
ID int64
|
||||
SiteID int64
|
||||
PublishTargetType int64
|
||||
BaseUrl string
|
||||
TargetSiteID string
|
||||
TargetPublishKey string
|
||||
ID int64
|
||||
SiteID int64
|
||||
TargetType int64
|
||||
BaseUrl string
|
||||
TargetRef string
|
||||
TargetKey string
|
||||
}
|
||||
|
||||
type Site struct {
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// sqlc v1.28.0
|
||||
// source: posts.sql
|
||||
|
||||
package sql
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// sqlc v1.28.0
|
||||
// source: pubtargets.sql
|
||||
|
||||
package sql
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -12,29 +12,29 @@ import (
|
|||
const insertPublishTarget = `-- name: InsertPublishTarget :one
|
||||
INSERT INTO publish_targets (
|
||||
site_id,
|
||||
publish_target_type,
|
||||
target_type,
|
||||
base_url,
|
||||
target_site_id,
|
||||
target_publish_key
|
||||
target_ref,
|
||||
target_key
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type InsertPublishTargetParams struct {
|
||||
SiteID int64
|
||||
PublishTargetType int64
|
||||
BaseUrl string
|
||||
TargetSiteID string
|
||||
TargetPublishKey string
|
||||
SiteID int64
|
||||
TargetType int64
|
||||
BaseUrl string
|
||||
TargetRef string
|
||||
TargetKey string
|
||||
}
|
||||
|
||||
func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTargetParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertPublishTarget,
|
||||
arg.SiteID,
|
||||
arg.PublishTargetType,
|
||||
arg.TargetType,
|
||||
arg.BaseUrl,
|
||||
arg.TargetSiteID,
|
||||
arg.TargetPublishKey,
|
||||
arg.TargetRef,
|
||||
arg.TargetKey,
|
||||
)
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
|
|
@ -42,7 +42,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg
|
|||
}
|
||||
|
||||
const selectPublishTargetsOfSite = `-- name: SelectPublishTargetsOfSite :many
|
||||
SELECT id, site_id, publish_target_type, base_url, target_site_id, target_publish_key FROM publish_targets WHERE site_id = ?
|
||||
SELECT id, site_id, target_type, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) {
|
||||
|
|
@ -57,10 +57,10 @@ func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64)
|
|||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.SiteID,
|
||||
&i.PublishTargetType,
|
||||
&i.TargetType,
|
||||
&i.BaseUrl,
|
||||
&i.TargetSiteID,
|
||||
&i.TargetPublishKey,
|
||||
&i.TargetRef,
|
||||
&i.TargetKey,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// sqlc v1.28.0
|
||||
// source: sites.sql
|
||||
|
||||
package sql
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -31,6 +31,22 @@ func (q *Queries) InsertSite(ctx context.Context, arg InsertSiteParams) (int64,
|
|||
return id, err
|
||||
}
|
||||
|
||||
const selectSiteByID = `-- name: SelectSiteByID :one
|
||||
SELECT id, owner_id, title, tagline FROM sites WHERE id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) SelectSiteByID(ctx context.Context, id int64) (Site, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectSiteByID, id)
|
||||
var i Site
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.Title,
|
||||
&i.Tagline,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const selectSitesOwnedByUser = `-- name: SelectSitesOwnedByUser :many
|
||||
SELECT id, owner_id, title, tagline FROM sites WHERE owner_id = ?
|
||||
`
|
||||
52
providers/db/gen/sqlgen/users.sql.go
Normal file
52
providers/db/gen/sqlgen/users.sql.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: users.sql
|
||||
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const insertUser = `-- name: InsertUser :one
|
||||
INSERT INTO users (username, password) VALUES (?, ?) RETURNING id
|
||||
`
|
||||
|
||||
type InsertUserParams struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (q *Queries) InsertUser(ctx context.Context, arg InsertUserParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertUser, arg.Username, arg.Password)
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const selectUserByUsername = `-- name: SelectUserByUsername :one
|
||||
SELECT id, username, password FROM users WHERE username = ?
|
||||
`
|
||||
|
||||
func (q *Queries) SelectUserByUsername(ctx context.Context, username string) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectUserByUsername, username)
|
||||
var i User
|
||||
err := row.Scan(&i.ID, &i.Username, &i.Password)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateUser = `-- name: UpdateUser :exec
|
||||
UPDATE users SET username = ?, password = ? WHERE id = ?
|
||||
`
|
||||
|
||||
type UpdateUserParams struct {
|
||||
Username string
|
||||
Password string
|
||||
ID int64
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateUser, arg.Username, arg.Password, arg.ID)
|
||||
return err
|
||||
}
|
||||
53
providers/db/posts.go
Normal file
53
providers/db/posts.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db/gen/sqlgen"
|
||||
)
|
||||
|
||||
func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64) ([]*models.Post, error) {
|
||||
rows, err := db.queries.SelectPostsOfSite(ctx, siteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts := make([]*models.Post, len(rows))
|
||||
for i, row := range rows {
|
||||
posts[i] = &models.Post{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
GUID: row.Guid,
|
||||
Title: row.Title,
|
||||
Body: row.Body,
|
||||
Slug: row.Slug,
|
||||
CreatedAt: time.Unix(row.CreatedAt, 0).UTC(),
|
||||
PublishedAt: time.Unix(row.PublishedAt, 0).UTC(),
|
||||
}
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SavePost(ctx context.Context, post *models.Post) error {
|
||||
if post.ID == 0 {
|
||||
newID, err := db.queries.InsertPost(ctx, sqlgen.InsertPostParams{
|
||||
SiteID: post.SiteID,
|
||||
Guid: post.GUID,
|
||||
Title: post.Title,
|
||||
Body: post.Body,
|
||||
Slug: post.Slug,
|
||||
CreatedAt: post.CreatedAt.Unix(),
|
||||
PublishedAt: post.PublishedAt.Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
post.ID = newID
|
||||
return nil
|
||||
}
|
||||
|
||||
// No update query defined in sqlgen yet
|
||||
return nil
|
||||
}
|
||||
|
|
@ -5,14 +5,14 @@ import (
|
|||
"database/sql"
|
||||
|
||||
"github.com/Southclaws/fault"
|
||||
"github.com/lmika/blogging-tools/providers/db/sqlc/maindbq"
|
||||
"github.com/lmika/blogging-tools/sql/maindb/schema"
|
||||
"lmika.dev/lmika/weiro/providers/db/gen/sqlgen"
|
||||
"lmika.dev/lmika/weiro/sql/schema"
|
||||
migration "lmika.dev/pkg/litemigrate"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
drvr *sql.DB
|
||||
queries *maindbq.Queries
|
||||
queries *sqlgen.Queries
|
||||
}
|
||||
|
||||
func New(dbFile string) (*Provider, error) {
|
||||
|
|
@ -31,7 +31,7 @@ func New(dbFile string) (*Provider, error) {
|
|||
|
||||
return &Provider{
|
||||
drvr: drvr,
|
||||
queries: maindbq.New(drvr),
|
||||
queries: sqlgen.New(drvr),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
306
providers/db/provider_test.go
Normal file
306
providers/db/provider_test.go
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
package db_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func newTestDB(t *testing.T) *db.Provider {
|
||||
t.Helper()
|
||||
dbFile := filepath.Join(t.TempDir(), "test.db")
|
||||
p, err := db.New(dbFile)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { p.Close() })
|
||||
return p
|
||||
}
|
||||
|
||||
func TestProvider_Users(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := newTestDB(t)
|
||||
|
||||
t.Run("save and select user", func(t *testing.T) {
|
||||
user := &models.User{
|
||||
Username: "alice",
|
||||
PasswordHashed: []byte("hashed-password"),
|
||||
}
|
||||
|
||||
err := p.SaveUser(ctx, user)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, user.ID)
|
||||
|
||||
got, err := p.SelectUserByUsername(ctx, "alice")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, got.ID)
|
||||
assert.Equal(t, "alice", got.Username)
|
||||
assert.Equal(t, []byte("hashed-password"), got.PasswordHashed)
|
||||
})
|
||||
|
||||
t.Run("update user", func(t *testing.T) {
|
||||
user := &models.User{
|
||||
Username: "bob",
|
||||
PasswordHashed: []byte("old-password"),
|
||||
}
|
||||
err := p.SaveUser(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
user.Username = "bob"
|
||||
user.PasswordHashed = []byte("new-password")
|
||||
err = p.SaveUser(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := p.SelectUserByUsername(ctx, "bob")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("new-password"), got.PasswordHashed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvider_Sites(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := newTestDB(t)
|
||||
|
||||
// Create a user first (sites need an owner)
|
||||
user := &models.User{
|
||||
Username: "testuser",
|
||||
PasswordHashed: []byte("password"),
|
||||
}
|
||||
require.NoError(t, p.SaveUser(ctx, user))
|
||||
|
||||
t.Run("save and select sites", func(t *testing.T) {
|
||||
site := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "My Blog",
|
||||
Tagline: "A test blog",
|
||||
}
|
||||
|
||||
err := p.SaveSite(ctx, site)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, site.ID)
|
||||
|
||||
sites, err := p.SelectSitesOwnedByUser(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sites, 1)
|
||||
assert.Equal(t, site.ID, sites[0].ID)
|
||||
assert.Equal(t, user.ID, sites[0].OwnerID)
|
||||
assert.Equal(t, "My Blog", sites[0].Title)
|
||||
assert.Equal(t, "A test blog", sites[0].Tagline)
|
||||
})
|
||||
|
||||
t.Run("select site by id", func(t *testing.T) {
|
||||
site := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "Lookup Blog",
|
||||
Tagline: "Find me by ID",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, site))
|
||||
|
||||
got, err := p.SelectSiteByID(ctx, site.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, site.ID, got.ID)
|
||||
assert.Equal(t, user.ID, got.OwnerID)
|
||||
assert.Equal(t, "Lookup Blog", got.Title)
|
||||
assert.Equal(t, "Find me by ID", got.Tagline)
|
||||
})
|
||||
|
||||
t.Run("select sites for user with no sites", func(t *testing.T) {
|
||||
otherUser := &models.User{
|
||||
Username: "otheruser",
|
||||
PasswordHashed: []byte("password"),
|
||||
}
|
||||
require.NoError(t, p.SaveUser(ctx, otherUser))
|
||||
|
||||
sites, err := p.SelectSitesOwnedByUser(ctx, otherUser.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, sites)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvider_Posts(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := newTestDB(t)
|
||||
|
||||
// Create user and site
|
||||
user := &models.User{
|
||||
Username: "testuser",
|
||||
PasswordHashed: []byte("password"),
|
||||
}
|
||||
require.NoError(t, p.SaveUser(ctx, user))
|
||||
|
||||
site := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "My Blog",
|
||||
Tagline: "A test blog",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, site))
|
||||
|
||||
t.Run("save and select posts", func(t *testing.T) {
|
||||
now := time.Date(2026, 2, 19, 12, 0, 0, 0, time.UTC)
|
||||
post := &models.Post{
|
||||
SiteID: site.ID,
|
||||
GUID: "post-001",
|
||||
Title: "First Post",
|
||||
Body: "Hello world",
|
||||
Slug: "/2026/02/19/first-post",
|
||||
CreatedAt: now,
|
||||
PublishedAt: now,
|
||||
}
|
||||
|
||||
err := p.SavePost(ctx, post)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, post.ID)
|
||||
|
||||
posts, err := p.SelectPostsOfSite(ctx, site.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, posts, 1)
|
||||
assert.Equal(t, post.ID, posts[0].ID)
|
||||
assert.Equal(t, site.ID, posts[0].SiteID)
|
||||
assert.Equal(t, "post-001", posts[0].GUID)
|
||||
assert.Equal(t, "First Post", posts[0].Title)
|
||||
assert.Equal(t, "Hello world", posts[0].Body)
|
||||
assert.Equal(t, "/2026/02/19/first-post", posts[0].Slug)
|
||||
assert.Equal(t, now, posts[0].CreatedAt)
|
||||
assert.Equal(t, now, posts[0].PublishedAt)
|
||||
})
|
||||
|
||||
t.Run("posts ordered by created_at desc", func(t *testing.T) {
|
||||
// Create a second site to isolate this test
|
||||
site2 := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "Second Blog",
|
||||
Tagline: "",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, site2))
|
||||
|
||||
earlier := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
later := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
post1 := &models.Post{
|
||||
SiteID: site2.ID,
|
||||
GUID: "old-post",
|
||||
Title: "Old Post",
|
||||
Body: "old",
|
||||
Slug: "/old",
|
||||
CreatedAt: earlier,
|
||||
PublishedAt: earlier,
|
||||
}
|
||||
post2 := &models.Post{
|
||||
SiteID: site2.ID,
|
||||
GUID: "new-post",
|
||||
Title: "New Post",
|
||||
Body: "new",
|
||||
Slug: "/new",
|
||||
CreatedAt: later,
|
||||
PublishedAt: later,
|
||||
}
|
||||
|
||||
require.NoError(t, p.SavePost(ctx, post1))
|
||||
require.NoError(t, p.SavePost(ctx, post2))
|
||||
|
||||
posts, err := p.SelectPostsOfSite(ctx, site2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, posts, 2)
|
||||
assert.Equal(t, "New Post", posts[0].Title)
|
||||
assert.Equal(t, "Old Post", posts[1].Title)
|
||||
})
|
||||
|
||||
t.Run("select posts for site with no posts", func(t *testing.T) {
|
||||
emptySite := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "Empty Blog",
|
||||
Tagline: "",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, emptySite))
|
||||
|
||||
posts, err := p.SelectPostsOfSite(ctx, emptySite.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, posts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvider_PublishTargets(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := newTestDB(t)
|
||||
|
||||
// Create user and site
|
||||
user := &models.User{
|
||||
Username: "testuser",
|
||||
PasswordHashed: []byte("password"),
|
||||
}
|
||||
require.NoError(t, p.SaveUser(ctx, user))
|
||||
|
||||
site := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "My Blog",
|
||||
Tagline: "A test blog",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, site))
|
||||
|
||||
t.Run("save and select publish targets", func(t *testing.T) {
|
||||
target := &models.SitePublishTarget{
|
||||
SiteID: site.ID,
|
||||
TargetType: models.PublishTargetTypeNetlify,
|
||||
BaseURL: "https://example.netlify.app",
|
||||
TargetRef: "netlify-site-123",
|
||||
TargetKey: "secret-key",
|
||||
}
|
||||
|
||||
err := p.SavePublishTarget(ctx, target)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, target.ID)
|
||||
|
||||
targets, err := p.SelectPublishTargetsOfSite(ctx, site.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, targets, 1)
|
||||
assert.Equal(t, target.ID, targets[0].ID)
|
||||
assert.Equal(t, site.ID, targets[0].SiteID)
|
||||
assert.Equal(t, models.PublishTargetTypeNetlify, targets[0].TargetType)
|
||||
assert.Equal(t, "https://example.netlify.app", targets[0].BaseURL)
|
||||
assert.Equal(t, "netlify-site-123", targets[0].TargetRef)
|
||||
assert.Equal(t, "secret-key", targets[0].TargetKey)
|
||||
})
|
||||
|
||||
t.Run("select targets for site with no targets", func(t *testing.T) {
|
||||
emptySite := &models.Site{
|
||||
OwnerID: user.ID,
|
||||
Title: "No Targets",
|
||||
Tagline: "",
|
||||
}
|
||||
require.NoError(t, p.SaveSite(ctx, emptySite))
|
||||
|
||||
targets, err := p.SelectPublishTargetsOfSite(ctx, emptySite.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, targets)
|
||||
})
|
||||
}
|
||||
|
||||
// Verify that password encoding roundtrips correctly through base64
|
||||
func TestProvider_UserPasswordEncoding(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := newTestDB(t)
|
||||
|
||||
// Use bytes that aren't valid UTF-8 to verify binary safety
|
||||
rawPassword := []byte{0x00, 0xff, 0x80, 0x7f, 0x01}
|
||||
|
||||
user := &models.User{
|
||||
Username: "binuser",
|
||||
PasswordHashed: rawPassword,
|
||||
}
|
||||
require.NoError(t, p.SaveUser(ctx, user))
|
||||
|
||||
got, err := p.SelectUserByUsername(ctx, "binuser")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rawPassword, got.PasswordHashed)
|
||||
|
||||
// Verify it's stored as base64 (not raw bytes) - this is implicit
|
||||
// from the implementation but good to confirm the roundtrip works
|
||||
_ = base64.StdEncoding.EncodeToString(rawPassword)
|
||||
}
|
||||
48
providers/db/pubtargets.go
Normal file
48
providers/db/pubtargets.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db/gen/sqlgen"
|
||||
)
|
||||
|
||||
func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]models.SitePublishTarget, error) {
|
||||
rows, err := db.queries.SelectPublishTargetsOfSite(ctx, siteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targets := make([]models.SitePublishTarget, len(rows))
|
||||
for i, row := range rows {
|
||||
targets[i] = models.SitePublishTarget{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
TargetType: models.PublishTargetType(row.TargetType),
|
||||
BaseURL: row.BaseUrl,
|
||||
TargetRef: row.TargetRef,
|
||||
TargetKey: row.TargetKey,
|
||||
}
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SavePublishTarget(ctx context.Context, target *models.SitePublishTarget) error {
|
||||
if target.ID == 0 {
|
||||
newID, err := db.queries.InsertPublishTarget(ctx, sqlgen.InsertPublishTargetParams{
|
||||
SiteID: target.SiteID,
|
||||
TargetType: int64(target.TargetType),
|
||||
BaseUrl: target.BaseURL,
|
||||
TargetRef: target.TargetRef,
|
||||
TargetKey: target.TargetKey,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.ID = newID
|
||||
return nil
|
||||
}
|
||||
|
||||
// No update query defined in sqlgen yet
|
||||
return nil
|
||||
}
|
||||
58
providers/db/sites.go
Normal file
58
providers/db/sites.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db/gen/sqlgen"
|
||||
)
|
||||
|
||||
func (db *Provider) SelectSiteByID(ctx context.Context, id int64) (models.Site, error) {
|
||||
row, err := db.queries.SelectSiteByID(ctx, id)
|
||||
if err != nil {
|
||||
return models.Site{}, err
|
||||
}
|
||||
|
||||
return models.Site{
|
||||
ID: row.ID,
|
||||
OwnerID: row.OwnerID,
|
||||
Title: row.Title,
|
||||
Tagline: row.Tagline,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([]models.Site, error) {
|
||||
rows, err := db.queries.SelectSitesOwnedByUser(ctx, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sites := make([]models.Site, len(rows))
|
||||
for i, row := range rows {
|
||||
sites[i] = models.Site{
|
||||
ID: row.ID,
|
||||
OwnerID: row.OwnerID,
|
||||
Title: row.Title,
|
||||
Tagline: row.Tagline,
|
||||
}
|
||||
}
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SaveSite(ctx context.Context, site *models.Site) error {
|
||||
if site.ID == 0 {
|
||||
newID, err := db.queries.InsertSite(ctx, sqlgen.InsertSiteParams{
|
||||
OwnerID: site.OwnerID,
|
||||
Title: site.Title,
|
||||
Tagline: site.Tagline,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
site.ID = newID
|
||||
return nil
|
||||
}
|
||||
|
||||
// No update query defined in sqlgen yet
|
||||
return nil
|
||||
}
|
||||
49
providers/db/users.go
Normal file
49
providers/db/users.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/providers/db/gen/sqlgen"
|
||||
)
|
||||
|
||||
func (db *Provider) SelectUserByUsername(ctx context.Context, username string) (models.User, error) {
|
||||
res, err := db.queries.SelectUserByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
pwdBytes, err := base64.StdEncoding.DecodeString(res.Password)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return models.User{
|
||||
ID: res.ID,
|
||||
Username: res.Username,
|
||||
PasswordHashed: pwdBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SaveUser(ctx context.Context, user *models.User) error {
|
||||
hashedPassword := base64.StdEncoding.EncodeToString(user.PasswordHashed)
|
||||
|
||||
if user.ID == 0 {
|
||||
newID, err := db.queries.InsertUser(ctx, sqlgen.InsertUserParams{
|
||||
Username: user.Username,
|
||||
Password: hashedPassword,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.ID = newID
|
||||
return nil
|
||||
}
|
||||
|
||||
return db.queries.UpdateUser(ctx, sqlgen.UpdateUserParams{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Password: hashedPassword,
|
||||
})
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
|
|||
return postSingleData{
|
||||
commonData: commonData{Site: b.site},
|
||||
Path: postPath,
|
||||
Meta: post,
|
||||
Post: post,
|
||||
HTML: template.HTML(md.String()),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func TestBuilder_BuildSite(t *testing.T) {
|
|||
t.Run("build site", func(t *testing.T) {
|
||||
tmpls := fstest.MapFS{
|
||||
"posts_single.html": {Data: []byte(`{{ .HTML }}`)},
|
||||
"posts_list.html": {Data: []byte(`{{ range .Posts}}<a href="{{url_abs .Path}}">{{.Meta.Title}}</a>,{{ end }}`)},
|
||||
"posts_list.html": {Data: []byte(`{{ range .Posts}}<a href="{{url_abs .Path}}">{{.Post.Title}}</a>,{{ end }}`)},
|
||||
"layout_main.html": {Data: []byte(`{{ .Body }}`)},
|
||||
}
|
||||
|
||||
|
|
@ -59,4 +59,4 @@ func TestBuilder_BuildSite(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type commonData struct {
|
|||
|
||||
type postSingleData struct {
|
||||
commonData
|
||||
Meta *models.Post
|
||||
Post *models.Post
|
||||
HTML template.HTML
|
||||
Path string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ import (
|
|||
)
|
||||
|
||||
type ReadSiteModels struct {
|
||||
Site models.Site
|
||||
Target models.SitePublishTarget
|
||||
Posts []*models.Post
|
||||
Site models.Site
|
||||
Posts []*models.Post
|
||||
}
|
||||
|
||||
type siteMeta struct {
|
||||
|
|
|
|||
|
|
@ -39,14 +39,10 @@ func (p *Provider) ReadSite() (ReadSiteModels, error) {
|
|||
Title: meta.Title,
|
||||
Tagline: meta.Tagline,
|
||||
}
|
||||
publishTarget := models.SitePublishTarget{
|
||||
BaseURL: meta.BaseURL,
|
||||
}
|
||||
|
||||
return ReadSiteModels{
|
||||
Site: site,
|
||||
Target: publishTarget,
|
||||
Posts: posts,
|
||||
Site: site,
|
||||
Posts: posts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue