mirror of
https://github.com/1f349/tulip.git
synced 2024-12-22 16:24:10 +00:00
Provide datalist for zoneinfo and locale
This commit is contained in:
parent
ece74ea36a
commit
65f77dbe79
@ -27,7 +27,7 @@ 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.NullString.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.NullString.String)
|
||||||
}
|
}
|
||||||
func (s *NullStringScanner) UnmarshalJSON(bytes []byte) error {
|
func (s *NullStringScanner) UnmarshalJSON(bytes []byte) error {
|
||||||
if string(bytes) == "null" {
|
if string(bytes) == "null" {
|
||||||
@ -40,6 +40,12 @@ func (s *NullStringScanner) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
return s.Scan(&a)
|
return s.Scan(&a)
|
||||||
}
|
}
|
||||||
|
func (s NullStringScanner) String() string {
|
||||||
|
if s.Null() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.NullString.String
|
||||||
|
}
|
||||||
|
|
||||||
type NullDateScanner struct{ sql.NullTime }
|
type NullDateScanner struct{ sql.NullTime }
|
||||||
|
|
||||||
@ -59,6 +65,12 @@ func (t *NullDateScanner) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
return t.Scan(&a)
|
return t.Scan(&a)
|
||||||
}
|
}
|
||||||
|
func (t NullDateScanner) String() string {
|
||||||
|
if t.Null() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t.NullTime.Time.UTC().Format(time.DateOnly)
|
||||||
|
}
|
||||||
|
|
||||||
type LocationScanner struct{ *time.Location }
|
type LocationScanner struct{ *time.Location }
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func updatedAt() string {
|
||||||
|
return time.Now().UTC().Format(time.DateTime)
|
||||||
|
}
|
||||||
|
|
||||||
type Tx struct{ tx *sql.Tx }
|
type Tx struct{ tx *sql.Tx }
|
||||||
|
|
||||||
func (t *Tx) Commit() error {
|
func (t *Tx) Commit() error {
|
||||||
@ -37,7 +41,7 @@ func (t *Tx) InsertUser(name, un, pw, email string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = t.tx.Exec(`INSERT INTO users (subject, name, username, password, email) VALUES (?, ?, ?, ?, ?)`, uuid.NewString(), name, un, pwHash, email)
|
_, err = t.tx.Exec(`INSERT INTO users (subject, name, username, password, email, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, uuid.NewString(), name, un, pwHash, email, updatedAt())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +100,7 @@ func (t *Tx) ChangeUserPassword(sub uuid.UUID, pwOld, pwNew string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
exec, err := t.tx.Exec(`UPDATE users SET password = ?, updated_at = ? WHERE subject = ? AND password = ?`, pwNewHash, time.Now().Format(time.DateTime), sub, pwHash)
|
exec, err := t.tx.Exec(`UPDATE users SET password = ?, updated_at = ? WHERE subject = ? AND password = ?`, pwNewHash, updatedAt(), sub, pwHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -129,7 +133,7 @@ WHERE subject = ?`,
|
|||||||
v.Birthdate,
|
v.Birthdate,
|
||||||
v.ZoneInfo.String(),
|
v.ZoneInfo.String(),
|
||||||
v.Locale.String(),
|
v.Locale.String(),
|
||||||
time.Now().Format(time.DateTime),
|
updatedAt(),
|
||||||
sub,
|
sub,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,7 +16,7 @@ func TestTx_ChangeUserPassword(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
d, err := Open("file::memory:")
|
d, err := Open("file::memory:")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = d.db.Exec(`INSERT INTO users (subject, name, username, password, email) VALUES (?, ?, ?, ?, ?)`, u.String(), "Test", "test", pw, "test@localhost")
|
_, err = d.db.Exec(`INSERT INTO users (subject, name, username, password, email, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, u.String(), "Test", "test", pw, "test@localhost", updatedAt())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
tx, err := d.Begin()
|
tx, err := d.Begin()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -39,7 +39,7 @@ func TestTx_ModifyUser(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
d, err := Open("file::memory:")
|
d, err := Open("file::memory:")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = d.db.Exec(`INSERT INTO users (subject, name, username, password, email) VALUES (?, ?, ?, ?, ?)`, u.String(), "Test", "test", pw, "test@localhost")
|
_, err = d.db.Exec(`INSERT INTO users (subject, name, username, password, email, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, u.String(), "Test", "test", pw, "test@localhost", updatedAt())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
tx, err := d.Begin()
|
tx, err := d.Begin()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
104
lists/locales.go
Normal file
104
lists/locales.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package lists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"golang.org/x/text/language/display"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localeOnce sync.Once
|
||||||
|
localeNames []struct{ Value, Label string }
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListLocale() []struct{ Value, Label string } {
|
||||||
|
localeOnce.Do(func() {
|
||||||
|
localeNames = make([]struct{ Value, Label string }, len(localeList))
|
||||||
|
for i := range localeList {
|
||||||
|
localeNames[i] = struct{ Value, Label string }{Value: localeList[i].String(), Label: display.Self.Name(localeList[i])}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return localeNames
|
||||||
|
}
|
||||||
|
|
||||||
|
var localeList = []language.Tag{
|
||||||
|
language.Afrikaans,
|
||||||
|
language.Amharic,
|
||||||
|
language.Arabic,
|
||||||
|
language.ModernStandardArabic,
|
||||||
|
language.Azerbaijani,
|
||||||
|
language.Bulgarian,
|
||||||
|
language.Bengali,
|
||||||
|
language.Catalan,
|
||||||
|
language.Czech,
|
||||||
|
language.Danish,
|
||||||
|
language.German,
|
||||||
|
language.Greek,
|
||||||
|
language.English,
|
||||||
|
language.AmericanEnglish,
|
||||||
|
language.BritishEnglish,
|
||||||
|
language.Spanish,
|
||||||
|
language.EuropeanSpanish,
|
||||||
|
language.LatinAmericanSpanish,
|
||||||
|
language.Estonian,
|
||||||
|
language.Persian,
|
||||||
|
language.Finnish,
|
||||||
|
language.Filipino,
|
||||||
|
language.French,
|
||||||
|
language.CanadianFrench,
|
||||||
|
language.Gujarati,
|
||||||
|
language.Hebrew,
|
||||||
|
language.Hindi,
|
||||||
|
language.Croatian,
|
||||||
|
language.Hungarian,
|
||||||
|
language.Armenian,
|
||||||
|
language.Indonesian,
|
||||||
|
language.Icelandic,
|
||||||
|
language.Italian,
|
||||||
|
language.Japanese,
|
||||||
|
language.Georgian,
|
||||||
|
language.Kazakh,
|
||||||
|
language.Khmer,
|
||||||
|
language.Kannada,
|
||||||
|
language.Korean,
|
||||||
|
language.Kirghiz,
|
||||||
|
language.Lao,
|
||||||
|
language.Lithuanian,
|
||||||
|
language.Latvian,
|
||||||
|
language.Macedonian,
|
||||||
|
language.Malayalam,
|
||||||
|
language.Mongolian,
|
||||||
|
language.Marathi,
|
||||||
|
language.Malay,
|
||||||
|
language.Burmese,
|
||||||
|
language.Nepali,
|
||||||
|
language.Dutch,
|
||||||
|
language.Norwegian,
|
||||||
|
language.Punjabi,
|
||||||
|
language.Polish,
|
||||||
|
language.Portuguese,
|
||||||
|
language.BrazilianPortuguese,
|
||||||
|
language.EuropeanPortuguese,
|
||||||
|
language.Romanian,
|
||||||
|
language.Russian,
|
||||||
|
language.Sinhala,
|
||||||
|
language.Slovak,
|
||||||
|
language.Slovenian,
|
||||||
|
language.Albanian,
|
||||||
|
language.Serbian,
|
||||||
|
language.SerbianLatin,
|
||||||
|
language.Swedish,
|
||||||
|
language.Swahili,
|
||||||
|
language.Tamil,
|
||||||
|
language.Telugu,
|
||||||
|
language.Thai,
|
||||||
|
language.Turkish,
|
||||||
|
language.Ukrainian,
|
||||||
|
language.Urdu,
|
||||||
|
language.Uzbek,
|
||||||
|
language.Vietnamese,
|
||||||
|
language.Chinese,
|
||||||
|
language.SimplifiedChinese,
|
||||||
|
language.TraditionalChinese,
|
||||||
|
language.Zulu,
|
||||||
|
}
|
15
lists/locales_test.go
Normal file
15
lists/locales_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package lists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListLocale(t *testing.T) {
|
||||||
|
locales := ListLocale()
|
||||||
|
assert.True(t, len(locales) > 4)
|
||||||
|
assert.Equal(t, struct{ Value, Label string }{Value: "af", Label: "Afrikaans"}, locales[0])
|
||||||
|
assert.Equal(t, struct{ Value, Label string }{Value: "am", Label: "አማርኛ"}, locales[1])
|
||||||
|
assert.Equal(t, struct{ Value, Label string }{Value: "zh-Hant", Label: "繁體中文"}, locales[len(locales)-2])
|
||||||
|
assert.Equal(t, struct{ Value, Label string }{Value: "zu", Label: "isiZulu"}, locales[len(locales)-1])
|
||||||
|
}
|
53
lists/zoneinfo.go
Normal file
53
lists/zoneinfo.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package lists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
zoneDirs = []string{
|
||||||
|
// Update path according to your OS
|
||||||
|
"/usr/share/zoneinfo/",
|
||||||
|
"/usr/share/lib/zoneinfo/",
|
||||||
|
"/usr/lib/locale/TZ/",
|
||||||
|
}
|
||||||
|
zoneInfoOnce sync.Once
|
||||||
|
zoneNames []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListZoneInfo() []string {
|
||||||
|
zoneInfoOnce.Do(func() {
|
||||||
|
zoneNames = make([]string, 0)
|
||||||
|
for _, zoneDir := range zoneDirs {
|
||||||
|
zoneNames = append(zoneNames, FindTimeZoneFiles(zoneDir)...)
|
||||||
|
}
|
||||||
|
sort.Strings(zoneNames)
|
||||||
|
})
|
||||||
|
return zoneNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindTimeZoneFiles(zoneDir string) []string {
|
||||||
|
dArr := make([]string, 0)
|
||||||
|
dArr = append(dArr, "")
|
||||||
|
arr := make([]string, 0)
|
||||||
|
|
||||||
|
for i := 0; i < len(dArr); i++ {
|
||||||
|
dir := dArr[i]
|
||||||
|
files, _ := os.ReadDir(filepath.Join(zoneDir, dir))
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() != strings.ToUpper(f.Name()[:1])+f.Name()[1:] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
dArr = append(dArr, filepath.Join(dir, f.Name()))
|
||||||
|
} else {
|
||||||
|
arr = append(arr, filepath.Join(dir, f.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
15
lists/zoneinfo_test.go
Normal file
15
lists/zoneinfo_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package lists
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListZoneInfo(t *testing.T) {
|
||||||
|
zoneinfos := ListZoneInfo()
|
||||||
|
assert.True(t, len(zoneinfos) > 4)
|
||||||
|
assert.Equal(t, "Africa/Abidjan", zoneinfos[0])
|
||||||
|
assert.Equal(t, "Africa/Accra", zoneinfos[1])
|
||||||
|
assert.Equal(t, "WET", zoneinfos[len(zoneinfos)-2])
|
||||||
|
assert.Equal(t, "Zulu", zoneinfos[len(zoneinfos)-1])
|
||||||
|
}
|
@ -37,15 +37,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="field_birthdate">Birthdate</label>
|
<label for="field_birthdate">Birthdate</label>
|
||||||
<input type="text" name="birthdate" id="field_birthdate" value="{{.User.Birthdate}}">
|
<input type="date" name="birthdate" id="field_birthdate" value="{{.User.Birthdate}}">
|
||||||
<label>Reset? <input type="checkbox" name="reset_birthdate"></label>
|
<label>Reset? <input type="checkbox" name="reset_birthdate"></label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="field_zoneinfo">Time Zone</label>
|
<label for="field_zoneinfo">Time Zone</label>
|
||||||
<input type="text" name="zoneinfo" id="field_zoneinfo" value="{{.User.ZoneInfo}}" list="list_zoneinfo">
|
<input type="text" name="zoneinfo" id="field_zoneinfo" value="{{.User.ZoneInfo}}" list="list_zoneinfo">
|
||||||
<datalist id="list_zoneinfo">
|
<datalist id="list_zoneinfo">
|
||||||
<!-- Fill in -->
|
{{range .ListZoneInfo}}
|
||||||
<option value="Europe/London"></option>
|
<option value="{{.}}"></option>
|
||||||
|
{{end}}
|
||||||
</datalist>
|
</datalist>
|
||||||
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||||
</div>
|
</div>
|
||||||
@ -53,8 +54,9 @@
|
|||||||
<label for="field_locale">Language</label>
|
<label for="field_locale">Language</label>
|
||||||
<input type="text" name="locale" id="field_locale" value="{{.User.Locale}}" list="list_locale">
|
<input type="text" name="locale" id="field_locale" value="{{.User.Locale}}" list="list_locale">
|
||||||
<datalist id="list_locale">
|
<datalist id="list_locale">
|
||||||
<!-- Fill in -->
|
{{range .ListLocale}}
|
||||||
<option value="en-US"></option>
|
<option value="{{.Value}}">{{.Label}}</option>
|
||||||
|
{{end}}
|
||||||
</datalist>
|
</datalist>
|
||||||
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
errors2 "errors"
|
errors2 "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
|
"github.com/1f349/tulip/lists"
|
||||||
"github.com/1f349/tulip/openid"
|
"github.com/1f349/tulip/openid"
|
||||||
"github.com/1f349/tulip/pages"
|
"github.com/1f349/tulip/pages"
|
||||||
"github.com/go-oauth2/oauth2/v4"
|
"github.com/go-oauth2/oauth2/v4"
|
||||||
@ -112,11 +113,16 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hs.DbTx(rw, func(tx *database.Tx) error {
|
var userWithName *database.User
|
||||||
userWithName, err := tx.GetUserDisplayName(auth.ID)
|
if hs.DbTx(rw, func(tx *database.Tx) (err error) {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if 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,
|
||||||
@ -124,8 +130,6 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("Failed to render page: edit: %s\n", err)
|
log.Printf("Failed to render page: edit: %s\n", err)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
r.POST("/logout", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
r.POST("/logout", hs.RequireAuthentication("403 Forbidden", http.StatusForbidden, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
||||||
lNonce, ok := auth.Session.Get("action-nonce")
|
lNonce, ok := auth.Session.Get("action-nonce")
|
||||||
@ -230,6 +234,8 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
|||||||
if err := pages.RenderPageTemplate(rw, "edit", map[string]any{
|
if err := pages.RenderPageTemplate(rw, "edit", map[string]any{
|
||||||
"User": user,
|
"User": user,
|
||||||
"Nonce": lNonce,
|
"Nonce": lNonce,
|
||||||
|
"ListZoneInfo": lists.ListZoneInfo(),
|
||||||
|
"ListLocale": lists.ListLocale(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("Failed to render page: edit: %s\n", err)
|
log.Printf("Failed to render page: edit: %s\n", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user