From d6017f1edf3d673ff09ce0f2e9bb08bc9deef06d Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 31 Jan 2023 00:50:17 +0100 Subject: [PATCH] feat: read/write improved names/pronouns for users, read/write improved fields/names/pronouns for members --- backend/db/entries.go | 75 +++++++-- backend/db/member.go | 73 +++++--- backend/db/names_pronouns.go | 224 +++---------------------- backend/db/user.go | 16 +- backend/routes/member/create_member.go | 30 ++-- backend/routes/member/get_member.go | 36 +--- backend/routes/member/patch_member.go | 61 +++---- backend/routes/user/get_user.go | 20 +-- backend/routes/user/patch_user.go | 55 +++--- frontend/lib/api-fetch.ts | 7 +- frontend/lib/api.ts | 4 +- 11 files changed, 231 insertions(+), 370 deletions(-) diff --git a/backend/db/entries.go b/backend/db/entries.go index 02e234a..7110da7 100644 --- a/backend/db/entries.go +++ b/backend/db/entries.go @@ -1,6 +1,11 @@ package db -import "codeberg.org/u1f320/pronouns.cc/backend/db/queries" +import ( + "fmt" + "strings" + + "codeberg.org/u1f320/pronouns.cc/backend/db/queries" +) type WordStatus int @@ -19,12 +24,63 @@ type FieldEntry struct { Status WordStatus `json:"status"` } +func (fe FieldEntry) Validate() string { + if fe.Value == "" { + return "value cannot be empty" + } + + if len([]rune(fe.Value)) > FieldEntryMaxLength { + return fmt.Sprintf("name must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(fe.Value))) + } + + if fe.Status == StatusUnknown || fe.Status >= wordStatusMax { + return fmt.Sprintf("status is invalid, must be between 1 and %d, is %d", wordStatusMax-1, fe.Status) + } + + return "" +} + type PronounEntry struct { Pronouns string `json:"pronouns"` DisplayText *string `json:"display_text"` Status WordStatus `json:"status"` } +func (p PronounEntry) Validate() string { + if p.Pronouns == "" { + return "pronouns cannot be empty" + } + + if p.DisplayText != nil { + if len([]rune(*p.DisplayText)) > FieldEntryMaxLength { + return fmt.Sprintf("display_text must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(*p.DisplayText))) + } + } + + if len([]rune(p.Pronouns)) > FieldEntryMaxLength { + return fmt.Sprintf("pronouns must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(p.Pronouns))) + } + + if p.Status == StatusUnknown || p.Status >= wordStatusMax { + return fmt.Sprintf("status is invalid, must be between 1 and %d, is %d", wordStatusMax-1, p.Status) + } + + return "" +} + +func (p PronounEntry) String() string { + if p.DisplayText != nil { + return *p.DisplayText + } + + split := strings.Split(p.Pronouns, "/") + if len(split) <= 2 { + return strings.Join(split, "/") + } + + return strings.Join(split[:1], "/") +} + func dbEntriesToFieldEntries(entries []queries.FieldEntry) []FieldEntry { out := make([]FieldEntry, len(entries)) for i := range entries { @@ -35,22 +91,13 @@ func dbEntriesToFieldEntries(entries []queries.FieldEntry) []FieldEntry { return out } -func dbPronounEntriesToPronounEntries(entries []queries.PronounEntry) []PronounEntry { - out := make([]PronounEntry, len(entries)) - for i := range entries { - out[i] = PronounEntry{ - *entries[i].Value, entries[i].DisplayValue, WordStatus(*entries[i].Status), - } - } - return out -} - func entriesToDBEntries(entries []FieldEntry) []queries.FieldEntry { out := make([]queries.FieldEntry, len(entries)) for i := range entries { status := int32(entries[i].Status) out[i] = queries.FieldEntry{ - &entries[i].Value, &status, + Value: &entries[i].Value, + Status: &status, } } return out @@ -61,7 +108,9 @@ func pronounEntriesToDBEntries(entries []PronounEntry) []queries.PronounEntry { for i := range entries { status := int32(entries[i].Status) out[i] = queries.PronounEntry{ - &entries[i].Pronouns, entries[i].DisplayText, &status, + Value: &entries[i].Pronouns, + DisplayValue: entries[i].DisplayText, + Status: &status, } } return out diff --git a/backend/db/member.go b/backend/db/member.go index b4d2e23..b700d79 100644 --- a/backend/db/member.go +++ b/backend/db/member.go @@ -3,6 +3,7 @@ package db import ( "context" + "codeberg.org/u1f320/pronouns.cc/backend/db/queries" "emperror.dev/errors" "github.com/georgysavva/scany/pgxscan" "github.com/jackc/pgconn" @@ -23,6 +24,8 @@ type Member struct { Bio *string AvatarURLs []string `db:"avatar_urls"` Links []string + Names []FieldEntry + Pronouns []PronounEntry } const ( @@ -30,19 +33,27 @@ const ( ErrMemberNameInUse = errors.Sentinel("member name already in use") ) -func (db *DB) getMember(ctx context.Context, q pgxscan.Querier, id xid.ID) (m Member, err error) { - sql, args, err := sq.Select("*").From("members").Where("id = ?", id).ToSql() +func (db *DB) getMember(ctx context.Context, q querier, id xid.ID) (m Member, err error) { + qm, err := queries.NewQuerier(q).GetMemberByID(ctx, id.String()) if err != nil { - return m, errors.Wrap(err, "building sql") + return m, errors.Wrap(err, "getting member from db") } - err = pgxscan.Get(ctx, q, &m, sql, args...) + userID, err := xid.FromString(qm.UserID) if err != nil { - if errors.Cause(err) == pgx.ErrNoRows { - return m, ErrMemberNotFound - } + return m, errors.Wrap(err, "parsing user ID") + } - return m, errors.Wrap(err, "retrieving member") + m = Member{ + ID: id, + UserID: userID, + Name: qm.Name, + DisplayName: qm.DisplayName, + Bio: qm.Bio, + AvatarURLs: qm.AvatarUrls, + Links: qm.Links, + Names: fieldEntriesFromDB(qm.Names), + Pronouns: pronounsFromDB(qm.Pronouns), } return m, nil } @@ -53,26 +64,35 @@ func (db *DB) Member(ctx context.Context, id xid.ID) (m Member, err error) { // UserMember returns a member scoped by user. func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (m Member, err error) { - sql, args, err := sq.Select("*").From("members"). - Where("user_id = ? and (id = ? or name = ?)", userID, memberRef, memberRef).ToSql() + qm, err := db.q.GetMemberByName(ctx, userID.String(), memberRef) if err != nil { - return m, errors.Wrap(err, "building sql") + return m, errors.Wrap(err, "getting member from db") } - err = pgxscan.Get(ctx, db, &m, sql, args...) + memberID, err := xid.FromString(qm.ID) if err != nil { - if errors.Cause(err) == pgx.ErrNoRows { - return m, ErrMemberNotFound - } + return m, errors.Wrap(err, "parsing member ID") + } - return m, errors.Wrap(err, "retrieving member") + m = Member{ + ID: memberID, + UserID: userID, + Name: qm.Name, + DisplayName: qm.DisplayName, + Bio: qm.Bio, + AvatarURLs: qm.AvatarUrls, + Links: qm.Links, + Names: fieldEntriesFromDB(qm.Names), + Pronouns: pronounsFromDB(qm.Pronouns), } return m, nil } // UserMembers returns all of a user's members, sorted by name. func (db *DB) UserMembers(ctx context.Context, userID xid.ID) (ms []Member, err error) { - sql, args, err := sq.Select("*").From("members").Where("user_id = ?", userID).OrderBy("name", "id").ToSql() + sql, args, err := sq.Select("id", "user_id", "name", "display_name", "bio", "avatar_urls"). + From("members").Where("user_id = ?", userID). + OrderBy("name", "id").ToSql() if err != nil { return nil, errors.Wrap(err, "building sql") } @@ -93,12 +113,13 @@ func (db *DB) CreateMember(ctx context.Context, tx pgx.Tx, userID xid.ID, name s sql, args, err := sq.Insert("members"). Columns("user_id", "id", "name", "display_name", "bio", "links"). Values(userID, xid.New(), name, displayName, bio, links). - Suffix("RETURNING *").ToSql() + Suffix("RETURNING id").ToSql() if err != nil { return m, errors.Wrap(err, "building sql") } - err = pgxscan.Get(ctx, tx, &m, sql, args...) + var id xid.ID + err = tx.QueryRow(ctx, sql, args...).Scan(&id) if err != nil { pge := &pgconn.PgError{} if errors.As(err, &pge) { @@ -111,6 +132,11 @@ func (db *DB) CreateMember(ctx context.Context, tx pgx.Tx, userID xid.ID, name s return m, errors.Wrap(err, "executing query") } + m, err = db.getMember(ctx, tx, id) + if err != nil { + return m, errors.Wrap(err, "getting created member") + } + return m, nil } @@ -192,12 +218,12 @@ func (db *DB) UpdateMember( } } - sql, args, err := builder.Suffix("RETURNING *").ToSql() + sql, args, err := builder.ToSql() if err != nil { return m, errors.Wrap(err, "building sql") } - err = pgxscan.Get(ctx, tx, &m, sql, args...) + _, err = tx.Exec(ctx, sql, args...) if err != nil { pge := &pgconn.PgError{} if errors.As(err, &pge) { @@ -209,5 +235,10 @@ func (db *DB) UpdateMember( return m, errors.Wrap(err, "executing sql") } + m, err = db.getMember(ctx, tx, id) + if err != nil { + return m, errors.Wrap(err, "getting member") + } + return m, nil } diff --git a/backend/db/names_pronouns.go b/backend/db/names_pronouns.go index e5d0907..c58b0ad 100644 --- a/backend/db/names_pronouns.go +++ b/backend/db/names_pronouns.go @@ -2,234 +2,52 @@ package db import ( "context" - "fmt" - "strings" "codeberg.org/u1f320/pronouns.cc/backend/db/queries" "emperror.dev/errors" - "github.com/georgysavva/scany/pgxscan" "github.com/jackc/pgx/v4" "github.com/rs/xid" ) -type Name struct { - ID int64 `json:"-"` - Name string `json:"name"` - Status WordStatus `json:"status"` -} - -func (n Name) Validate() string { - if n.Name == "" { - return "name cannot be empty" - } - - if len([]rune(n.Name)) > FieldEntryMaxLength { - return fmt.Sprintf("name must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(n.Name))) - } - - if n.Status == StatusUnknown || n.Status >= wordStatusMax { - return fmt.Sprintf("status is invalid, must be between 1 and %d, is %d", wordStatusMax-1, n.Status) - } - - return "" -} - -type Pronoun struct { - ID int64 `json:"-"` - DisplayText *string `json:"display_text"` - Pronouns string `json:"pronouns"` - Status WordStatus `json:"status"` -} - -func (p Pronoun) Validate() string { - if p.Pronouns == "" { - return "pronouns cannot be empty" - } - - if p.DisplayText != nil { - if len([]rune(*p.DisplayText)) > FieldEntryMaxLength { - return fmt.Sprintf("display_text must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(*p.DisplayText))) - } - } - - if len([]rune(p.Pronouns)) > FieldEntryMaxLength { - return fmt.Sprintf("pronouns must be %d characters or less, is %d", FieldEntryMaxLength, len([]rune(p.Pronouns))) - } - - if p.Status == StatusUnknown || p.Status >= wordStatusMax { - return fmt.Sprintf("status is invalid, must be between 1 and %d, is %d", wordStatusMax-1, p.Status) - } - - return "" -} - -func (p Pronoun) String() string { - if p.DisplayText != nil { - return *p.DisplayText - } - - split := strings.Split(p.Pronouns, "/") - if len(split) <= 2 { - return strings.Join(split, "/") - } - - return strings.Join(split[:1], "/") -} - -func (db *DB) SetUserNames(ctx context.Context, tx pgx.Tx, userID xid.ID, names []Name) (err error) { - sql, args, err := sq.Delete("user_names").Where("user_id = ?", userID).ToSql() +func (db *DB) SetUserNamesPronouns(ctx context.Context, tx pgx.Tx, userID xid.ID, names []FieldEntry, pronouns []PronounEntry) (err error) { + _, err = queries.NewQuerier(tx).UpdateUserNamesPronouns(ctx, queries.UpdateUserNamesPronounsParams{ + ID: userID.String(), + Names: entriesToDBEntries(names), + Pronouns: pronounEntriesToDBEntries(pronouns), + }) if err != nil { - return errors.Wrap(err, "building sql") - } - - _, err = tx.Exec(ctx, sql, args...) - if err != nil { - return errors.Wrap(err, "deleting existing names") - } - - _, err = tx.CopyFrom(ctx, - pgx.Identifier{"user_names"}, - []string{"user_id", "name", "status"}, - pgx.CopyFromSlice(len(names), func(i int) ([]any, error) { - return []any{ - userID, - names[i].Name, - names[i].Status, - }, nil - })) - if err != nil { - return errors.Wrap(err, "inserting new names") + return errors.Wrap(err, "executing update names/pronouns query") } return nil } -func (db *DB) SetUserPronouns(ctx context.Context, tx pgx.Tx, userID xid.ID, names []Pronoun) (err error) { - sql, args, err := sq.Delete("user_pronouns").Where("user_id = ?", userID).ToSql() +func (db *DB) SetMemberNamesPronouns(ctx context.Context, tx pgx.Tx, memberID xid.ID, names []FieldEntry, pronouns []PronounEntry) (err error) { + _, err = queries.NewQuerier(tx).UpdateMemberNamesPronouns(ctx, queries.UpdateMemberNamesPronounsParams{ + ID: memberID.String(), + Names: entriesToDBEntries(names), + Pronouns: pronounEntriesToDBEntries(pronouns), + }) if err != nil { - return errors.Wrap(err, "building sql") - } - - _, err = tx.Exec(ctx, sql, args...) - if err != nil { - return errors.Wrap(err, "deleting existing pronouns") - } - - _, err = tx.CopyFrom(ctx, - pgx.Identifier{"user_pronouns"}, - []string{"user_id", "pronouns", "display_text", "status"}, - pgx.CopyFromSlice(len(names), func(i int) ([]any, error) { - return []any{ - userID, - names[i].Pronouns, - names[i].DisplayText, - names[i].Status, - }, nil - })) - if err != nil { - return errors.Wrap(err, "inserting new pronouns") + return errors.Wrap(err, "executing update names/pronouns query") } return nil } -func (db *DB) MemberNames(ctx context.Context, memberID xid.ID) (ns []Name, err error) { - sql, args, err := sq.Select("id", "name", "status").From("member_names").Where("member_id = ?", memberID).OrderBy("id").ToSql() - if err != nil { - return nil, errors.Wrap(err, "building sql") - } - - err = pgxscan.Select(ctx, db, &ns, sql, args...) - if err != nil { - return nil, errors.Wrap(err, "executing query") - } - return ns, nil -} - -func (db *DB) MemberPronouns(ctx context.Context, memberID xid.ID) (ps []Pronoun, err error) { - sql, args, err := sq. - Select("id", "display_text", "pronouns", "status"). - From("member_pronouns").Where("member_id = ?", memberID). - OrderBy("id").ToSql() - if err != nil { - return nil, errors.Wrap(err, "building sql") - } - - err = pgxscan.Select(ctx, db, &ps, sql, args...) - if err != nil { - return nil, errors.Wrap(err, "executing query") - } - return ps, nil -} - -func (db *DB) SetMemberNames(ctx context.Context, tx pgx.Tx, memberID xid.ID, names []Name) (err error) { - sql, args, err := sq.Delete("member_names").Where("member_id = ?", memberID).ToSql() - if err != nil { - return errors.Wrap(err, "building sql") - } - - _, err = tx.Exec(ctx, sql, args...) - if err != nil { - return errors.Wrap(err, "deleting existing names") - } - - _, err = tx.CopyFrom(ctx, - pgx.Identifier{"member_names"}, - []string{"member_id", "name", "status"}, - pgx.CopyFromSlice(len(names), func(i int) ([]any, error) { - return []any{ - memberID, - names[i].Name, - names[i].Status, - }, nil - })) - if err != nil { - return errors.Wrap(err, "inserting new names") - } - return nil -} - -func (db *DB) SetMemberPronouns(ctx context.Context, tx pgx.Tx, memberID xid.ID, names []Pronoun) (err error) { - sql, args, err := sq.Delete("member_pronouns").Where("member_id = ?", memberID).ToSql() - if err != nil { - return errors.Wrap(err, "building sql") - } - - _, err = tx.Exec(ctx, sql, args...) - if err != nil { - return errors.Wrap(err, "deleting existing pronouns") - } - - _, err = tx.CopyFrom(ctx, - pgx.Identifier{"member_pronouns"}, - []string{"member_id", "pronouns", "display_text", "status"}, - pgx.CopyFromSlice(len(names), func(i int) ([]any, error) { - return []any{ - memberID, - names[i].Pronouns, - names[i].DisplayText, - names[i].Status, - }, nil - })) - if err != nil { - return errors.Wrap(err, "inserting new pronouns") - } - return nil -} - -func namesFromDB(dn []queries.FieldEntry) []Name { - names := make([]Name, len(dn)) +func fieldEntriesFromDB(dn []queries.FieldEntry) []FieldEntry { + names := make([]FieldEntry, len(dn)) for i := range dn { - names[i] = Name{ - Name: *dn[i].Value, + names[i] = FieldEntry{ + Value: *dn[i].Value, Status: WordStatus(*dn[i].Status), } } return names } -func pronounsFromDB(dn []queries.PronounEntry) []Pronoun { - pronouns := make([]Pronoun, len(dn)) +func pronounsFromDB(dn []queries.PronounEntry) []PronounEntry { + pronouns := make([]PronounEntry, len(dn)) for i := range dn { - pronouns[i] = Pronoun{ + pronouns[i] = PronounEntry{ DisplayText: dn[i].DisplayValue, Pronouns: *dn[i].Value, Status: WordStatus(*dn[i].Status), diff --git a/backend/db/user.go b/backend/db/user.go index 4b68afe..589cbc2 100644 --- a/backend/db/user.go +++ b/backend/db/user.go @@ -22,8 +22,8 @@ type User struct { AvatarURLs []string `db:"avatar_urls"` Links []string - Names []Name - Pronouns []Pronoun + Names []FieldEntry + Pronouns []PronounEntry Discord *string DiscordUsername *string @@ -133,7 +133,7 @@ func (db *DB) getUser(ctx context.Context, q querier, id xid.ID) (u User, err er DisplayName: qu.DisplayName, Bio: qu.Bio, AvatarURLs: qu.AvatarUrls, - Names: namesFromDB(qu.Names), + Names: fieldEntriesFromDB(qu.Names), Pronouns: pronounsFromDB(qu.Pronouns), Links: qu.Links, Discord: qu.Discord, @@ -171,7 +171,7 @@ func (db *DB) Username(ctx context.Context, name string) (u User, err error) { DisplayName: qu.DisplayName, Bio: qu.Bio, AvatarURLs: qu.AvatarUrls, - Names: namesFromDB(qu.Names), + Names: fieldEntriesFromDB(qu.Names), Pronouns: pronounsFromDB(qu.Pronouns), Links: qu.Links, Discord: qu.Discord, @@ -260,15 +260,19 @@ func (db *DB) UpdateUser( } } - sql, args, err := builder.Suffix("RETURNING *").ToSql() + sql, args, err := builder.ToSql() if err != nil { return u, errors.Wrap(err, "building sql") } - err = pgxscan.Get(ctx, tx, &u, sql, args...) + _, err = tx.Exec(ctx, sql, args...) if err != nil { return u, errors.Wrap(err, "executing sql") } + u, err = db.getUser(ctx, tx, id) + if err != nil { + return u, errors.Wrap(err, "getting updated user") + } return u, nil } diff --git a/backend/routes/member/create_member.go b/backend/routes/member/create_member.go index 840ec12..50ff54e 100644 --- a/backend/routes/member/create_member.go +++ b/backend/routes/member/create_member.go @@ -12,14 +12,14 @@ import ( ) type CreateMemberRequest struct { - Name string `json:"name"` - DisplayName *string `json:"display_name"` - Bio string `json:"bio"` - Avatar string `json:"avatar"` - Links []string `json:"links"` - Names []db.Name `json:"names"` - Pronouns []db.Pronoun `json:"pronouns"` - Fields []db.Field `json:"fields"` + Name string `json:"name"` + DisplayName *string `json:"display_name"` + Bio string `json:"bio"` + Avatar string `json:"avatar"` + Links []string `json:"links"` + Names []db.FieldEntry `json:"names"` + Pronouns []db.PronounEntry `json:"pronouns"` + Fields []db.Field `json:"fields"` } func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error) { @@ -92,16 +92,14 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error } // set names, pronouns, fields - err = s.DB.SetMemberNames(ctx, tx, m.ID, cmr.Names) + err = s.DB.SetMemberNamesPronouns(ctx, tx, m.ID, cmr.Names, cmr.Pronouns) if err != nil { - log.Errorf("setting names for member %v: %v", m.ID, err) - return err - } - err = s.DB.SetMemberPronouns(ctx, tx, m.ID, cmr.Pronouns) - if err != nil { - log.Errorf("setting pronouns for member %v: %v", m.ID, err) + log.Errorf("setting names and pronouns for member %v: %v", m.ID, err) return err } + m.Names = cmr.Names + m.Pronouns = cmr.Pronouns + err = s.DB.SetMemberFields(ctx, tx, m.ID, cmr.Fields) if err != nil { log.Errorf("setting fields for member %v: %v", m.ID, err) @@ -144,7 +142,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error return errors.Wrap(err, "committing transaction") } - render.JSON(w, r, dbMemberToMember(u, m, cmr.Names, cmr.Pronouns, cmr.Fields)) + render.JSON(w, r, dbMemberToMember(u, m, cmr.Fields)) return nil } diff --git a/backend/routes/member/get_member.go b/backend/routes/member/get_member.go index 0bfa1bc..8020c17 100644 --- a/backend/routes/member/get_member.go +++ b/backend/routes/member/get_member.go @@ -19,14 +19,14 @@ type GetMemberResponse struct { AvatarURLs []string `json:"avatar_urls"` Links []string `json:"links"` - Names []db.Name `json:"names"` - Pronouns []db.Pronoun `json:"pronouns"` - Fields []db.Field `json:"fields"` + Names []db.FieldEntry `json:"names"` + Pronouns []db.PronounEntry `json:"pronouns"` + Fields []db.Field `json:"fields"` User PartialUser `json:"user"` } -func dbMemberToMember(u db.User, m db.Member, names []db.Name, pronouns []db.Pronoun, fields []db.Field) GetMemberResponse { +func dbMemberToMember(u db.User, m db.Member, fields []db.Field) GetMemberResponse { return GetMemberResponse{ ID: m.ID, Name: m.Name, @@ -35,8 +35,8 @@ func dbMemberToMember(u db.User, m db.Member, names []db.Name, pronouns []db.Pro AvatarURLs: m.AvatarURLs, Links: m.Links, - Names: names, - Pronouns: pronouns, + Names: m.Names, + Pronouns: m.Pronouns, Fields: fields, User: PartialUser{ @@ -77,22 +77,12 @@ func (s *Server) getMember(w http.ResponseWriter, r *http.Request) error { return err } - names, err := s.DB.MemberNames(ctx, m.ID) - if err != nil { - return err - } - - pronouns, err := s.DB.MemberPronouns(ctx, m.ID) - if err != nil { - return err - } - fields, err := s.DB.MemberFields(ctx, m.ID) if err != nil { return err } - render.JSON(w, r, dbMemberToMember(u, m, names, pronouns, fields)) + render.JSON(w, r, dbMemberToMember(u, m, fields)) return nil } @@ -113,22 +103,12 @@ func (s *Server) getUserMember(w http.ResponseWriter, r *http.Request) error { } } - names, err := s.DB.MemberNames(ctx, m.ID) - if err != nil { - return err - } - - pronouns, err := s.DB.MemberPronouns(ctx, m.ID) - if err != nil { - return err - } - fields, err := s.DB.MemberFields(ctx, m.ID) if err != nil { return err } - render.JSON(w, r, dbMemberToMember(u, m, names, pronouns, fields)) + render.JSON(w, r, dbMemberToMember(u, m, fields)) return nil } diff --git a/backend/routes/member/patch_member.go b/backend/routes/member/patch_member.go index 1696eef..61f39d6 100644 --- a/backend/routes/member/patch_member.go +++ b/backend/routes/member/patch_member.go @@ -14,14 +14,14 @@ import ( ) type PatchMemberRequest struct { - Name *string `json:"name"` - Bio *string `json:"bio"` - DisplayName *string `json:"display_name"` - Links *[]string `json:"links"` - Names *[]db.Name `json:"names"` - Pronouns *[]db.Pronoun `json:"pronouns"` - Fields *[]db.Field `json:"fields"` - Avatar *string `json:"avatar"` + Name *string `json:"name"` + Bio *string `json:"bio"` + DisplayName *string `json:"display_name"` + Links *[]string `json:"links"` + Names *[]db.FieldEntry `json:"names"` + Pronouns *[]db.PronounEntry `json:"pronouns"` + Fields *[]db.Field `json:"fields"` + Avatar *string `json:"avatar"` } func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { @@ -169,42 +169,27 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { } - var ( - names []db.Name - pronouns []db.Pronoun - fields []db.Field - ) + if req.Names != nil || req.Pronouns != nil { + names := m.Names + pronouns := m.Pronouns - if req.Names != nil { - err = s.DB.SetMemberNames(ctx, tx, id, *req.Names) + if req.Names != nil { + names = *req.Names + } + if req.Pronouns != nil { + pronouns = *req.Pronouns + } + + err = s.DB.SetMemberNamesPronouns(ctx, tx, id, names, pronouns) if err != nil { log.Errorf("setting names for member %v: %v", id, err) return err } - names = *req.Names - } else { - names, err = s.DB.MemberNames(ctx, id) - if err != nil { - log.Errorf("getting names for member %v: %v", id, err) - return err - } - } - - if req.Pronouns != nil { - err = s.DB.SetMemberPronouns(ctx, tx, id, *req.Pronouns) - if err != nil { - log.Errorf("setting pronouns for member %v: %v", id, err) - return err - } - pronouns = *req.Pronouns - } else { - pronouns, err = s.DB.MemberPronouns(ctx, id) - if err != nil { - log.Errorf("getting fields for member %v: %v", id, err) - return err - } + m.Names = names + m.Pronouns = pronouns } + var fields []db.Field if req.Fields != nil { err = s.DB.SetMemberFields(ctx, tx, id, *req.Fields) if err != nil { @@ -232,6 +217,6 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { } // echo the updated member back on success - render.JSON(w, r, dbMemberToMember(u, m, names, pronouns, fields)) + render.JSON(w, r, dbMemberToMember(u, m, fields)) return nil } diff --git a/backend/routes/user/get_user.go b/backend/routes/user/get_user.go index 78b243c..ffbf56b 100644 --- a/backend/routes/user/get_user.go +++ b/backend/routes/user/get_user.go @@ -12,16 +12,16 @@ import ( ) type GetUserResponse struct { - ID xid.ID `json:"id"` - Username string `json:"name"` - DisplayName *string `json:"display_name"` - Bio *string `json:"bio"` - AvatarURLs []string `json:"avatar_urls"` - Links []string `json:"links"` - Names []db.Name `json:"names"` - Pronouns []db.Pronoun `json:"pronouns"` - Members []PartialMember `json:"members"` - Fields []db.Field `json:"fields"` + ID xid.ID `json:"id"` + Username string `json:"name"` + DisplayName *string `json:"display_name"` + Bio *string `json:"bio"` + AvatarURLs []string `json:"avatar_urls"` + Links []string `json:"links"` + Names []db.FieldEntry `json:"names"` + Pronouns []db.PronounEntry `json:"pronouns"` + Members []PartialMember `json:"members"` + Fields []db.Field `json:"fields"` } type GetMeResponse struct { diff --git a/backend/routes/user/patch_user.go b/backend/routes/user/patch_user.go index b46e9f7..2550b2e 100644 --- a/backend/routes/user/patch_user.go +++ b/backend/routes/user/patch_user.go @@ -12,14 +12,14 @@ import ( ) type PatchUserRequest struct { - Username *string `json:"username"` - DisplayName *string `json:"display_name"` - Bio *string `json:"bio"` - Links *[]string `json:"links"` - Names *[]db.Name `json:"names"` - Pronouns *[]db.Pronoun `json:"pronouns"` - Fields *[]db.Field `json:"fields"` - Avatar *string `json:"avatar"` + Username *string `json:"username"` + DisplayName *string `json:"display_name"` + Bio *string `json:"bio"` + Links *[]string `json:"links"` + Names *[]db.FieldEntry `json:"names"` + Pronouns *[]db.PronounEntry `json:"pronouns"` + Fields *[]db.Field `json:"fields"` + Avatar *string `json:"avatar"` } // patchUser parses a PatchUserRequest and updates the user with the given ID. @@ -159,26 +159,27 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { return err } + if req.Names != nil || req.Pronouns != nil { + names := u.Names + pronouns := u.Pronouns + + if req.Names != nil { + names = *req.Names + } + if req.Pronouns != nil { + pronouns = *req.Pronouns + } + + err = s.DB.SetUserNamesPronouns(ctx, tx, claims.UserID, names, pronouns) + if err != nil { + log.Errorf("setting names for member %v: %v", claims.UserID, err) + return err + } + u.Names = names + u.Pronouns = pronouns + } + var fields []db.Field - - if req.Names != nil { - err = s.DB.SetUserNames(ctx, tx, claims.UserID, *req.Names) - if err != nil { - log.Errorf("setting names for user %v: %v", claims.UserID, err) - return err - } - u.Names = *req.Names - } - - if req.Pronouns != nil { - err = s.DB.SetUserPronouns(ctx, tx, claims.UserID, *req.Pronouns) - if err != nil { - log.Errorf("setting pronouns for user %v: %v", claims.UserID, err) - return err - } - u.Pronouns = *req.Pronouns - } - if req.Fields != nil { err = s.DB.SetUserFields(ctx, tx, claims.UserID, *req.Fields) if err != nil { diff --git a/frontend/lib/api-fetch.ts b/frontend/lib/api-fetch.ts index 1d8f590..207d7f5 100644 --- a/frontend/lib/api-fetch.ts +++ b/frontend/lib/api-fetch.ts @@ -18,7 +18,7 @@ export type PartialMember = PartialPerson; export interface _Person extends PartialPerson { bio: string | null; links: Arr; - names: Arr; + names: Arr; pronouns: Arr; fields: Arr; } @@ -38,11 +38,6 @@ export interface MeUser extends User { discord_username: string | null; } -export interface Name { - name: string; - status: WordStatus; -} - export interface Pronoun { display_text?: string; pronouns: string; diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index a9e8a81..f5194b4 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -164,11 +164,11 @@ export class Label { } export class Name extends Label { - constructor({ name, status }: API.Name) { + constructor({ value, status }: API.FieldEntry) { super({ type: LabelType.Name, displayText: null, - text: name, + text: value, status, }); }