#2: i18n#9
Conversation
mrdimidium
left a comment
There was a problem hiding this comment.
I also don't understand why you're committing the generated code? Is there a reason it wasn't included in gitignore?
| ALTER TABLE workers DISABLE ROW LEVEL SECURITY; | ||
| `) | ||
|
|
||
| migrator.AppendMigration("add_user_settings", ` |
There was a problem hiding this comment.
There may be more locale settings, it's more convenient to store them all in a single locale column with a custom type.
For languages, don't validate here: adding a locale will require migration.
Regarding the date/time format, don't store the format string. There are about three formats; store a simple enum here that specifies the type.
Fot timezone postgres already has validator: https://www.postgresql.org/docs/9.2/view-pg-timezone-names.html
| rpc WorkerList(WorkerListRequest) returns (WorkerListResponse); | ||
| rpc WorkerDelete(WorkerDeleteRequest) returns (WorkerDeleteResponse); | ||
|
|
||
| rpc UserSettingsUpdate(UserSettingsUpdateRequest) returns (UserSettingsUpdateResponse); |
There was a problem hiding this comment.
You already have UserUpdate method
I thought it's needed |
|
@PaulineNemchak, I would try to remove the generated files in gitignore (as we already do with proto). |
0ce0bf3 to
b6f7e74
Compare
| }]; | ||
| } | ||
| message WorkerDeleteResponse {} | ||
|
|
There was a problem hiding this comment.
No need to mess up the formatting
| optional string email = 2 [(buf.validate.field).string.email = true]; | ||
| optional string password = 3 [(buf.validate.field).string.min_len = 1]; | ||
| optional string locale = 4 [(buf.validate.field).string = { | ||
| in: ["en-GB", "en-US"] |
There was a problem hiding this comment.
As with db, the check here only gets in the way and requires updating the API when a language is added. It's just a string.
| env: | ||
| CGO_ENABLED: "1" # required for -race | ||
| cmds: | ||
| - go test -race -count=1 ./... |
| return | ||
| } | ||
|
|
||
| if locale := parseLocale(r.Header.Get("Accept-Language")); locale != "" { |
There was a problem hiding this comment.
Not exactly. You define a title for each page if the database doesn't have a value for it, rather than storing one upon login.
| func parseLocale(acceptLang string) string { | ||
| for _, part := range strings.Split(acceptLang, ",") { | ||
| tag := strings.TrimSpace(strings.SplitN(part, ";", 2)[0]) | ||
| if strings.HasPrefix(tag, "en-US") { |
There was a problem hiding this comment.
You need a value parser (there may be several with weights), and not just a switch-case
| // For unauthenticated actors no DB call is made. | ||
| func localeFor(ctx context.Context, actor Actor, db *DB) string { | ||
| if actor.Kind() == KindUser { | ||
| if u, err := db.UserGet(ctx, actor, UserByID(actor.UserID())); err == nil && u.Locale != nil { |
There was a problem hiding this comment.
I don't think you need a separate request here, Actor is already a user, expand it.
d72f10a to
9b6eb63
Compare
| linux-ppc64le, | ||
| linux-loong64, | ||
| linux-s390x, | ||
| ] |
There was a problem hiding this comment.
What are these changes for?
| optional string email = 2 [(buf.validate.field).string.email = true]; | ||
| optional string password = 3 [(buf.validate.field).string.min_len = 1]; | ||
| optional string locale = 4 [(buf.validate.field).string.max_len = 32]; | ||
| optional string date_format = 5 [(buf.validate.field).string = { |
There was a problem hiding this comment.
I think it's not a string, but an enum. String validation also don't required in proto files
| bytes id = 1; | ||
| string email = 2; | ||
| google.protobuf.Timestamp created_at = 3; | ||
| optional string locale = 4; |
There was a problem hiding this comment.
Like in a database locale should be an object
| // Invalidates all sessions when password changes. | ||
| func (db *DB) UserUpdate(ctx context.Context, actor Actor, ref UserRef, email *string, password *string, pepper []byte) error { | ||
| if email == nil && password == nil { | ||
| func (db *DB) UserUpdate(ctx context.Context, actor Actor, ref UserRef, email *string, password *string, pepper []byte, locale, dateFormat, timezone *string) error { |
There was a problem hiding this comment.
Create an props object for aguments
| if password != nil { | ||
| if _, err := tx.Exec(ctx, `DELETE FROM sessions WHERE user_id = $1`, id); err != nil { | ||
| if locale != nil || dateFormat != nil || timezone != nil { | ||
| if _, err := tx.Exec(ctx, |
There was a problem hiding this comment.
It's more reliable prepare one sql request, use builder
|
|
||
| // UserSettingsInit sets locale on first login from browser hints. | ||
| // Only updates columns that are still NULL — never overwrites a user-chosen value. | ||
| func (db *DB) UserSettingsInit(ctx context.Context, actor Actor, id UserID, locale string) error { |
There was a problem hiding this comment.
You shouldn't init user locale after login, use default from browser if it wasn't redefined in user settings
| return | ||
| } | ||
| data := map[string]any{ | ||
| "user": map[string]string{"email": actor.Email()}, |
There was a problem hiding this comment.
Just save user object to "user", without flatten fields
4b3ac73 to
9376647
Compare
8c45420 to
3291adf
Compare
| DateFormat date_format = 2; | ||
| } | ||
|
|
||
| message User { |
There was a problem hiding this comment.
| message User { | |
| message User { | |
| bytes id = 1; | |
| string email = 2; | |
| google.protobuf.Timestamp created_at = 3; | |
| optional Locale locale = 4; | |
| optional string timezone = 5; | |
| } |
| type DateFormat string | ||
|
|
||
| const ( | ||
| DateFormatDMY DateFormat = "dmy" |
There was a problem hiding this comment.
change to numbers
| // LocaleSettings holds a user's language and date-format preferences, | ||
| // stored as a composite column in the database. | ||
| type LocaleSettings struct { | ||
| Language *string |
There was a problem hiding this comment.
they are not optional
| type UserUpdateParams struct { | ||
| Email *string | ||
| Password *string | ||
| Pepper []byte |
There was a problem hiding this comment.
user doesn't have pepper
| CREATE TYPE locale_settings AS ( | ||
| language TEXT, | ||
| date_format date_format_t, | ||
| timezone timezone_t |
| ); | ||
| ALTER TABLE users ADD COLUMN locale locale_settings; | ||
| `, ` | ||
| ALTER TABLE users DROP COLUMN locale; |
There was a problem hiding this comment.
add to existing table instead
| `) | ||
|
|
||
| migrator.AppendMigration("add_user_settings", ` | ||
| CREATE DOMAIN timezone_t AS TEXT |
| // UserUpdate updates a user's email and/or password. | ||
| // UserUpdate updates a user's email, password, and/or preferences. | ||
| // Invalidates all sessions when password changes. | ||
| func (db *DB) UserUpdate(ctx context.Context, actor Actor, ref UserRef, email *string, password *string, pepper []byte) error { |
There was a problem hiding this comment.
returt pepper
| // licenses serves /about/licenses. Public: no auth, no CSRF. | ||
| func (h *webHandler) licenses(w http.ResponseWriter, _ *http.Request) { | ||
| h.assets.renderPage(w, "licenses", http.StatusOK, licensesData) | ||
| func (h *webHandler) licenses(w http.ResponseWriter, r *http.Request) { |
There was a problem hiding this comment.
write middleware instead
Closes #2