mirror of
https://github.com/1f349/tulip.git
synced 2024-12-22 16:24:10 +00:00
Improve underlying types and start writing edit page
This commit is contained in:
parent
703f3d17cd
commit
ece74ea36a
@ -114,7 +114,7 @@ func checkDbHasUser(db *database.DB) error {
|
|||||||
}
|
}
|
||||||
if err := tx.HasUser(); err != nil {
|
if err := tx.HasUser(); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
err := tx.InsertUser("admin", "admin", "admin@localhost")
|
err := tx.InsertUser("Admin", "admin", "admin", "admin@localhost")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add user: %w", err)
|
return fmt.Errorf("failed to add user: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ func marshalValueOrNull(null bool, data any) ([]byte, error) {
|
|||||||
type NullStringScanner struct{ sql.NullString }
|
type NullStringScanner struct{ sql.NullString }
|
||||||
|
|
||||||
func (s *NullStringScanner) Null() bool { return !s.Valid }
|
func (s *NullStringScanner) Null() bool { return !s.Valid }
|
||||||
func (s *NullStringScanner) Scan(src any) error { return s.Scan(src) }
|
func (s *NullStringScanner) Scan(src any) error { return s.NullString.Scan(src) }
|
||||||
func (s NullStringScanner) MarshalJSON() ([]byte, error) {
|
func (s NullStringScanner) MarshalJSON() ([]byte, error) {
|
||||||
return marshalValueOrNull(s.Null(), s.String)
|
return marshalValueOrNull(s.Null(), s.String)
|
||||||
}
|
}
|
||||||
@ -75,6 +75,14 @@ func (l *LocationScanner) Scan(src any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (l LocationScanner) MarshalJSON() ([]byte, error) { return json.Marshal(l.Location.String()) }
|
func (l LocationScanner) MarshalJSON() ([]byte, error) { return json.Marshal(l.Location.String()) }
|
||||||
|
func (l *LocationScanner) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var a string
|
||||||
|
err := json.Unmarshal(bytes, &a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.Scan(a)
|
||||||
|
}
|
||||||
|
|
||||||
type LocaleScanner struct{ language.Tag }
|
type LocaleScanner struct{ language.Tag }
|
||||||
|
|
||||||
@ -90,8 +98,14 @@ func (l *LocaleScanner) Scan(src any) error {
|
|||||||
l.Tag = lang
|
l.Tag = lang
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (l LocaleScanner) MarshalJSON() ([]byte, error) {
|
func (l LocaleScanner) MarshalJSON() ([]byte, error) { return json.Marshal(l.Tag.String()) }
|
||||||
return json.Marshal(l.Tag.String())
|
func (l *LocaleScanner) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var a string
|
||||||
|
err := json.Unmarshal(bytes, &a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.Scan(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PronounScanner struct{ pronouns.Pronoun }
|
type PronounScanner struct{ pronouns.Pronoun }
|
||||||
@ -109,3 +123,11 @@ func (p *PronounScanner) Scan(src any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (p PronounScanner) MarshalJSON() ([]byte, error) { return json.Marshal(p.Pronoun.String()) }
|
func (p PronounScanner) MarshalJSON() ([]byte, error) { return json.Marshal(p.Pronoun.String()) }
|
||||||
|
func (p *PronounScanner) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var a string
|
||||||
|
err := json.Unmarshal(bytes, &a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.Scan(a)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"database/sql"
|
||||||
"github.com/MrMelon54/pronouns"
|
"github.com/MrMelon54/pronouns"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@ -27,79 +27,51 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserPatch struct {
|
type UserPatch struct {
|
||||||
Name NullStringScanner `json:"name"`
|
Name string
|
||||||
Picture NullStringScanner `json:"picture"`
|
Picture string
|
||||||
Website NullStringScanner `json:"website"`
|
Website string
|
||||||
Pronouns PronounScanner `json:"pronouns"`
|
Pronouns pronouns.Pronoun
|
||||||
Birthdate NullDateScanner `json:"birthdate"`
|
Birthdate sql.NullTime
|
||||||
ZoneInfo *time.Location `json:"zoneinfo"`
|
ZoneInfo *time.Location
|
||||||
Locale *language.Tag `json:"locale"`
|
Locale language.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserPatch) UnmarshalJSON(bytes []byte) error {
|
func (u *UserPatch) ParseFromForm(v url.Values) (err error) {
|
||||||
var m struct {
|
u.Name = v.Get("name")
|
||||||
Name string `json:"name"`
|
u.Picture = v.Get("picture")
|
||||||
Picture string `json:"picture"`
|
u.Website = v.Get("website")
|
||||||
Website string `json:"website"`
|
if v.Has("reset_pronouns") {
|
||||||
Pronouns string `json:"pronouns"`
|
u.Pronouns = pronouns.TheyThem
|
||||||
Birthdate string `json:"birthdate"`
|
} else {
|
||||||
ZoneInfo string `json:"zoneinfo"`
|
u.Pronouns, err = pronouns.FindPronoun(v.Get("pronouns"))
|
||||||
Locale string `json:"locale"`
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(bytes, &m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.Name = m.Name
|
|
||||||
|
|
||||||
// only parse the picture address if included
|
|
||||||
if m.Picture != "" {
|
|
||||||
u.Picture, err = url.Parse(m.Picture)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v.Has("reset_birthdate") {
|
||||||
// only parse the website address if included
|
u.Birthdate = sql.NullTime{}
|
||||||
if m.Website != "" {
|
} else {
|
||||||
u.Website, err = url.Parse(m.Website)
|
u.Birthdate = sql.NullTime{Valid: true}
|
||||||
|
u.Birthdate.Time, err = time.Parse(time.DateOnly, v.Get("birthdate"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v.Has("reset_zoneinfo") {
|
||||||
// only parse the pronouns if included
|
u.ZoneInfo = time.UTC
|
||||||
if m.Pronouns != "" {
|
} else {
|
||||||
u.Pronouns, err = pronouns.FindPronoun(m.Pronouns)
|
u.ZoneInfo, err = time.LoadLocation(v.Get("zoneinfo"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v.Has("reset_locale") {
|
||||||
// only parse the birthdate if included
|
u.Locale = language.AmericanEnglish
|
||||||
if m.Birthdate != "" {
|
} else {
|
||||||
u.Birthdate, err = time.Parse(time.DateOnly, m.Birthdate)
|
u.Locale, err = language.Parse(v.Get("locale"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only parse the zoneinfo if included
|
|
||||||
if m.ZoneInfo != "" {
|
|
||||||
u.ZoneInfo, err = time.LoadLocation(m.ZoneInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Locale != "" {
|
|
||||||
locale, err := language.Parse(m.Locale)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.Locale = &locale
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ json.Unmarshaler = &UserPatch{}
|
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/MrMelon54/pronouns"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maps"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserPatch_UnmarshalJSON(t *testing.T) {
|
|
||||||
const a = `{
|
|
||||||
"name": "Test",
|
|
||||||
"picture": "https://example.com/logo.png",
|
|
||||||
"website": "https://example.com",
|
|
||||||
"gender": "robot",
|
|
||||||
"pronouns": "they/them",
|
|
||||||
"birthdate": "3070-01-01",
|
|
||||||
"zoneinfo": "Europe/London",
|
|
||||||
"locale": "en-GB"
|
|
||||||
}`
|
|
||||||
var p UserPatch
|
|
||||||
assert.NoError(t, json.Unmarshal([]byte(a), &p))
|
|
||||||
assert.Equal(t, "Test", p.Name)
|
|
||||||
assert.Equal(t, "https://example.com/logo.png", p.Picture.String())
|
|
||||||
assert.Equal(t, "https://example.com", p.Website.String())
|
|
||||||
assert.Equal(t, pronouns.TheyThem, p.Pronouns)
|
|
||||||
assert.Equal(t, time.Date(3070, time.January, 1, 0, 0, 0, 0, time.UTC), p.Birthdate)
|
|
||||||
location, err := time.LoadLocation("Europe/London")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, location, p.ZoneInfo)
|
|
||||||
assert.Equal(t, "en-GB", p.Locale.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserPatch_UnmarshalJSON2(t *testing.T) {
|
|
||||||
var userModifyChecks = map[string]struct{ valid, invalid []string }{
|
|
||||||
"picture": {valid: []string{"https://example.com/icon.png"}, invalid: []string{"%/icon.png"}},
|
|
||||||
"website": {valid: []string{"https://example.com"}, invalid: []string{"%/example.com"}},
|
|
||||||
"pronouns": {valid: []string{"he/him", "she/her"}, invalid: []string{"a/a"}},
|
|
||||||
"birthdate": {valid: []string{"2023-08-07", "2023-01-01"}, invalid: []string{"2023-00-00", "hello"}},
|
|
||||||
"zoneinfo": {
|
|
||||||
valid: []string{"Europe/London", "Europe/Berlin", "America/Los_Angeles", "America/Edmonton", "America/Montreal"},
|
|
||||||
invalid: []string{"Europe/York", "Canada/Edmonton", "hello"},
|
|
||||||
},
|
|
||||||
"locale": {valid: []string{"en-GB", "en-US", "zh-CN"}, invalid: []string{"en-YY"}},
|
|
||||||
}
|
|
||||||
m := map[string]string{
|
|
||||||
"name": "Test",
|
|
||||||
"picture": "https://example.com/logo.png",
|
|
||||||
"website": "https://example.com",
|
|
||||||
"gender": "robot",
|
|
||||||
"pronouns": "they/them",
|
|
||||||
"birthdate": "3070-01-01",
|
|
||||||
"zoneinfo": "Europe/London",
|
|
||||||
"locale": "en-GB",
|
|
||||||
}
|
|
||||||
for k, v := range userModifyChecks {
|
|
||||||
t.Run(k, func(t *testing.T) {
|
|
||||||
m2 := maps.Clone(m)
|
|
||||||
for _, i := range v.valid {
|
|
||||||
m2[k] = i
|
|
||||||
marshal, err := json.Marshal(m2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
var m3 UserPatch
|
|
||||||
assert.NoError(t, json.Unmarshal(marshal, &m3))
|
|
||||||
}
|
|
||||||
for _, i := range v.invalid {
|
|
||||||
m2[k] = i
|
|
||||||
marshal, err := json.Marshal(m2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
var m3 UserPatch
|
|
||||||
assert.Error(t, json.Unmarshal(marshal, &m3))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,13 +4,13 @@ CREATE TABLE IF NOT EXISTS users
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
picture TEXT,
|
picture TEXT DEFAULT "" NOT NULL,
|
||||||
website TEXT,
|
website TEXT DEFAULT "" NOT NULL,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
email_verified INTEGER DEFAULT 0 NOT NULL,
|
email_verified INTEGER DEFAULT 0 NOT NULL,
|
||||||
pronouns TEXT DEFAULT "they/them" NOT NULL,
|
pronouns TEXT DEFAULT "they/them" NOT NULL,
|
||||||
birthdate DATE,
|
birthdate DATE,
|
||||||
zoneinfo TEXT DEFAULT "" NOT NULL,
|
zoneinfo TEXT DEFAULT "UTC" NOT NULL,
|
||||||
locale TEXT DEFAULT "en-US" NOT NULL,
|
locale TEXT DEFAULT "en-US" NOT NULL,
|
||||||
updated_at DATETIME,
|
updated_at DATETIME,
|
||||||
active INTEGER DEFAULT 1
|
active INTEGER DEFAULT 1
|
||||||
|
@ -32,12 +32,12 @@ func (t *Tx) HasUser() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tx) InsertUser(un, pw, email string) error {
|
func (t *Tx) InsertUser(name, un, pw, email string) error {
|
||||||
pwHash, err := password.HashPassword(pw)
|
pwHash, err := password.HashPassword(pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = t.tx.Exec(`INSERT INTO users (subject, username, password, email) VALUES (?, ?, ?, ?)`, uuid.NewString(), un, pwHash, email)
|
_, err = t.tx.Exec(`INSERT INTO users (subject, name, username, password, email) VALUES (?, ?, ?, ?, ?)`, uuid.NewString(), name, un, pwHash, email)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,20 +113,20 @@ func (t *Tx) ChangeUserPassword(sub uuid.UUID, pwOld, pwNew string) error {
|
|||||||
func (t *Tx) ModifyUser(sub uuid.UUID, v *UserPatch) error {
|
func (t *Tx) ModifyUser(sub uuid.UUID, v *UserPatch) error {
|
||||||
exec, err := t.tx.Exec(
|
exec, err := t.tx.Exec(
|
||||||
`UPDATE users
|
`UPDATE users
|
||||||
SET name = ifnull(?, name),
|
SET name = ?,
|
||||||
picture = ifnull(?, picture),
|
picture = ?,
|
||||||
website = ifnull(?, website),
|
website = ?,
|
||||||
pronouns = ifnull(?, pronouns),
|
pronouns = ?,
|
||||||
birthdate = ifnull(?, birthdate),
|
birthdate = ?,
|
||||||
zoneinfo = ifnull(?, zoneinfo),
|
zoneinfo = ?,
|
||||||
locale = ifnull(?, locale),
|
locale = ?,
|
||||||
updated_at = ?
|
updated_at = ?
|
||||||
WHERE subject = ?`,
|
WHERE subject = ?`,
|
||||||
v.Name,
|
v.Name,
|
||||||
stringify(v.Picture),
|
v.Picture,
|
||||||
stringify(v.Website),
|
v.Website,
|
||||||
v.Pronouns.String(),
|
v.Pronouns.String(),
|
||||||
sql.NullTime{Time: v.Birthdate, Valid: !v.Birthdate.IsZero()},
|
v.Birthdate,
|
||||||
v.ZoneInfo.String(),
|
v.ZoneInfo.String(),
|
||||||
v.Locale.String(),
|
v.Locale.String(),
|
||||||
time.Now().Format(time.DateTime),
|
time.Now().Format(time.DateTime),
|
||||||
@ -164,14 +164,3 @@ func (c *clientInfoDbOutput) GetDomain() string { return c.domain }
|
|||||||
func (c *clientInfoDbOutput) IsPublic() bool { return false }
|
func (c *clientInfoDbOutput) IsPublic() bool { return false }
|
||||||
func (c *clientInfoDbOutput) GetUserID() string { return "" }
|
func (c *clientInfoDbOutput) GetUserID() string { return "" }
|
||||||
func (c *clientInfoDbOutput) IsSSO() bool { return c.sso }
|
func (c *clientInfoDbOutput) IsSSO() bool { return c.sso }
|
||||||
|
|
||||||
func stringify(stringer fmt.Stringer) sql.NullString {
|
|
||||||
if stringer == nil {
|
|
||||||
return sql.NullString{}
|
|
||||||
}
|
|
||||||
return emptyToNull(stringer.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func emptyToNull(a string) sql.NullString {
|
|
||||||
return sql.NullString{String: a, Valid: a != ""}
|
|
||||||
}
|
|
||||||
|
@ -44,12 +44,9 @@ func TestTx_ModifyUser(t *testing.T) {
|
|||||||
tx, err := d.Begin()
|
tx, err := d.Begin()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, tx.ModifyUser(u, &UserPatch{
|
assert.NoError(t, tx.ModifyUser(u, &UserPatch{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Picture: nil,
|
Pronouns: pronouns.TheyThem,
|
||||||
Website: nil,
|
ZoneInfo: time.UTC,
|
||||||
Pronouns: pronouns.Pronoun{},
|
Locale: language.AmericanEnglish,
|
||||||
Birthdate: time.Time{},
|
|
||||||
ZoneInfo: nil,
|
|
||||||
Locale: &language.Tag{},
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
66
pages/edit.go.html
Normal file
66
pages/edit.go.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>1f349 ID</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>1f349 ID</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div>Logged in as: {{.User.Name}} ({{.User.Sub}})</div>
|
||||||
|
<div>
|
||||||
|
<form method="POST" action="/edit">
|
||||||
|
<input type="hidden" name="nonce" value="{{.Nonce}}">
|
||||||
|
<div>
|
||||||
|
<label for="field_name">Name</label>
|
||||||
|
<input type="text" name="name" id="field_name" value="{{.User.Name}}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_picture">Picture</label>
|
||||||
|
<input type="text" name="picture" id="field_picture" value="{{.User.Picture}}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_website">Website</label>
|
||||||
|
<input type="text" name="website" id="field_website" value="{{.User.Picture}}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_pronouns">Pronouns</label>
|
||||||
|
<select name="pronouns" id="field_pronouns">
|
||||||
|
<option value="they/them" selected>They/Them</option>
|
||||||
|
<option value="he/him">He/Him</option>
|
||||||
|
<option value="she/her">She/Her</option>
|
||||||
|
<option value="it/its">It/Its</option>
|
||||||
|
<option value="one/one's">One/One's</option>
|
||||||
|
</select>
|
||||||
|
<label>Reset? <input type="checkbox" name="reset_pronouns"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_birthdate">Birthdate</label>
|
||||||
|
<input type="text" name="birthdate" id="field_birthdate" value="{{.User.Birthdate}}">
|
||||||
|
<label>Reset? <input type="checkbox" name="reset_birthdate"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_zoneinfo">Time Zone</label>
|
||||||
|
<input type="text" name="zoneinfo" id="field_zoneinfo" value="{{.User.ZoneInfo}}" list="list_zoneinfo">
|
||||||
|
<datalist id="list_zoneinfo">
|
||||||
|
<!-- Fill in -->
|
||||||
|
<option value="Europe/London"></option>
|
||||||
|
</datalist>
|
||||||
|
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="field_locale">Language</label>
|
||||||
|
<input type="text" name="locale" id="field_locale" value="{{.User.Locale}}" list="list_locale">
|
||||||
|
<datalist id="list_locale">
|
||||||
|
<!-- Fill in -->
|
||||||
|
<option value="en-US"></option>
|
||||||
|
</datalist>
|
||||||
|
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Edit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -8,9 +8,13 @@
|
|||||||
<h1>1f349 ID</h1>
|
<h1>1f349 ID</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div>Logged in as: {{.User.Name}} ({{.User.ID}})</div>
|
<div>Logged in as: {{.User.Name}} ({{.User.Sub}})</div>
|
||||||
<div>
|
<div>
|
||||||
<form method="POST" action="/logout"><input type="hidden" name="nonce" value="{{.Nonce}}">
|
<button onclick="location.href='/edit'">Edit Profile</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<input type="hidden" name="nonce" value="{{.Nonce}}">
|
||||||
<button type="submit">Log Out</button>
|
<button type="submit">Log Out</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *HttpServer) dbTx(rw http.ResponseWriter, action func(tx *database.Tx) error) bool {
|
// DbTx wraps a database transaction with http error messages and a simple action
|
||||||
|
// function. If the action function returns an error the transaction will be
|
||||||
|
// rolled back. If there is no error then the transaction is committed.
|
||||||
|
func (h *HttpServer) DbTx(rw http.ResponseWriter, action func(tx *database.Tx) error) bool {
|
||||||
tx, err := h.db.Begin()
|
tx, err := h.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, "Failed to begin database transaction", http.StatusInternalServerError)
|
http.Error(rw, "Failed to begin database transaction", http.StatusInternalServerError)
|
||||||
|
@ -93,9 +93,6 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
return "openid", nil
|
return "openid", nil
|
||||||
})
|
})
|
||||||
|
|
||||||
newUserUuid := uuid.New()
|
|
||||||
fmt.Println("New User Uuid:", newUserUuid.String())
|
|
||||||
|
|
||||||
r.GET("/.well-known/openid-configuration", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.GET("/.well-known/openid-configuration", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
_, _ = rw.Write(openIdBytes)
|
_, _ = rw.Write(openIdBytes)
|
||||||
@ -115,16 +112,18 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hs.dbTx(rw, func(tx *database.Tx) error {
|
hs.DbTx(rw, func(tx *database.Tx) error {
|
||||||
userWithName, err := tx.GetUserDisplayName(auth.ID)
|
userWithName, err := tx.GetUserDisplayName(auth.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get user display name: %w", err)
|
return fmt.Errorf("failed to get user display name: %w", err)
|
||||||
}
|
}
|
||||||
_ = pages.RenderPageTemplate(rw, "index", map[string]any{
|
if err := pages.RenderPageTemplate(rw, "index", map[string]any{
|
||||||
"Auth": auth,
|
"Auth": auth,
|
||||||
"User": userWithName,
|
"User": userWithName,
|
||||||
"Nonce": lNonce,
|
"Nonce": lNonce,
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Printf("Failed to render page: edit: %s\n", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
@ -152,13 +151,15 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
}
|
}
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
rw.Header().Set("Content-Type", "text/html")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
_ = pages.RenderPageTemplate(rw, "login", nil)
|
if err := pages.RenderPageTemplate(rw, "login", nil); err != nil {
|
||||||
|
log.Printf("Failed to render page: edit: %s\n", err)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
r.POST("/login", hs.OptionalAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
r.POST("/login", hs.OptionalAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
||||||
un := req.FormValue("username")
|
un := req.FormValue("username")
|
||||||
pw := req.FormValue("password")
|
pw := req.FormValue("password")
|
||||||
var userSub uuid.UUID
|
var userSub uuid.UUID
|
||||||
if hs.dbTx(rw, func(tx *database.Tx) error {
|
if hs.DbTx(rw, func(tx *database.Tx) error {
|
||||||
loginUser, err := tx.CheckLogin(un, pw)
|
loginUser, err := tx.CheckLogin(un, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors2.Is(err, sql.ErrNoRows) || errors2.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
if errors2.Is(err, sql.ErrNoRows) || errors2.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
@ -207,13 +208,16 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
r.GET("/edit", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
r.GET("/edit", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
||||||
begin, err := db.Begin()
|
var user *database.User
|
||||||
if err != nil {
|
|
||||||
return
|
if hs.DbTx(rw, func(tx *database.Tx) error {
|
||||||
}
|
var err error
|
||||||
user, err := begin.GetUser(auth.ID)
|
user, err = tx.GetUser(auth.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, "Failed to read user data", http.StatusInternalServerError)
|
return fmt.Errorf("failed to read user data: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,33 +227,31 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = pages.RenderPageTemplate(rw, "edit", map[string]any{
|
if err := pages.RenderPageTemplate(rw, "edit", map[string]any{
|
||||||
"User": user,
|
"User": user,
|
||||||
"Nonce": lNonce,
|
"Nonce": lNonce,
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Printf("Failed to render page: edit: %s\n", err)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
r.POST("/edit", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
r.POST("/edit", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
||||||
if req.ParseForm() != nil {
|
if req.ParseForm() != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: parse user patch from form
|
|
||||||
req.Form.Get("")
|
|
||||||
var patch database.UserPatch
|
var patch database.UserPatch
|
||||||
decoder := json.NewDecoder(req.Body)
|
err := patch.ParseFromForm(req.Form)
|
||||||
decoder.DisallowUnknownFields()
|
|
||||||
err := decoder.Decode(&patch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
begin, err := db.Begin()
|
if hs.DbTx(rw, func(tx *database.Tx) error {
|
||||||
if err != nil {
|
if err := tx.ModifyUser(auth.ID, &patch); err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
return fmt.Errorf("failed to modify user info: %w", err)
|
||||||
return
|
}
|
||||||
}
|
return nil
|
||||||
if begin.ModifyUser(auth.ID, &patch) != nil {
|
}) {
|
||||||
http.Error(rw, "Failed to modify user info", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(rw, req, "/", http.StatusFound)
|
http.Redirect(rw, req, "/", http.StatusFound)
|
||||||
|
Loading…
Reference in New Issue
Block a user