mirror of
https://github.com/1f349/tulip.git
synced 2024-12-22 08:14:13 +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) Scan(src any) error { return s.NullString.Scan(src) }
|
||||
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 {
|
||||
if string(bytes) == "null" {
|
||||
@ -40,6 +40,12 @@ func (s *NullStringScanner) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
return s.Scan(&a)
|
||||
}
|
||||
func (s NullStringScanner) String() string {
|
||||
if s.Null() {
|
||||
return ""
|
||||
}
|
||||
return s.NullString.String
|
||||
}
|
||||
|
||||
type NullDateScanner struct{ sql.NullTime }
|
||||
|
||||
@ -59,6 +65,12 @@ func (t *NullDateScanner) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
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 }
|
||||
|
||||
|
@ -9,6 +9,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func updatedAt() string {
|
||||
return time.Now().UTC().Format(time.DateTime)
|
||||
}
|
||||
|
||||
type Tx struct{ tx *sql.Tx }
|
||||
|
||||
func (t *Tx) Commit() error {
|
||||
@ -37,7 +41,7 @@ func (t *Tx) InsertUser(name, un, pw, email string) error {
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -96,7 +100,7 @@ func (t *Tx) ChangeUserPassword(sub uuid.UUID, pwOld, pwNew string) error {
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -129,7 +133,7 @@ WHERE subject = ?`,
|
||||
v.Birthdate,
|
||||
v.ZoneInfo.String(),
|
||||
v.Locale.String(),
|
||||
time.Now().Format(time.DateTime),
|
||||
updatedAt(),
|
||||
sub,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -16,7 +16,7 @@ func TestTx_ChangeUserPassword(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
d, err := Open("file::memory:")
|
||||
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)
|
||||
tx, err := d.Begin()
|
||||
assert.NoError(t, err)
|
||||
@ -39,7 +39,7 @@ func TestTx_ModifyUser(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
d, err := Open("file::memory:")
|
||||
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)
|
||||
tx, err := d.Begin()
|
||||
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>
|
||||
<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>
|
||||
</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>
|
||||
{{range .ListZoneInfo}}
|
||||
<option value="{{.}}"></option>
|
||||
{{end}}
|
||||
</datalist>
|
||||
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||
</div>
|
||||
@ -53,8 +54,9 @@
|
||||
<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>
|
||||
{{range .ListLocale}}
|
||||
<option value="{{.Value}}">{{.Label}}</option>
|
||||
{{end}}
|
||||
</datalist>
|
||||
<label>Reset? <input type="checkbox" name="reset_zoneinfo"></label>
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
errors2 "errors"
|
||||
"fmt"
|
||||
"github.com/1f349/tulip/database"
|
||||
"github.com/1f349/tulip/lists"
|
||||
"github.com/1f349/tulip/openid"
|
||||
"github.com/1f349/tulip/pages"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
@ -112,11 +113,16 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
||||
return
|
||||
}
|
||||
|
||||
hs.DbTx(rw, func(tx *database.Tx) error {
|
||||
userWithName, err := tx.GetUserDisplayName(auth.ID)
|
||||
var userWithName *database.User
|
||||
if hs.DbTx(rw, func(tx *database.Tx) (err error) {
|
||||
userWithName, err = tx.GetUserDisplayName(auth.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user display name: %w", err)
|
||||
}
|
||||
return
|
||||
}) {
|
||||
return
|
||||
}
|
||||
if err := pages.RenderPageTemplate(rw, "index", map[string]any{
|
||||
"Auth": auth,
|
||||
"User": userWithName,
|
||||
@ -124,8 +130,6 @@ func NewHttpServer(listen, domain string, db *database.DB, privKey []byte, clien
|
||||
}); err != nil {
|
||||
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) {
|
||||
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{
|
||||
"User": user,
|
||||
"Nonce": lNonce,
|
||||
"ListZoneInfo": lists.ListZoneInfo(),
|
||||
"ListLocale": lists.ListLocale(),
|
||||
}); err != nil {
|
||||
log.Printf("Failed to render page: edit: %s\n", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user