Merge pull request 'names/pronouns/fields rework' (#17) from better-fields into main
Reviewed-on: https://codeberg.org/u1f320/pronouns.cc/pulls/17
This commit is contained in:
		
						commit
						d11f296026
					
				
					 22 changed files with 1632 additions and 556 deletions
				
			
		
							
								
								
									
										4
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -9,3 +9,7 @@ seeddb: | ||||||
| .PHONY: backend | .PHONY: backend | ||||||
| backend: | backend: | ||||||
| 	CGO_ENABLED=0 go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD`" ./backend | 	CGO_ENABLED=0 go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD`" ./backend | ||||||
|  | 
 | ||||||
|  | .PHONY: generate | ||||||
|  | generate: | ||||||
|  | 	go generate ./... | ||||||
|  |  | ||||||
|  | @ -7,9 +7,12 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 
 | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
| 	"codeberg.org/u1f320/pronouns.cc/backend/log" | 	"codeberg.org/u1f320/pronouns.cc/backend/log" | ||||||
| 	"emperror.dev/errors" | 	"emperror.dev/errors" | ||||||
| 	"github.com/Masterminds/squirrel" | 	"github.com/Masterminds/squirrel" | ||||||
|  | 	"github.com/jackc/pgconn" | ||||||
|  | 	"github.com/jackc/pgx/v4" | ||||||
| 	"github.com/jackc/pgx/v4/pgxpool" | 	"github.com/jackc/pgx/v4/pgxpool" | ||||||
| 	"github.com/mediocregopher/radix/v4" | 	"github.com/mediocregopher/radix/v4" | ||||||
| 	"github.com/minio/minio-go/v7" | 	"github.com/minio/minio-go/v7" | ||||||
|  | @ -20,6 +23,12 @@ var sq = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) | ||||||
| 
 | 
 | ||||||
| const ErrNothingToUpdate = errors.Sentinel("nothing to update") | const ErrNothingToUpdate = errors.Sentinel("nothing to update") | ||||||
| 
 | 
 | ||||||
|  | type querier interface { | ||||||
|  | 	Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) | ||||||
|  | 	QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row | ||||||
|  | 	Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type DB struct { | type DB struct { | ||||||
| 	*pgxpool.Pool | 	*pgxpool.Pool | ||||||
| 
 | 
 | ||||||
|  | @ -28,6 +37,8 @@ type DB struct { | ||||||
| 	minio       *minio.Client | 	minio       *minio.Client | ||||||
| 	minioBucket string | 	minioBucket string | ||||||
| 	baseURL     *url.URL | 	baseURL     *url.URL | ||||||
|  | 
 | ||||||
|  | 	q queries.Querier | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func New() (*DB, error) { | func New() (*DB, error) { | ||||||
|  | @ -67,6 +78,8 @@ func New() (*DB, error) { | ||||||
| 		minio:       minioClient, | 		minio:       minioClient, | ||||||
| 		minioBucket: os.Getenv("MINIO_BUCKET"), | 		minioBucket: os.Getenv("MINIO_BUCKET"), | ||||||
| 		baseURL:     baseURL, | 		baseURL:     baseURL, | ||||||
|  | 
 | ||||||
|  | 		q: queries.NewQuerier(pool), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return db, nil | 	return db, nil | ||||||
|  |  | ||||||
							
								
								
									
										117
									
								
								backend/db/entries.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								backend/db/entries.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | ||||||
|  | package db | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type WordStatus int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	StatusUnknown     WordStatus = 0 | ||||||
|  | 	StatusFavourite   WordStatus = 1 | ||||||
|  | 	StatusOkay        WordStatus = 2 | ||||||
|  | 	StatusJokingly    WordStatus = 3 | ||||||
|  | 	StatusFriendsOnly WordStatus = 4 | ||||||
|  | 	StatusAvoid       WordStatus = 5 | ||||||
|  | 	wordStatusMax     WordStatus = 6 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type FieldEntry struct { | ||||||
|  | 	Value  string     `json:"value"` | ||||||
|  | 	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 { | ||||||
|  | 		out[i] = FieldEntry{ | ||||||
|  | 			*entries[i].Value, 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{ | ||||||
|  | 			Value:  &entries[i].Value, | ||||||
|  | 			Status: &status, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func pronounEntriesToDBEntries(entries []PronounEntry) []queries.PronounEntry { | ||||||
|  | 	out := make([]queries.PronounEntry, len(entries)) | ||||||
|  | 	for i := range entries { | ||||||
|  | 		status := int32(entries[i].Status) | ||||||
|  | 		out[i] = queries.PronounEntry{ | ||||||
|  | 			Value:        &entries[i].Pronouns, | ||||||
|  | 			DisplayValue: entries[i].DisplayText, | ||||||
|  | 			Status:       &status, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | @ -4,8 +4,8 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
| 	"emperror.dev/errors" | 	"emperror.dev/errors" | ||||||
| 	"github.com/georgysavva/scany/pgxscan" |  | ||||||
| 	"github.com/jackc/pgx/v4" | 	"github.com/jackc/pgx/v4" | ||||||
| 	"github.com/rs/xid" | 	"github.com/rs/xid" | ||||||
| ) | ) | ||||||
|  | @ -18,13 +18,9 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Field struct { | type Field struct { | ||||||
| 	ID          int64    `json:"-"` | 	ID      int64        `json:"-"` | ||||||
| 	Name        string   `json:"name"` | 	Name    string       `json:"name"` | ||||||
| 	Favourite   []string `json:"favourite"` | 	Entries []FieldEntry `json:"entries"` | ||||||
| 	Okay        []string `json:"okay"` |  | ||||||
| 	Jokingly    []string `json:"jokingly"` |  | ||||||
| 	FriendsOnly []string `json:"friends_only"` |  | ||||||
| 	Avoid       []string `json:"avoid"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Validate validates this field. If it is invalid, a non-empty string is returned as error message. | // Validate validates this field. If it is invalid, a non-empty string is returned as error message. | ||||||
|  | @ -37,37 +33,17 @@ func (f Field) Validate() string { | ||||||
| 		return fmt.Sprintf("name max length is %d characters, length is %d", FieldNameMaxLength, length) | 		return fmt.Sprintf("name max length is %d characters, length is %d", FieldNameMaxLength, length) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if length := len(f.Favourite) + len(f.Okay) + len(f.Jokingly) + len(f.FriendsOnly) + len(f.Avoid); length > FieldEntriesLimit { | 	if length := len(f.Entries); length > FieldEntriesLimit { | ||||||
| 		return fmt.Sprintf("max number of entries is %d, current number is %d", FieldEntriesLimit, length) | 		return fmt.Sprintf("max number of entries is %d, current number is %d", FieldEntriesLimit, length) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for i, entry := range f.Favourite { | 	for i, entry := range f.Entries { | ||||||
| 		if length := len([]rune(entry)); length > FieldEntryMaxLength { | 		if length := len([]rune(entry.Value)); length > FieldEntryMaxLength { | ||||||
| 			return fmt.Sprintf("favourite.%d: name max length is %d characters, length is %d", i, FieldEntryMaxLength, length) | 			return fmt.Sprintf("entries.%d: max length is %d characters, length is %d", i, FieldEntryMaxLength, length) | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	for i, entry := range f.Okay { | 		if entry.Status == StatusUnknown || entry.Status >= wordStatusMax { | ||||||
| 		if length := len([]rune(entry)); length > FieldEntryMaxLength { | 			return fmt.Sprintf("entries.%d: status is invalid, must be between 1 and %d, is %d", i, wordStatusMax-1, entry.Status) | ||||||
| 			return fmt.Sprintf("okay.%d: name max length is %d characters, length is %d", i, FieldEntryMaxLength, length) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, entry := range f.Jokingly { |  | ||||||
| 		if length := len([]rune(entry)); length > FieldEntryMaxLength { |  | ||||||
| 			return fmt.Sprintf("jokingly.%d: name max length is %d characters, length is %d", i, FieldEntryMaxLength, length) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, entry := range f.FriendsOnly { |  | ||||||
| 		if length := len([]rune(entry)); length > FieldEntryMaxLength { |  | ||||||
| 			return fmt.Sprintf("friends_only.%d: name max length is %d characters, length is %d", i, FieldEntryMaxLength, length) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, entry := range f.Avoid { |  | ||||||
| 		if length := len([]rune(entry)); length > FieldEntryMaxLength { |  | ||||||
| 			return fmt.Sprintf("avoid.%d: name max length is %d characters, length is %d", i, FieldEntryMaxLength, length) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -76,17 +52,20 @@ func (f Field) Validate() string { | ||||||
| 
 | 
 | ||||||
| // UserFields returns the fields associated with the given user ID. | // UserFields returns the fields associated with the given user ID. | ||||||
| func (db *DB) UserFields(ctx context.Context, id xid.ID) (fs []Field, err error) { | func (db *DB) UserFields(ctx context.Context, id xid.ID) (fs []Field, err error) { | ||||||
| 	sql, args, err := sq. | 	qfields, err := db.q.GetUserFields(ctx, id.String()) | ||||||
| 		Select("id", "name", "favourite", "okay", "jokingly", "friends_only", "avoid"). |  | ||||||
| 		From("user_fields").Where("user_id = ?", id).OrderBy("id ASC").ToSql() |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.Wrap(err, "building sql") | 		return nil, errors.Wrap(err, "querying fields") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = pgxscan.Select(ctx, db, &fs, sql, args...) | 	fs = make([]Field, len(qfields)) | ||||||
| 	if err != nil { | 	for i := range qfields { | ||||||
| 		return nil, errors.Cause(err) | 		fs[i] = Field{ | ||||||
|  | 			ID:      int64(*qfields[i].ID), | ||||||
|  | 			Name:    *qfields[i].Name, | ||||||
|  | 			Entries: dbEntriesToFieldEntries(qfields[i].Entries), | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return fs, nil | 	return fs, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -102,20 +81,14 @@ func (db *DB) SetUserFields(ctx context.Context, tx pgx.Tx, userID xid.ID, field | ||||||
| 		return errors.Wrap(err, "deleting existing fields") | 		return errors.Wrap(err, "deleting existing fields") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err = tx.CopyFrom(ctx, | 	querier := queries.NewQuerier(tx) | ||||||
| 		pgx.Identifier{"user_fields"}, | 	for _, field := range fields { | ||||||
| 		[]string{"user_id", "name", "favourite", "okay", "jokingly", "friends_only", "avoid"}, | 		querier.InsertUserField(ctx, queries.InsertUserFieldParams{ | ||||||
| 		pgx.CopyFromSlice(len(fields), func(i int) ([]any, error) { | 			UserID:  userID.String(), | ||||||
| 			return []any{ | 			Name:    field.Name, | ||||||
| 				userID, | 			Entries: entriesToDBEntries(field.Entries), | ||||||
| 				fields[i].Name, | 		}) | ||||||
| 				fields[i].Favourite, | 	} | ||||||
| 				fields[i].Okay, |  | ||||||
| 				fields[i].Jokingly, |  | ||||||
| 				fields[i].FriendsOnly, |  | ||||||
| 				fields[i].Avoid, |  | ||||||
| 			}, nil |  | ||||||
| 		})) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "inserting new fields") | 		return errors.Wrap(err, "inserting new fields") | ||||||
| 	} | 	} | ||||||
|  | @ -124,17 +97,20 @@ func (db *DB) SetUserFields(ctx context.Context, tx pgx.Tx, userID xid.ID, field | ||||||
| 
 | 
 | ||||||
| // MemberFields returns the fields associated with the given member ID. | // MemberFields returns the fields associated with the given member ID. | ||||||
| func (db *DB) MemberFields(ctx context.Context, id xid.ID) (fs []Field, err error) { | func (db *DB) MemberFields(ctx context.Context, id xid.ID) (fs []Field, err error) { | ||||||
| 	sql, args, err := sq. | 	qfields, err := db.q.GetMemberFields(ctx, id.String()) | ||||||
| 		Select("id", "name", "favourite", "okay", "jokingly", "friends_only", "avoid"). |  | ||||||
| 		From("member_fields").Where("member_id = ?", id).OrderBy("id ASC").ToSql() |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.Wrap(err, "building sql") | 		return nil, errors.Wrap(err, "querying fields") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = pgxscan.Select(ctx, db, &fs, sql, args...) | 	fs = make([]Field, len(qfields)) | ||||||
| 	if err != nil { | 	for i := range qfields { | ||||||
| 		return nil, errors.Cause(err) | 		fs[i] = Field{ | ||||||
|  | 			ID:      int64(*qfields[i].ID), | ||||||
|  | 			Name:    *qfields[i].Name, | ||||||
|  | 			Entries: dbEntriesToFieldEntries(qfields[i].Entries), | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return fs, nil | 	return fs, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -150,20 +126,14 @@ func (db *DB) SetMemberFields(ctx context.Context, tx pgx.Tx, memberID xid.ID, f | ||||||
| 		return errors.Wrap(err, "deleting existing fields") | 		return errors.Wrap(err, "deleting existing fields") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err = tx.CopyFrom(ctx, | 	querier := queries.NewQuerier(tx) | ||||||
| 		pgx.Identifier{"member_fields"}, | 	for _, field := range fields { | ||||||
| 		[]string{"member_id", "name", "favourite", "okay", "jokingly", "friends_only", "avoid"}, | 		querier.InsertMemberField(ctx, queries.InsertMemberFieldParams{ | ||||||
| 		pgx.CopyFromSlice(len(fields), func(i int) ([]any, error) { | 			MemberID: memberID.String(), | ||||||
| 			return []any{ | 			Name:     field.Name, | ||||||
| 				memberID, | 			Entries:  entriesToDBEntries(field.Entries), | ||||||
| 				fields[i].Name, | 		}) | ||||||
| 				fields[i].Favourite, | 	} | ||||||
| 				fields[i].Okay, |  | ||||||
| 				fields[i].Jokingly, |  | ||||||
| 				fields[i].FriendsOnly, |  | ||||||
| 				fields[i].Avoid, |  | ||||||
| 			}, nil |  | ||||||
| 		})) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "inserting new fields") | 		return errors.Wrap(err, "inserting new fields") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package db | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
| 	"emperror.dev/errors" | 	"emperror.dev/errors" | ||||||
| 	"github.com/georgysavva/scany/pgxscan" | 	"github.com/georgysavva/scany/pgxscan" | ||||||
| 	"github.com/jackc/pgconn" | 	"github.com/jackc/pgconn" | ||||||
|  | @ -23,6 +24,8 @@ type Member struct { | ||||||
| 	Bio         *string | 	Bio         *string | ||||||
| 	AvatarURLs  []string `db:"avatar_urls"` | 	AvatarURLs  []string `db:"avatar_urls"` | ||||||
| 	Links       []string | 	Links       []string | ||||||
|  | 	Names       []FieldEntry | ||||||
|  | 	Pronouns    []PronounEntry | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -30,19 +33,27 @@ const ( | ||||||
| 	ErrMemberNameInUse = errors.Sentinel("member name already in use") | 	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) { | func (db *DB) getMember(ctx context.Context, q querier, id xid.ID) (m Member, err error) { | ||||||
| 	sql, args, err := sq.Select("*").From("members").Where("id = ?", id).ToSql() | 	qm, err := queries.NewQuerier(q).GetMemberByID(ctx, id.String()) | ||||||
| 	if err != nil { | 	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 err != nil { | ||||||
| 		if errors.Cause(err) == pgx.ErrNoRows { | 		return m, errors.Wrap(err, "parsing user ID") | ||||||
| 			return m, ErrMemberNotFound | 	} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		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 | 	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. | // UserMember returns a member scoped by user. | ||||||
| func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (m Member, err error) { | func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (m Member, err error) { | ||||||
| 	sql, args, err := sq.Select("*").From("members"). | 	qm, err := db.q.GetMemberByName(ctx, userID.String(), memberRef) | ||||||
| 		Where("user_id = ? and (id = ? or name = ?)", userID, memberRef, memberRef).ToSql() |  | ||||||
| 	if err != nil { | 	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 err != nil { | ||||||
| 		if errors.Cause(err) == pgx.ErrNoRows { | 		return m, errors.Wrap(err, "parsing member ID") | ||||||
| 			return m, ErrMemberNotFound | 	} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		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 | 	return m, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UserMembers returns all of a user's members, sorted by name. | // 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) { | 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 { | 	if err != nil { | ||||||
| 		return nil, errors.Wrap(err, "building sql") | 		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"). | 	sql, args, err := sq.Insert("members"). | ||||||
| 		Columns("user_id", "id", "name", "display_name", "bio", "links"). | 		Columns("user_id", "id", "name", "display_name", "bio", "links"). | ||||||
| 		Values(userID, xid.New(), name, displayName, bio, links). | 		Values(userID, xid.New(), name, displayName, bio, links). | ||||||
| 		Suffix("RETURNING *").ToSql() | 		Suffix("RETURNING id").ToSql() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return m, errors.Wrap(err, "building sql") | 		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 { | 	if err != nil { | ||||||
| 		pge := &pgconn.PgError{} | 		pge := &pgconn.PgError{} | ||||||
| 		if errors.As(err, &pge) { | 		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") | 		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 | 	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 { | 	if err != nil { | ||||||
| 		return m, errors.Wrap(err, "building sql") | 		return m, errors.Wrap(err, "building sql") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = pgxscan.Get(ctx, tx, &m, sql, args...) | 	_, err = tx.Exec(ctx, sql, args...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		pge := &pgconn.PgError{} | 		pge := &pgconn.PgError{} | ||||||
| 		if errors.As(err, &pge) { | 		if errors.As(err, &pge) { | ||||||
|  | @ -209,5 +235,10 @@ func (db *DB) UpdateMember( | ||||||
| 		return m, errors.Wrap(err, "executing sql") | 		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 | 	return m, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,255 +2,56 @@ package db | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
| 	"emperror.dev/errors" | 	"emperror.dev/errors" | ||||||
| 	"github.com/georgysavva/scany/pgxscan" |  | ||||||
| 	"github.com/jackc/pgx/v4" | 	"github.com/jackc/pgx/v4" | ||||||
| 	"github.com/rs/xid" | 	"github.com/rs/xid" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type WordStatus int | 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{ | ||||||
| const ( | 		ID:       userID.String(), | ||||||
| 	StatusUnknown     WordStatus = 0 | 		Names:    entriesToDBEntries(names), | ||||||
| 	StatusFavourite   WordStatus = 1 | 		Pronouns: pronounEntriesToDBEntries(pronouns), | ||||||
| 	StatusOkay        WordStatus = 2 | 	}) | ||||||
| 	StatusJokingly    WordStatus = 3 | 	if err != nil { | ||||||
| 	StatusFriendsOnly WordStatus = 4 | 		return errors.Wrap(err, "executing update names/pronouns query") | ||||||
| 	StatusAvoid       WordStatus = 5 | 	} | ||||||
| 	wordStatusMax     WordStatus = 6 | 	return nil | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Name struct { |  | ||||||
| 	ID     int64      `json:"-"` |  | ||||||
| 	Name   string     `json:"name"` |  | ||||||
| 	Status WordStatus `json:"status"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (n Name) Validate() string { | func (db *DB) SetMemberNamesPronouns(ctx context.Context, tx pgx.Tx, memberID xid.ID, names []FieldEntry, pronouns []PronounEntry) (err error) { | ||||||
| 	if n.Name == "" { | 	_, err = queries.NewQuerier(tx).UpdateMemberNamesPronouns(ctx, queries.UpdateMemberNamesPronounsParams{ | ||||||
| 		return "name cannot be empty" | 		ID:       memberID.String(), | ||||||
|  | 		Names:    entriesToDBEntries(names), | ||||||
|  | 		Pronouns: pronounEntriesToDBEntries(pronouns), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrap(err, "executing update names/pronouns query") | ||||||
| 	} | 	} | ||||||
| 
 | 	return nil | ||||||
| 	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 { | func fieldEntriesFromDB(dn []queries.FieldEntry) []FieldEntry { | ||||||
| 	ID          int64      `json:"-"` | 	names := make([]FieldEntry, len(dn)) | ||||||
| 	DisplayText *string    `json:"display_text"` | 	for i := range dn { | ||||||
| 	Pronouns    string     `json:"pronouns"` | 		names[i] = FieldEntry{ | ||||||
| 	Status      WordStatus `json:"status"` | 			Value:  *dn[i].Value, | ||||||
| } | 			Status: WordStatus(*dn[i].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))) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 	return names | ||||||
| 	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 { | func pronounsFromDB(dn []queries.PronounEntry) []PronounEntry { | ||||||
| 	if p.DisplayText != nil { | 	pronouns := make([]PronounEntry, len(dn)) | ||||||
| 		return *p.DisplayText | 	for i := range dn { | ||||||
|  | 		pronouns[i] = PronounEntry{ | ||||||
|  | 			DisplayText: dn[i].DisplayValue, | ||||||
|  | 			Pronouns:    *dn[i].Value, | ||||||
|  | 			Status:      WordStatus(*dn[i].Status), | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 	return pronouns | ||||||
| 	split := strings.Split(p.Pronouns, "/") |  | ||||||
| 	if len(split) <= 2 { |  | ||||||
| 		return strings.Join(split, "/") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return strings.Join(split[:1], "/") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (db *DB) UserNames(ctx context.Context, userID xid.ID) (ns []Name, err error) { |  | ||||||
| 	sql, args, err := sq.Select("id", "name", "status").From("user_names").Where("user_id = ?", userID).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) UserPronouns(ctx context.Context, userID xid.ID) (ps []Pronoun, err error) { |  | ||||||
| 	sql, args, err := sq. |  | ||||||
| 		Select("id", "display_text", "pronouns", "status"). |  | ||||||
| 		From("user_pronouns").Where("user_id = ?", userID). |  | ||||||
| 		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) 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() |  | ||||||
| 	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 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() |  | ||||||
| 	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 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 |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								backend/db/queries/generate.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/db/queries/generate.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | package queries | ||||||
|  | 
 | ||||||
|  | //go:generate pggen gen go --query-glob queries.user.sql --query-glob queries.member.sql --postgres-connection "postgres://pggen:pggen@localhost/pggen" | ||||||
							
								
								
									
										31
									
								
								backend/db/queries/queries.member.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								backend/db/queries/queries.member.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | -- name: GetMemberByID :one | ||||||
|  | SELECT * FROM members | ||||||
|  | WHERE id = pggen.arg('id'); | ||||||
|  | 
 | ||||||
|  | -- name: GetMemberByName :one | ||||||
|  | SELECT * FROM members | ||||||
|  | WHERE user_id = pggen.arg('user_id') AND ( | ||||||
|  |     id = pggen.arg('member_ref') | ||||||
|  |     OR name = pggen.arg('member_ref') | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- name: GetMembers :many | ||||||
|  | SELECT * FROM members | ||||||
|  | WHERE user_id = pggen.arg('user_id') | ||||||
|  | ORDER BY name, id; | ||||||
|  | 
 | ||||||
|  | -- name: UpdateMemberNamesPronouns :one | ||||||
|  | UPDATE members SET | ||||||
|  | names = pggen.arg('names'), | ||||||
|  | pronouns = pggen.arg('pronouns') | ||||||
|  | WHERE id = pggen.arg('id') | ||||||
|  | RETURNING *; | ||||||
|  | 
 | ||||||
|  | -- name: GetMemberFields :many | ||||||
|  | SELECT * FROM member_fields WHERE member_id = pggen.arg('member_id') ORDER BY id ASC; | ||||||
|  | 
 | ||||||
|  | -- name: InsertMemberField :one | ||||||
|  | INSERT INTO member_fields | ||||||
|  | (member_id, name, entries) VALUES | ||||||
|  | (pggen.arg('member_id'), pggen.arg('name'), pggen.arg('entries')) | ||||||
|  | RETURNING *; | ||||||
							
								
								
									
										803
									
								
								backend/db/queries/queries.member.sql.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										803
									
								
								backend/db/queries/queries.member.sql.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,803 @@ | ||||||
|  | // Code generated by pggen. DO NOT EDIT. | ||||||
|  | 
 | ||||||
|  | package queries | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/jackc/pgconn" | ||||||
|  | 	"github.com/jackc/pgtype" | ||||||
|  | 	"github.com/jackc/pgx/v4" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Querier is a typesafe Go interface backed by SQL queries. | ||||||
|  | // | ||||||
|  | // Methods ending with Batch enqueue a query to run later in a pgx.Batch. After | ||||||
|  | // calling SendBatch on pgx.Conn, pgxpool.Pool, or pgx.Tx, use the Scan methods | ||||||
|  | // to parse the results. | ||||||
|  | type Querier interface { | ||||||
|  | 	GetMemberByID(ctx context.Context, id string) (GetMemberByIDRow, error) | ||||||
|  | 	// GetMemberByIDBatch enqueues a GetMemberByID query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetMemberByIDBatch(batch genericBatch, id string) | ||||||
|  | 	// GetMemberByIDScan scans the result of an executed GetMemberByIDBatch query. | ||||||
|  | 	GetMemberByIDScan(results pgx.BatchResults) (GetMemberByIDRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetMemberByName(ctx context.Context, userID string, memberRef string) (GetMemberByNameRow, error) | ||||||
|  | 	// GetMemberByNameBatch enqueues a GetMemberByName query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetMemberByNameBatch(batch genericBatch, userID string, memberRef string) | ||||||
|  | 	// GetMemberByNameScan scans the result of an executed GetMemberByNameBatch query. | ||||||
|  | 	GetMemberByNameScan(results pgx.BatchResults) (GetMemberByNameRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetMembers(ctx context.Context, userID string) ([]GetMembersRow, error) | ||||||
|  | 	// GetMembersBatch enqueues a GetMembers query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetMembersBatch(batch genericBatch, userID string) | ||||||
|  | 	// GetMembersScan scans the result of an executed GetMembersBatch query. | ||||||
|  | 	GetMembersScan(results pgx.BatchResults) ([]GetMembersRow, error) | ||||||
|  | 
 | ||||||
|  | 	UpdateMemberNamesPronouns(ctx context.Context, params UpdateMemberNamesPronounsParams) (UpdateMemberNamesPronounsRow, error) | ||||||
|  | 	// UpdateMemberNamesPronounsBatch enqueues a UpdateMemberNamesPronouns query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	UpdateMemberNamesPronounsBatch(batch genericBatch, params UpdateMemberNamesPronounsParams) | ||||||
|  | 	// UpdateMemberNamesPronounsScan scans the result of an executed UpdateMemberNamesPronounsBatch query. | ||||||
|  | 	UpdateMemberNamesPronounsScan(results pgx.BatchResults) (UpdateMemberNamesPronounsRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetMemberFields(ctx context.Context, memberID string) ([]GetMemberFieldsRow, error) | ||||||
|  | 	// GetMemberFieldsBatch enqueues a GetMemberFields query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetMemberFieldsBatch(batch genericBatch, memberID string) | ||||||
|  | 	// GetMemberFieldsScan scans the result of an executed GetMemberFieldsBatch query. | ||||||
|  | 	GetMemberFieldsScan(results pgx.BatchResults) ([]GetMemberFieldsRow, error) | ||||||
|  | 
 | ||||||
|  | 	InsertMemberField(ctx context.Context, params InsertMemberFieldParams) (InsertMemberFieldRow, error) | ||||||
|  | 	// InsertMemberFieldBatch enqueues a InsertMemberField query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	InsertMemberFieldBatch(batch genericBatch, params InsertMemberFieldParams) | ||||||
|  | 	// InsertMemberFieldScan scans the result of an executed InsertMemberFieldBatch query. | ||||||
|  | 	InsertMemberFieldScan(results pgx.BatchResults) (InsertMemberFieldRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetUserByID(ctx context.Context, id string) (GetUserByIDRow, error) | ||||||
|  | 	// GetUserByIDBatch enqueues a GetUserByID query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetUserByIDBatch(batch genericBatch, id string) | ||||||
|  | 	// GetUserByIDScan scans the result of an executed GetUserByIDBatch query. | ||||||
|  | 	GetUserByIDScan(results pgx.BatchResults) (GetUserByIDRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) | ||||||
|  | 	// GetUserByUsernameBatch enqueues a GetUserByUsername query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetUserByUsernameBatch(batch genericBatch, username string) | ||||||
|  | 	// GetUserByUsernameScan scans the result of an executed GetUserByUsernameBatch query. | ||||||
|  | 	GetUserByUsernameScan(results pgx.BatchResults) (GetUserByUsernameRow, error) | ||||||
|  | 
 | ||||||
|  | 	UpdateUserNamesPronouns(ctx context.Context, params UpdateUserNamesPronounsParams) (UpdateUserNamesPronounsRow, error) | ||||||
|  | 	// UpdateUserNamesPronounsBatch enqueues a UpdateUserNamesPronouns query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	UpdateUserNamesPronounsBatch(batch genericBatch, params UpdateUserNamesPronounsParams) | ||||||
|  | 	// UpdateUserNamesPronounsScan scans the result of an executed UpdateUserNamesPronounsBatch query. | ||||||
|  | 	UpdateUserNamesPronounsScan(results pgx.BatchResults) (UpdateUserNamesPronounsRow, error) | ||||||
|  | 
 | ||||||
|  | 	GetUserFields(ctx context.Context, userID string) ([]GetUserFieldsRow, error) | ||||||
|  | 	// GetUserFieldsBatch enqueues a GetUserFields query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	GetUserFieldsBatch(batch genericBatch, userID string) | ||||||
|  | 	// GetUserFieldsScan scans the result of an executed GetUserFieldsBatch query. | ||||||
|  | 	GetUserFieldsScan(results pgx.BatchResults) ([]GetUserFieldsRow, error) | ||||||
|  | 
 | ||||||
|  | 	InsertUserField(ctx context.Context, params InsertUserFieldParams) (InsertUserFieldRow, error) | ||||||
|  | 	// InsertUserFieldBatch enqueues a InsertUserField query into batch to be executed | ||||||
|  | 	// later by the batch. | ||||||
|  | 	InsertUserFieldBatch(batch genericBatch, params InsertUserFieldParams) | ||||||
|  | 	// InsertUserFieldScan scans the result of an executed InsertUserFieldBatch query. | ||||||
|  | 	InsertUserFieldScan(results pgx.BatchResults) (InsertUserFieldRow, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DBQuerier struct { | ||||||
|  | 	conn  genericConn   // underlying Postgres transport to use | ||||||
|  | 	types *typeResolver // resolve types by name | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ Querier = &DBQuerier{} | ||||||
|  | 
 | ||||||
|  | // genericConn is a connection to a Postgres database. This is usually backed by | ||||||
|  | // *pgx.Conn, pgx.Tx, or *pgxpool.Pool. | ||||||
|  | type genericConn interface { | ||||||
|  | 	// Query executes sql with args. If there is an error the returned Rows will | ||||||
|  | 	// be returned in an error state. So it is allowed to ignore the error | ||||||
|  | 	// returned from Query and handle it in Rows. | ||||||
|  | 	Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) | ||||||
|  | 
 | ||||||
|  | 	// QueryRow is a convenience wrapper over Query. Any error that occurs while | ||||||
|  | 	// querying is deferred until calling Scan on the returned Row. That Row will | ||||||
|  | 	// error with pgx.ErrNoRows if no rows are returned. | ||||||
|  | 	QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row | ||||||
|  | 
 | ||||||
|  | 	// Exec executes sql. sql can be either a prepared statement name or an SQL | ||||||
|  | 	// string. arguments should be referenced positionally from the sql string | ||||||
|  | 	// as $1, $2, etc. | ||||||
|  | 	Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // genericBatch batches queries to send in a single network request to a | ||||||
|  | // Postgres server. This is usually backed by *pgx.Batch. | ||||||
|  | type genericBatch interface { | ||||||
|  | 	// Queue queues a query to batch b. query can be an SQL query or the name of a | ||||||
|  | 	// prepared statement. See Queue on *pgx.Batch. | ||||||
|  | 	Queue(query string, arguments ...interface{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewQuerier creates a DBQuerier that implements Querier. conn is typically | ||||||
|  | // *pgx.Conn, pgx.Tx, or *pgxpool.Pool. | ||||||
|  | func NewQuerier(conn genericConn) *DBQuerier { | ||||||
|  | 	return NewQuerierConfig(conn, QuerierConfig{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type QuerierConfig struct { | ||||||
|  | 	// DataTypes contains pgtype.Value to use for encoding and decoding instead | ||||||
|  | 	// of pggen-generated pgtype.ValueTranscoder. | ||||||
|  | 	// | ||||||
|  | 	// If OIDs are available for an input parameter type and all of its | ||||||
|  | 	// transitive dependencies, pggen will use the binary encoding format for | ||||||
|  | 	// the input parameter. | ||||||
|  | 	DataTypes []pgtype.DataType | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewQuerierConfig creates a DBQuerier that implements Querier with the given | ||||||
|  | // config. conn is typically *pgx.Conn, pgx.Tx, or *pgxpool.Pool. | ||||||
|  | func NewQuerierConfig(conn genericConn, cfg QuerierConfig) *DBQuerier { | ||||||
|  | 	return &DBQuerier{conn: conn, types: newTypeResolver(cfg.DataTypes)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithTx creates a new DBQuerier that uses the transaction to run all queries. | ||||||
|  | func (q *DBQuerier) WithTx(tx pgx.Tx) (*DBQuerier, error) { | ||||||
|  | 	return &DBQuerier{conn: tx}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // preparer is any Postgres connection transport that provides a way to prepare | ||||||
|  | // a statement, most commonly *pgx.Conn. | ||||||
|  | type preparer interface { | ||||||
|  | 	Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PrepareAllQueries executes a PREPARE statement for all pggen generated SQL | ||||||
|  | // queries in querier files. Typical usage is as the AfterConnect callback | ||||||
|  | // for pgxpool.Config | ||||||
|  | // | ||||||
|  | // pgx will use the prepared statement if available. Calling PrepareAllQueries | ||||||
|  | // is an optional optimization to avoid a network round-trip the first time pgx | ||||||
|  | // runs a query if pgx statement caching is enabled. | ||||||
|  | func PrepareAllQueries(ctx context.Context, p preparer) error { | ||||||
|  | 	if _, err := p.Prepare(ctx, getMemberByIDSQL, getMemberByIDSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetMemberByID': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getMemberByNameSQL, getMemberByNameSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetMemberByName': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getMembersSQL, getMembersSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetMembers': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, updateMemberNamesPronounsSQL, updateMemberNamesPronounsSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'UpdateMemberNamesPronouns': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getMemberFieldsSQL, getMemberFieldsSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetMemberFields': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, insertMemberFieldSQL, insertMemberFieldSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'InsertMemberField': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getUserByIDSQL, getUserByIDSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetUserByID': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getUserByUsernameSQL, getUserByUsernameSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetUserByUsername': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, updateUserNamesPronounsSQL, updateUserNamesPronounsSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'UpdateUserNamesPronouns': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, getUserFieldsSQL, getUserFieldsSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'GetUserFields': %w", err) | ||||||
|  | 	} | ||||||
|  | 	if _, err := p.Prepare(ctx, insertUserFieldSQL, insertUserFieldSQL); err != nil { | ||||||
|  | 		return fmt.Errorf("prepare query 'InsertUserField': %w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FieldEntry represents the Postgres composite type "field_entry". | ||||||
|  | type FieldEntry struct { | ||||||
|  | 	Value  *string `json:"value"` | ||||||
|  | 	Status *int32  `json:"status"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PronounEntry represents the Postgres composite type "pronoun_entry". | ||||||
|  | type PronounEntry struct { | ||||||
|  | 	Value        *string `json:"value"` | ||||||
|  | 	DisplayValue *string `json:"display_value"` | ||||||
|  | 	Status       *int32  `json:"status"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // typeResolver looks up the pgtype.ValueTranscoder by Postgres type name. | ||||||
|  | type typeResolver struct { | ||||||
|  | 	connInfo *pgtype.ConnInfo // types by Postgres type name | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newTypeResolver(types []pgtype.DataType) *typeResolver { | ||||||
|  | 	ci := pgtype.NewConnInfo() | ||||||
|  | 	for _, typ := range types { | ||||||
|  | 		if txt, ok := typ.Value.(textPreferrer); ok && typ.OID != unknownOID { | ||||||
|  | 			typ.Value = txt.ValueTranscoder | ||||||
|  | 		} | ||||||
|  | 		ci.RegisterDataType(typ) | ||||||
|  | 	} | ||||||
|  | 	return &typeResolver{connInfo: ci} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // findValue find the OID, and pgtype.ValueTranscoder for a Postgres type name. | ||||||
|  | func (tr *typeResolver) findValue(name string) (uint32, pgtype.ValueTranscoder, bool) { | ||||||
|  | 	typ, ok := tr.connInfo.DataTypeForName(name) | ||||||
|  | 	if !ok { | ||||||
|  | 		return 0, nil, false | ||||||
|  | 	} | ||||||
|  | 	v := pgtype.NewValue(typ.Value) | ||||||
|  | 	return typ.OID, v.(pgtype.ValueTranscoder), true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // setValue sets the value of a ValueTranscoder to a value that should always | ||||||
|  | // work and panics if it fails. | ||||||
|  | func (tr *typeResolver) setValue(vt pgtype.ValueTranscoder, val interface{}) pgtype.ValueTranscoder { | ||||||
|  | 	if err := vt.Set(val); err != nil { | ||||||
|  | 		panic(fmt.Sprintf("set ValueTranscoder %T to %+v: %s", vt, val, err)) | ||||||
|  | 	} | ||||||
|  | 	return vt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type compositeField struct { | ||||||
|  | 	name       string                 // name of the field | ||||||
|  | 	typeName   string                 // Postgres type name | ||||||
|  | 	defaultVal pgtype.ValueTranscoder // default value to use | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *typeResolver) newCompositeValue(name string, fields ...compositeField) pgtype.ValueTranscoder { | ||||||
|  | 	if _, val, ok := tr.findValue(name); ok { | ||||||
|  | 		return val | ||||||
|  | 	} | ||||||
|  | 	fs := make([]pgtype.CompositeTypeField, len(fields)) | ||||||
|  | 	vals := make([]pgtype.ValueTranscoder, len(fields)) | ||||||
|  | 	isBinaryOk := true | ||||||
|  | 	for i, field := range fields { | ||||||
|  | 		oid, val, ok := tr.findValue(field.typeName) | ||||||
|  | 		if !ok { | ||||||
|  | 			oid = unknownOID | ||||||
|  | 			val = field.defaultVal | ||||||
|  | 		} | ||||||
|  | 		isBinaryOk = isBinaryOk && oid != unknownOID | ||||||
|  | 		fs[i] = pgtype.CompositeTypeField{Name: field.name, OID: oid} | ||||||
|  | 		vals[i] = val | ||||||
|  | 	} | ||||||
|  | 	// Okay to ignore error because it's only thrown when the number of field | ||||||
|  | 	// names does not equal the number of ValueTranscoders. | ||||||
|  | 	typ, _ := pgtype.NewCompositeTypeValues(name, fs, vals) | ||||||
|  | 	if !isBinaryOk { | ||||||
|  | 		return textPreferrer{ValueTranscoder: typ, typeName: name} | ||||||
|  | 	} | ||||||
|  | 	return typ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tr *typeResolver) newArrayValue(name, elemName string, defaultVal func() pgtype.ValueTranscoder) pgtype.ValueTranscoder { | ||||||
|  | 	if _, val, ok := tr.findValue(name); ok { | ||||||
|  | 		return val | ||||||
|  | 	} | ||||||
|  | 	elemOID, elemVal, ok := tr.findValue(elemName) | ||||||
|  | 	elemValFunc := func() pgtype.ValueTranscoder { | ||||||
|  | 		return pgtype.NewValue(elemVal).(pgtype.ValueTranscoder) | ||||||
|  | 	} | ||||||
|  | 	if !ok { | ||||||
|  | 		elemOID = unknownOID | ||||||
|  | 		elemValFunc = defaultVal | ||||||
|  | 	} | ||||||
|  | 	typ := pgtype.NewArrayType(name, elemOID, elemValFunc) | ||||||
|  | 	if elemOID == unknownOID { | ||||||
|  | 		return textPreferrer{ValueTranscoder: typ, typeName: name} | ||||||
|  | 	} | ||||||
|  | 	return typ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newFieldEntry creates a new pgtype.ValueTranscoder for the Postgres | ||||||
|  | // composite type 'field_entry'. | ||||||
|  | func (tr *typeResolver) newFieldEntry() pgtype.ValueTranscoder { | ||||||
|  | 	return tr.newCompositeValue( | ||||||
|  | 		"field_entry", | ||||||
|  | 		compositeField{name: "value", typeName: "text", defaultVal: &pgtype.Text{}}, | ||||||
|  | 		compositeField{name: "status", typeName: "int4", defaultVal: &pgtype.Int4{}}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newFieldEntryRaw returns all composite fields for the Postgres composite | ||||||
|  | // type 'field_entry' as a slice of interface{} to encode query parameters. | ||||||
|  | func (tr *typeResolver) newFieldEntryRaw(v FieldEntry) []interface{} { | ||||||
|  | 	return []interface{}{ | ||||||
|  | 		v.Value, | ||||||
|  | 		v.Status, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPronounEntry creates a new pgtype.ValueTranscoder for the Postgres | ||||||
|  | // composite type 'pronoun_entry'. | ||||||
|  | func (tr *typeResolver) newPronounEntry() pgtype.ValueTranscoder { | ||||||
|  | 	return tr.newCompositeValue( | ||||||
|  | 		"pronoun_entry", | ||||||
|  | 		compositeField{name: "value", typeName: "text", defaultVal: &pgtype.Text{}}, | ||||||
|  | 		compositeField{name: "display_value", typeName: "text", defaultVal: &pgtype.Text{}}, | ||||||
|  | 		compositeField{name: "status", typeName: "int4", defaultVal: &pgtype.Int4{}}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPronounEntryRaw returns all composite fields for the Postgres composite | ||||||
|  | // type 'pronoun_entry' as a slice of interface{} to encode query parameters. | ||||||
|  | func (tr *typeResolver) newPronounEntryRaw(v PronounEntry) []interface{} { | ||||||
|  | 	return []interface{}{ | ||||||
|  | 		v.Value, | ||||||
|  | 		v.DisplayValue, | ||||||
|  | 		v.Status, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newFieldEntryArray creates a new pgtype.ValueTranscoder for the Postgres | ||||||
|  | // '_field_entry' array type. | ||||||
|  | func (tr *typeResolver) newFieldEntryArray() pgtype.ValueTranscoder { | ||||||
|  | 	return tr.newArrayValue("_field_entry", "field_entry", tr.newFieldEntry) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newFieldEntryArrayInit creates an initialized pgtype.ValueTranscoder for the | ||||||
|  | // Postgres array type '_field_entry' to encode query parameters. | ||||||
|  | func (tr *typeResolver) newFieldEntryArrayInit(ps []FieldEntry) pgtype.ValueTranscoder { | ||||||
|  | 	dec := tr.newFieldEntryArray() | ||||||
|  | 	if err := dec.Set(tr.newFieldEntryArrayRaw(ps)); err != nil { | ||||||
|  | 		panic("encode []FieldEntry: " + err.Error()) // should always succeed | ||||||
|  | 	} | ||||||
|  | 	return textPreferrer{ValueTranscoder: dec, typeName: "_field_entry"} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newFieldEntryArrayRaw returns all elements for the Postgres array type '_field_entry' | ||||||
|  | // as a slice of interface{} for use with the pgtype.Value Set method. | ||||||
|  | func (tr *typeResolver) newFieldEntryArrayRaw(vs []FieldEntry) []interface{} { | ||||||
|  | 	elems := make([]interface{}, len(vs)) | ||||||
|  | 	for i, v := range vs { | ||||||
|  | 		elems[i] = tr.newFieldEntryRaw(v) | ||||||
|  | 	} | ||||||
|  | 	return elems | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPronounEntryArray creates a new pgtype.ValueTranscoder for the Postgres | ||||||
|  | // '_pronoun_entry' array type. | ||||||
|  | func (tr *typeResolver) newPronounEntryArray() pgtype.ValueTranscoder { | ||||||
|  | 	return tr.newArrayValue("_pronoun_entry", "pronoun_entry", tr.newPronounEntry) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPronounEntryArrayInit creates an initialized pgtype.ValueTranscoder for the | ||||||
|  | // Postgres array type '_pronoun_entry' to encode query parameters. | ||||||
|  | func (tr *typeResolver) newPronounEntryArrayInit(ps []PronounEntry) pgtype.ValueTranscoder { | ||||||
|  | 	dec := tr.newPronounEntryArray() | ||||||
|  | 	if err := dec.Set(tr.newPronounEntryArrayRaw(ps)); err != nil { | ||||||
|  | 		panic("encode []PronounEntry: " + err.Error()) // should always succeed | ||||||
|  | 	} | ||||||
|  | 	return textPreferrer{ValueTranscoder: dec, typeName: "_pronoun_entry"} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newPronounEntryArrayRaw returns all elements for the Postgres array type '_pronoun_entry' | ||||||
|  | // as a slice of interface{} for use with the pgtype.Value Set method. | ||||||
|  | func (tr *typeResolver) newPronounEntryArrayRaw(vs []PronounEntry) []interface{} { | ||||||
|  | 	elems := make([]interface{}, len(vs)) | ||||||
|  | 	for i, v := range vs { | ||||||
|  | 		elems[i] = tr.newPronounEntryRaw(v) | ||||||
|  | 	} | ||||||
|  | 	return elems | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getMemberByIDSQL = `SELECT * FROM members | ||||||
|  | WHERE id = $1;` | ||||||
|  | 
 | ||||||
|  | type GetMemberByIDRow struct { | ||||||
|  | 	ID          string         `json:"id"` | ||||||
|  | 	UserID      string         `json:"user_id"` | ||||||
|  | 	Name        string         `json:"name"` | ||||||
|  | 	Bio         *string        `json:"bio"` | ||||||
|  | 	AvatarUrls  []string       `json:"avatar_urls"` | ||||||
|  | 	Links       []string       `json:"links"` | ||||||
|  | 	DisplayName *string        `json:"display_name"` | ||||||
|  | 	Names       []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns    []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByID implements Querier.GetMemberByID. | ||||||
|  | func (q *DBQuerier) GetMemberByID(ctx context.Context, id string) (GetMemberByIDRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetMemberByID") | ||||||
|  | 	row := q.conn.QueryRow(ctx, getMemberByIDSQL, id) | ||||||
|  | 	var item GetMemberByIDRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query GetMemberByID: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByIDBatch implements Querier.GetMemberByIDBatch. | ||||||
|  | func (q *DBQuerier) GetMemberByIDBatch(batch genericBatch, id string) { | ||||||
|  | 	batch.Queue(getMemberByIDSQL, id) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByIDScan implements Querier.GetMemberByIDScan. | ||||||
|  | func (q *DBQuerier) GetMemberByIDScan(results pgx.BatchResults) (GetMemberByIDRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item GetMemberByIDRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan GetMemberByIDBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getMemberByNameSQL = `SELECT * FROM members | ||||||
|  | WHERE user_id = $1 AND ( | ||||||
|  |     id = $2 | ||||||
|  |     OR name = $2 | ||||||
|  | );` | ||||||
|  | 
 | ||||||
|  | type GetMemberByNameRow struct { | ||||||
|  | 	ID          string         `json:"id"` | ||||||
|  | 	UserID      string         `json:"user_id"` | ||||||
|  | 	Name        string         `json:"name"` | ||||||
|  | 	Bio         *string        `json:"bio"` | ||||||
|  | 	AvatarUrls  []string       `json:"avatar_urls"` | ||||||
|  | 	Links       []string       `json:"links"` | ||||||
|  | 	DisplayName *string        `json:"display_name"` | ||||||
|  | 	Names       []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns    []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByName implements Querier.GetMemberByName. | ||||||
|  | func (q *DBQuerier) GetMemberByName(ctx context.Context, userID string, memberRef string) (GetMemberByNameRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetMemberByName") | ||||||
|  | 	row := q.conn.QueryRow(ctx, getMemberByNameSQL, userID, memberRef) | ||||||
|  | 	var item GetMemberByNameRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query GetMemberByName: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByName row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByName row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByNameBatch implements Querier.GetMemberByNameBatch. | ||||||
|  | func (q *DBQuerier) GetMemberByNameBatch(batch genericBatch, userID string, memberRef string) { | ||||||
|  | 	batch.Queue(getMemberByNameSQL, userID, memberRef) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberByNameScan implements Querier.GetMemberByNameScan. | ||||||
|  | func (q *DBQuerier) GetMemberByNameScan(results pgx.BatchResults) (GetMemberByNameRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item GetMemberByNameRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan GetMemberByNameBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByName row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetMemberByName row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getMembersSQL = `SELECT * FROM members | ||||||
|  | WHERE user_id = $1 | ||||||
|  | ORDER BY name, id;` | ||||||
|  | 
 | ||||||
|  | type GetMembersRow struct { | ||||||
|  | 	ID          *string        `json:"id"` | ||||||
|  | 	UserID      *string        `json:"user_id"` | ||||||
|  | 	Name        *string        `json:"name"` | ||||||
|  | 	Bio         *string        `json:"bio"` | ||||||
|  | 	AvatarUrls  []string       `json:"avatar_urls"` | ||||||
|  | 	Links       []string       `json:"links"` | ||||||
|  | 	DisplayName *string        `json:"display_name"` | ||||||
|  | 	Names       []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns    []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMembers implements Querier.GetMembers. | ||||||
|  | func (q *DBQuerier) GetMembers(ctx context.Context, userID string) ([]GetMembersRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetMembers") | ||||||
|  | 	rows, err := q.conn.Query(ctx, getMembersSQL, userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetMembers: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetMembersRow{} | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetMembersRow | ||||||
|  | 		if err := rows.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetMembers row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMembers row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMembers row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetMembers rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMembersBatch implements Querier.GetMembersBatch. | ||||||
|  | func (q *DBQuerier) GetMembersBatch(batch genericBatch, userID string) { | ||||||
|  | 	batch.Queue(getMembersSQL, userID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMembersScan implements Querier.GetMembersScan. | ||||||
|  | func (q *DBQuerier) GetMembersScan(results pgx.BatchResults) ([]GetMembersRow, error) { | ||||||
|  | 	rows, err := results.Query() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetMembersBatch: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetMembersRow{} | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetMembersRow | ||||||
|  | 		if err := rows.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetMembersBatch row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMembers row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMembers row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetMembersBatch rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const updateMemberNamesPronounsSQL = `UPDATE members SET | ||||||
|  | names = $1, | ||||||
|  | pronouns = $2 | ||||||
|  | WHERE id = $3 | ||||||
|  | RETURNING *;` | ||||||
|  | 
 | ||||||
|  | type UpdateMemberNamesPronounsParams struct { | ||||||
|  | 	Names    []FieldEntry | ||||||
|  | 	Pronouns []PronounEntry | ||||||
|  | 	ID       string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UpdateMemberNamesPronounsRow struct { | ||||||
|  | 	ID          string         `json:"id"` | ||||||
|  | 	UserID      string         `json:"user_id"` | ||||||
|  | 	Name        string         `json:"name"` | ||||||
|  | 	Bio         *string        `json:"bio"` | ||||||
|  | 	AvatarUrls  []string       `json:"avatar_urls"` | ||||||
|  | 	Links       []string       `json:"links"` | ||||||
|  | 	DisplayName *string        `json:"display_name"` | ||||||
|  | 	Names       []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns    []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateMemberNamesPronouns implements Querier.UpdateMemberNamesPronouns. | ||||||
|  | func (q *DBQuerier) UpdateMemberNamesPronouns(ctx context.Context, params UpdateMemberNamesPronounsParams) (UpdateMemberNamesPronounsRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "UpdateMemberNamesPronouns") | ||||||
|  | 	row := q.conn.QueryRow(ctx, updateMemberNamesPronounsSQL, q.types.newFieldEntryArrayInit(params.Names), q.types.newPronounEntryArrayInit(params.Pronouns), params.ID) | ||||||
|  | 	var item UpdateMemberNamesPronounsRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query UpdateMemberNamesPronouns: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateMemberNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateMemberNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateMemberNamesPronounsBatch implements Querier.UpdateMemberNamesPronounsBatch. | ||||||
|  | func (q *DBQuerier) UpdateMemberNamesPronounsBatch(batch genericBatch, params UpdateMemberNamesPronounsParams) { | ||||||
|  | 	batch.Queue(updateMemberNamesPronounsSQL, q.types.newFieldEntryArrayInit(params.Names), q.types.newPronounEntryArrayInit(params.Pronouns), params.ID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateMemberNamesPronounsScan implements Querier.UpdateMemberNamesPronounsScan. | ||||||
|  | func (q *DBQuerier) UpdateMemberNamesPronounsScan(results pgx.BatchResults) (UpdateMemberNamesPronounsRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item UpdateMemberNamesPronounsRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.UserID, &item.Name, &item.Bio, &item.AvatarUrls, &item.Links, &item.DisplayName, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan UpdateMemberNamesPronounsBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateMemberNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateMemberNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getMemberFieldsSQL = `SELECT * FROM member_fields WHERE member_id = $1 ORDER BY id ASC;` | ||||||
|  | 
 | ||||||
|  | type GetMemberFieldsRow struct { | ||||||
|  | 	MemberID *string      `json:"member_id"` | ||||||
|  | 	ID       *int         `json:"id"` | ||||||
|  | 	Name     *string      `json:"name"` | ||||||
|  | 	Entries  []FieldEntry `json:"entries"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberFields implements Querier.GetMemberFields. | ||||||
|  | func (q *DBQuerier) GetMemberFields(ctx context.Context, memberID string) ([]GetMemberFieldsRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetMemberFields") | ||||||
|  | 	rows, err := q.conn.Query(ctx, getMemberFieldsSQL, memberID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetMemberFields: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetMemberFieldsRow{} | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetMemberFieldsRow | ||||||
|  | 		if err := rows.Scan(&item.MemberID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetMemberFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMemberFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetMemberFields rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberFieldsBatch implements Querier.GetMemberFieldsBatch. | ||||||
|  | func (q *DBQuerier) GetMemberFieldsBatch(batch genericBatch, memberID string) { | ||||||
|  | 	batch.Queue(getMemberFieldsSQL, memberID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetMemberFieldsScan implements Querier.GetMemberFieldsScan. | ||||||
|  | func (q *DBQuerier) GetMemberFieldsScan(results pgx.BatchResults) ([]GetMemberFieldsRow, error) { | ||||||
|  | 	rows, err := results.Query() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetMemberFieldsBatch: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetMemberFieldsRow{} | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetMemberFieldsRow | ||||||
|  | 		if err := rows.Scan(&item.MemberID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetMemberFieldsBatch row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetMemberFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetMemberFieldsBatch rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const insertMemberFieldSQL = `INSERT INTO member_fields | ||||||
|  | (member_id, name, entries) VALUES | ||||||
|  | ($1, $2, $3) | ||||||
|  | RETURNING *;` | ||||||
|  | 
 | ||||||
|  | type InsertMemberFieldParams struct { | ||||||
|  | 	MemberID string | ||||||
|  | 	Name     string | ||||||
|  | 	Entries  []FieldEntry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type InsertMemberFieldRow struct { | ||||||
|  | 	MemberID string       `json:"member_id"` | ||||||
|  | 	ID       int          `json:"id"` | ||||||
|  | 	Name     string       `json:"name"` | ||||||
|  | 	Entries  []FieldEntry `json:"entries"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertMemberField implements Querier.InsertMemberField. | ||||||
|  | func (q *DBQuerier) InsertMemberField(ctx context.Context, params InsertMemberFieldParams) (InsertMemberFieldRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "InsertMemberField") | ||||||
|  | 	row := q.conn.QueryRow(ctx, insertMemberFieldSQL, params.MemberID, params.Name, q.types.newFieldEntryArrayInit(params.Entries)) | ||||||
|  | 	var item InsertMemberFieldRow | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	if err := row.Scan(&item.MemberID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query InsertMemberField: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign InsertMemberField row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertMemberFieldBatch implements Querier.InsertMemberFieldBatch. | ||||||
|  | func (q *DBQuerier) InsertMemberFieldBatch(batch genericBatch, params InsertMemberFieldParams) { | ||||||
|  | 	batch.Queue(insertMemberFieldSQL, params.MemberID, params.Name, q.types.newFieldEntryArrayInit(params.Entries)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertMemberFieldScan implements Querier.InsertMemberFieldScan. | ||||||
|  | func (q *DBQuerier) InsertMemberFieldScan(results pgx.BatchResults) (InsertMemberFieldRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item InsertMemberFieldRow | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	if err := row.Scan(&item.MemberID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan InsertMemberFieldBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign InsertMemberField row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // textPreferrer wraps a pgtype.ValueTranscoder and sets the preferred encoding | ||||||
|  | // format to text instead binary (the default). pggen uses the text format | ||||||
|  | // when the OID is unknownOID because the binary format requires the OID. | ||||||
|  | // Typically occurs if the results from QueryAllDataTypes aren't passed to | ||||||
|  | // NewQuerierConfig. | ||||||
|  | type textPreferrer struct { | ||||||
|  | 	pgtype.ValueTranscoder | ||||||
|  | 	typeName string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PreferredParamFormat implements pgtype.ParamFormatPreferrer. | ||||||
|  | func (t textPreferrer) PreferredParamFormat() int16 { return pgtype.TextFormatCode } | ||||||
|  | 
 | ||||||
|  | func (t textPreferrer) NewTypeValue() pgtype.Value { | ||||||
|  | 	return textPreferrer{ValueTranscoder: pgtype.NewValue(t.ValueTranscoder).(pgtype.ValueTranscoder), typeName: t.typeName} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (t textPreferrer) TypeName() string { | ||||||
|  | 	return t.typeName | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // unknownOID means we don't know the OID for a type. This is okay for decoding | ||||||
|  | // because pgx call DecodeText or DecodeBinary without requiring the OID. For | ||||||
|  | // encoding parameters, pggen uses textPreferrer if the OID is unknown. | ||||||
|  | const unknownOID = 0 | ||||||
							
								
								
									
										21
									
								
								backend/db/queries/queries.user.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								backend/db/queries/queries.user.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | -- name: GetUserByID :one | ||||||
|  | SELECT * FROM users WHERE id = pggen.arg('id'); | ||||||
|  | 
 | ||||||
|  | -- name: GetUserByUsername :one | ||||||
|  | SELECT * FROM users WHERE username = pggen.arg('username'); | ||||||
|  | 
 | ||||||
|  | -- name: UpdateUserNamesPronouns :one | ||||||
|  | UPDATE users SET | ||||||
|  | names = pggen.arg('names'), | ||||||
|  | pronouns = pggen.arg('pronouns') | ||||||
|  | WHERE id = pggen.arg('id') | ||||||
|  | RETURNING *; | ||||||
|  | 
 | ||||||
|  | -- name: GetUserFields :many | ||||||
|  | SELECT * FROM user_fields WHERE user_id = pggen.arg('user_id') ORDER BY id ASC; | ||||||
|  | 
 | ||||||
|  | -- name: InsertUserField :one | ||||||
|  | INSERT INTO user_fields | ||||||
|  | (user_id, name, entries) VALUES | ||||||
|  | (pggen.arg('user_id'), pggen.arg('name'), pggen.arg('entries')) | ||||||
|  | RETURNING *; | ||||||
							
								
								
									
										310
									
								
								backend/db/queries/queries.user.sql.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								backend/db/queries/queries.user.sql.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,310 @@ | ||||||
|  | // Code generated by pggen. DO NOT EDIT. | ||||||
|  | 
 | ||||||
|  | package queries | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/jackc/pgx/v4" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const getUserByIDSQL = `SELECT * FROM users WHERE id = $1;` | ||||||
|  | 
 | ||||||
|  | type GetUserByIDRow struct { | ||||||
|  | 	ID              string         `json:"id"` | ||||||
|  | 	Username        string         `json:"username"` | ||||||
|  | 	DisplayName     *string        `json:"display_name"` | ||||||
|  | 	Bio             *string        `json:"bio"` | ||||||
|  | 	AvatarUrls      []string       `json:"avatar_urls"` | ||||||
|  | 	Links           []string       `json:"links"` | ||||||
|  | 	Discord         *string        `json:"discord"` | ||||||
|  | 	DiscordUsername *string        `json:"discord_username"` | ||||||
|  | 	MaxInvites      int32          `json:"max_invites"` | ||||||
|  | 	Names           []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns        []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByID implements Querier.GetUserByID. | ||||||
|  | func (q *DBQuerier) GetUserByID(ctx context.Context, id string) (GetUserByIDRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetUserByID") | ||||||
|  | 	row := q.conn.QueryRow(ctx, getUserByIDSQL, id) | ||||||
|  | 	var item GetUserByIDRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query GetUserByID: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByIDBatch implements Querier.GetUserByIDBatch. | ||||||
|  | func (q *DBQuerier) GetUserByIDBatch(batch genericBatch, id string) { | ||||||
|  | 	batch.Queue(getUserByIDSQL, id) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByIDScan implements Querier.GetUserByIDScan. | ||||||
|  | func (q *DBQuerier) GetUserByIDScan(results pgx.BatchResults) (GetUserByIDRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item GetUserByIDRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan GetUserByIDBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByID row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getUserByUsernameSQL = `SELECT * FROM users WHERE username = $1;` | ||||||
|  | 
 | ||||||
|  | type GetUserByUsernameRow struct { | ||||||
|  | 	ID              string         `json:"id"` | ||||||
|  | 	Username        string         `json:"username"` | ||||||
|  | 	DisplayName     *string        `json:"display_name"` | ||||||
|  | 	Bio             *string        `json:"bio"` | ||||||
|  | 	AvatarUrls      []string       `json:"avatar_urls"` | ||||||
|  | 	Links           []string       `json:"links"` | ||||||
|  | 	Discord         *string        `json:"discord"` | ||||||
|  | 	DiscordUsername *string        `json:"discord_username"` | ||||||
|  | 	MaxInvites      int32          `json:"max_invites"` | ||||||
|  | 	Names           []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns        []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByUsername implements Querier.GetUserByUsername. | ||||||
|  | func (q *DBQuerier) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetUserByUsername") | ||||||
|  | 	row := q.conn.QueryRow(ctx, getUserByUsernameSQL, username) | ||||||
|  | 	var item GetUserByUsernameRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query GetUserByUsername: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByUsername row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByUsername row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByUsernameBatch implements Querier.GetUserByUsernameBatch. | ||||||
|  | func (q *DBQuerier) GetUserByUsernameBatch(batch genericBatch, username string) { | ||||||
|  | 	batch.Queue(getUserByUsernameSQL, username) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByUsernameScan implements Querier.GetUserByUsernameScan. | ||||||
|  | func (q *DBQuerier) GetUserByUsernameScan(results pgx.BatchResults) (GetUserByUsernameRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item GetUserByUsernameRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan GetUserByUsernameBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByUsername row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign GetUserByUsername row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const updateUserNamesPronounsSQL = `UPDATE users SET | ||||||
|  | names = $1, | ||||||
|  | pronouns = $2 | ||||||
|  | WHERE id = $3 | ||||||
|  | RETURNING *;` | ||||||
|  | 
 | ||||||
|  | type UpdateUserNamesPronounsParams struct { | ||||||
|  | 	Names    []FieldEntry | ||||||
|  | 	Pronouns []PronounEntry | ||||||
|  | 	ID       string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UpdateUserNamesPronounsRow struct { | ||||||
|  | 	ID              string         `json:"id"` | ||||||
|  | 	Username        string         `json:"username"` | ||||||
|  | 	DisplayName     *string        `json:"display_name"` | ||||||
|  | 	Bio             *string        `json:"bio"` | ||||||
|  | 	AvatarUrls      []string       `json:"avatar_urls"` | ||||||
|  | 	Links           []string       `json:"links"` | ||||||
|  | 	Discord         *string        `json:"discord"` | ||||||
|  | 	DiscordUsername *string        `json:"discord_username"` | ||||||
|  | 	MaxInvites      int32          `json:"max_invites"` | ||||||
|  | 	Names           []FieldEntry   `json:"names"` | ||||||
|  | 	Pronouns        []PronounEntry `json:"pronouns"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateUserNamesPronouns implements Querier.UpdateUserNamesPronouns. | ||||||
|  | func (q *DBQuerier) UpdateUserNamesPronouns(ctx context.Context, params UpdateUserNamesPronounsParams) (UpdateUserNamesPronounsRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "UpdateUserNamesPronouns") | ||||||
|  | 	row := q.conn.QueryRow(ctx, updateUserNamesPronounsSQL, q.types.newFieldEntryArrayInit(params.Names), q.types.newPronounEntryArrayInit(params.Pronouns), params.ID) | ||||||
|  | 	var item UpdateUserNamesPronounsRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query UpdateUserNamesPronouns: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateUserNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateUserNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateUserNamesPronounsBatch implements Querier.UpdateUserNamesPronounsBatch. | ||||||
|  | func (q *DBQuerier) UpdateUserNamesPronounsBatch(batch genericBatch, params UpdateUserNamesPronounsParams) { | ||||||
|  | 	batch.Queue(updateUserNamesPronounsSQL, q.types.newFieldEntryArrayInit(params.Names), q.types.newPronounEntryArrayInit(params.Pronouns), params.ID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateUserNamesPronounsScan implements Querier.UpdateUserNamesPronounsScan. | ||||||
|  | func (q *DBQuerier) UpdateUserNamesPronounsScan(results pgx.BatchResults) (UpdateUserNamesPronounsRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item UpdateUserNamesPronounsRow | ||||||
|  | 	namesArray := q.types.newFieldEntryArray() | ||||||
|  | 	pronounsArray := q.types.newPronounEntryArray() | ||||||
|  | 	if err := row.Scan(&item.ID, &item.Username, &item.DisplayName, &item.Bio, &item.AvatarUrls, &item.Links, &item.Discord, &item.DiscordUsername, &item.MaxInvites, namesArray, pronounsArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan UpdateUserNamesPronounsBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := namesArray.AssignTo(&item.Names); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateUserNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pronounsArray.AssignTo(&item.Pronouns); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign UpdateUserNamesPronouns row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getUserFieldsSQL = `SELECT * FROM user_fields WHERE user_id = $1 ORDER BY id ASC;` | ||||||
|  | 
 | ||||||
|  | type GetUserFieldsRow struct { | ||||||
|  | 	UserID  *string      `json:"user_id"` | ||||||
|  | 	ID      *int         `json:"id"` | ||||||
|  | 	Name    *string      `json:"name"` | ||||||
|  | 	Entries []FieldEntry `json:"entries"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserFields implements Querier.GetUserFields. | ||||||
|  | func (q *DBQuerier) GetUserFields(ctx context.Context, userID string) ([]GetUserFieldsRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "GetUserFields") | ||||||
|  | 	rows, err := q.conn.Query(ctx, getUserFieldsSQL, userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetUserFields: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetUserFieldsRow{} | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetUserFieldsRow | ||||||
|  | 		if err := rows.Scan(&item.UserID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetUserFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetUserFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetUserFields rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserFieldsBatch implements Querier.GetUserFieldsBatch. | ||||||
|  | func (q *DBQuerier) GetUserFieldsBatch(batch genericBatch, userID string) { | ||||||
|  | 	batch.Queue(getUserFieldsSQL, userID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserFieldsScan implements Querier.GetUserFieldsScan. | ||||||
|  | func (q *DBQuerier) GetUserFieldsScan(results pgx.BatchResults) ([]GetUserFieldsRow, error) { | ||||||
|  | 	rows, err := results.Query() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("query GetUserFieldsBatch: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer rows.Close() | ||||||
|  | 	items := []GetUserFieldsRow{} | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var item GetUserFieldsRow | ||||||
|  | 		if err := rows.Scan(&item.UserID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("scan GetUserFieldsBatch row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 			return nil, fmt.Errorf("assign GetUserFields row: %w", err) | ||||||
|  | 		} | ||||||
|  | 		items = append(items, item) | ||||||
|  | 	} | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("close GetUserFieldsBatch rows: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return items, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const insertUserFieldSQL = `INSERT INTO user_fields | ||||||
|  | (user_id, name, entries) VALUES | ||||||
|  | ($1, $2, $3) | ||||||
|  | RETURNING *;` | ||||||
|  | 
 | ||||||
|  | type InsertUserFieldParams struct { | ||||||
|  | 	UserID  string | ||||||
|  | 	Name    string | ||||||
|  | 	Entries []FieldEntry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type InsertUserFieldRow struct { | ||||||
|  | 	UserID  string       `json:"user_id"` | ||||||
|  | 	ID      int          `json:"id"` | ||||||
|  | 	Name    string       `json:"name"` | ||||||
|  | 	Entries []FieldEntry `json:"entries"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertUserField implements Querier.InsertUserField. | ||||||
|  | func (q *DBQuerier) InsertUserField(ctx context.Context, params InsertUserFieldParams) (InsertUserFieldRow, error) { | ||||||
|  | 	ctx = context.WithValue(ctx, "pggen_query_name", "InsertUserField") | ||||||
|  | 	row := q.conn.QueryRow(ctx, insertUserFieldSQL, params.UserID, params.Name, q.types.newFieldEntryArrayInit(params.Entries)) | ||||||
|  | 	var item InsertUserFieldRow | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	if err := row.Scan(&item.UserID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("query InsertUserField: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign InsertUserField row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertUserFieldBatch implements Querier.InsertUserFieldBatch. | ||||||
|  | func (q *DBQuerier) InsertUserFieldBatch(batch genericBatch, params InsertUserFieldParams) { | ||||||
|  | 	batch.Queue(insertUserFieldSQL, params.UserID, params.Name, q.types.newFieldEntryArrayInit(params.Entries)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertUserFieldScan implements Querier.InsertUserFieldScan. | ||||||
|  | func (q *DBQuerier) InsertUserFieldScan(results pgx.BatchResults) (InsertUserFieldRow, error) { | ||||||
|  | 	row := results.QueryRow() | ||||||
|  | 	var item InsertUserFieldRow | ||||||
|  | 	entriesArray := q.types.newFieldEntryArray() | ||||||
|  | 	if err := row.Scan(&item.UserID, &item.ID, &item.Name, entriesArray); err != nil { | ||||||
|  | 		return item, fmt.Errorf("scan InsertUserFieldBatch row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := entriesArray.AssignTo(&item.Entries); err != nil { | ||||||
|  | 		return item, fmt.Errorf("assign InsertUserField row: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return item, nil | ||||||
|  | } | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 
 | 
 | ||||||
|  | 	"codeberg.org/u1f320/pronouns.cc/backend/db/queries" | ||||||
| 	"emperror.dev/errors" | 	"emperror.dev/errors" | ||||||
| 	"github.com/bwmarrin/discordgo" | 	"github.com/bwmarrin/discordgo" | ||||||
| 	"github.com/georgysavva/scany/pgxscan" | 	"github.com/georgysavva/scany/pgxscan" | ||||||
|  | @ -21,6 +22,9 @@ type User struct { | ||||||
| 	AvatarURLs []string `db:"avatar_urls"` | 	AvatarURLs []string `db:"avatar_urls"` | ||||||
| 	Links      []string | 	Links      []string | ||||||
| 
 | 
 | ||||||
|  | 	Names    []FieldEntry | ||||||
|  | 	Pronouns []PronounEntry | ||||||
|  | 
 | ||||||
| 	Discord         *string | 	Discord         *string | ||||||
| 	DiscordUsername *string | 	DiscordUsername *string | ||||||
| 
 | 
 | ||||||
|  | @ -98,7 +102,7 @@ func (db *DB) DiscordUser(ctx context.Context, discordID string) (u User, err er | ||||||
| 	return u, nil | 	return u, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (u *User) UpdateFromDiscord(ctx context.Context, db pgxscan.Querier, du *discordgo.User) error { | func (u *User) UpdateFromDiscord(ctx context.Context, db querier, du *discordgo.User) error { | ||||||
| 	builder := sq.Update("users"). | 	builder := sq.Update("users"). | ||||||
| 		Set("discord", du.ID). | 		Set("discord", du.ID). | ||||||
| 		Set("discord_username", du.String()). | 		Set("discord_username", du.String()). | ||||||
|  | @ -113,14 +117,28 @@ func (u *User) UpdateFromDiscord(ctx context.Context, db pgxscan.Querier, du *di | ||||||
| 	return pgxscan.Get(ctx, db, u, sql, args...) | 	return pgxscan.Get(ctx, db, u, sql, args...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (db *DB) getUser(ctx context.Context, q pgxscan.Querier, id xid.ID) (u User, err error) { | func (db *DB) getUser(ctx context.Context, q querier, id xid.ID) (u User, err error) { | ||||||
| 	err = pgxscan.Get(ctx, q, &u, "select * from users where id = $1", id) | 	qu, err := queries.NewQuerier(q).GetUserByID(ctx, id.String()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Cause(err) == pgx.ErrNoRows { | 		if errors.Cause(err) == pgx.ErrNoRows { | ||||||
| 			return u, ErrUserNotFound | 			return u, ErrUserNotFound | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return u, errors.Cause(err) | 		return u, errors.Wrap(err, "getting user from database") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	u = User{ | ||||||
|  | 		ID:              id, | ||||||
|  | 		Username:        qu.Username, | ||||||
|  | 		DisplayName:     qu.DisplayName, | ||||||
|  | 		Bio:             qu.Bio, | ||||||
|  | 		AvatarURLs:      qu.AvatarUrls, | ||||||
|  | 		Names:           fieldEntriesFromDB(qu.Names), | ||||||
|  | 		Pronouns:        pronounsFromDB(qu.Pronouns), | ||||||
|  | 		Links:           qu.Links, | ||||||
|  | 		Discord:         qu.Discord, | ||||||
|  | 		DiscordUsername: qu.DiscordUsername, | ||||||
|  | 		MaxInvites:      int(qu.MaxInvites), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return u, nil | 	return u, nil | ||||||
|  | @ -133,13 +151,32 @@ func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) { | ||||||
| 
 | 
 | ||||||
| // Username gets a user by username. | // Username gets a user by username. | ||||||
| func (db *DB) Username(ctx context.Context, name string) (u User, err error) { | func (db *DB) Username(ctx context.Context, name string) (u User, err error) { | ||||||
| 	err = pgxscan.Get(ctx, db, &u, "select * from users where username = $1", name) | 	qu, err := db.q.GetUserByUsername(ctx, name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Cause(err) == pgx.ErrNoRows { | 		if errors.Cause(err) == pgx.ErrNoRows { | ||||||
| 			return u, ErrUserNotFound | 			return u, ErrUserNotFound | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return u, errors.Cause(err) | 		return u, errors.Wrap(err, "getting user from db") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	id, err := xid.FromString(qu.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return u, errors.Wrap(err, "parsing ID") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	u = User{ | ||||||
|  | 		ID:              id, | ||||||
|  | 		Username:        qu.Username, | ||||||
|  | 		DisplayName:     qu.DisplayName, | ||||||
|  | 		Bio:             qu.Bio, | ||||||
|  | 		AvatarURLs:      qu.AvatarUrls, | ||||||
|  | 		Names:           fieldEntriesFromDB(qu.Names), | ||||||
|  | 		Pronouns:        pronounsFromDB(qu.Pronouns), | ||||||
|  | 		Links:           qu.Links, | ||||||
|  | 		Discord:         qu.Discord, | ||||||
|  | 		DiscordUsername: qu.DiscordUsername, | ||||||
|  | 		MaxInvites:      int(qu.MaxInvites), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return u, nil | 	return u, nil | ||||||
|  | @ -223,15 +260,19 @@ func (db *DB) UpdateUser( | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sql, args, err := builder.Suffix("RETURNING *").ToSql() | 	sql, args, err := builder.ToSql() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return u, errors.Wrap(err, "building sql") | 		return u, errors.Wrap(err, "building sql") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = pgxscan.Get(ctx, tx, &u, sql, args...) | 	_, err = tx.Exec(ctx, sql, args...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return u, errors.Wrap(err, "executing sql") | 		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 | 	return u, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -133,17 +133,25 @@ func (bot *Bot) userPronouns(w http.ResponseWriter, r *http.Request, ev *discord | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, field := range fields { | 	for _, field := range fields { | ||||||
| 		if len(field.Favourite) == 0 { | 		var favs []db.FieldEntry | ||||||
|  | 
 | ||||||
|  | 		for _, e := range field.Entries { | ||||||
|  | 			if e.Status == db.StatusFavourite { | ||||||
|  | 				favs = append(favs, e) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if len(favs) == 0 { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		var value string | 		var value string | ||||||
| 		for _, fav := range field.Favourite { | 		for _, fav := range favs { | ||||||
| 			if len(value) > 500 { | 			if len(fav.Value) > 500 { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			value += fav + "\n" | 			value += fav.Value + "\n" | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		e.Fields = append(e.Fields, &discordgo.MessageEmbedField{ | 		e.Fields = append(e.Fields, &discordgo.MessageEmbedField{ | ||||||
|  |  | ||||||
|  | @ -12,14 +12,14 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type CreateMemberRequest struct { | type CreateMemberRequest struct { | ||||||
| 	Name        string       `json:"name"` | 	Name        string            `json:"name"` | ||||||
| 	DisplayName *string      `json:"display_name"` | 	DisplayName *string           `json:"display_name"` | ||||||
| 	Bio         string       `json:"bio"` | 	Bio         string            `json:"bio"` | ||||||
| 	Avatar      string       `json:"avatar"` | 	Avatar      string            `json:"avatar"` | ||||||
| 	Links       []string     `json:"links"` | 	Links       []string          `json:"links"` | ||||||
| 	Names       []db.Name    `json:"names"` | 	Names       []db.FieldEntry   `json:"names"` | ||||||
| 	Pronouns    []db.Pronoun `json:"pronouns"` | 	Pronouns    []db.PronounEntry `json:"pronouns"` | ||||||
| 	Fields      []db.Field   `json:"fields"` | 	Fields      []db.Field        `json:"fields"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error) { | 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 | 	// 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 { | 	if err != nil { | ||||||
| 		log.Errorf("setting names for member %v: %v", m.ID, err) | 		log.Errorf("setting names and pronouns 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) |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	m.Names = cmr.Names | ||||||
|  | 	m.Pronouns = cmr.Pronouns | ||||||
|  | 
 | ||||||
| 	err = s.DB.SetMemberFields(ctx, tx, m.ID, cmr.Fields) | 	err = s.DB.SetMemberFields(ctx, tx, m.ID, cmr.Fields) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Errorf("setting fields for member %v: %v", m.ID, err) | 		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") | 		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 | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,14 +19,14 @@ type GetMemberResponse struct { | ||||||
| 	AvatarURLs  []string `json:"avatar_urls"` | 	AvatarURLs  []string `json:"avatar_urls"` | ||||||
| 	Links       []string `json:"links"` | 	Links       []string `json:"links"` | ||||||
| 
 | 
 | ||||||
| 	Names    []db.Name    `json:"names"` | 	Names    []db.FieldEntry   `json:"names"` | ||||||
| 	Pronouns []db.Pronoun `json:"pronouns"` | 	Pronouns []db.PronounEntry `json:"pronouns"` | ||||||
| 	Fields   []db.Field   `json:"fields"` | 	Fields   []db.Field        `json:"fields"` | ||||||
| 
 | 
 | ||||||
| 	User PartialUser `json:"user"` | 	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{ | 	return GetMemberResponse{ | ||||||
| 		ID:          m.ID, | 		ID:          m.ID, | ||||||
| 		Name:        m.Name, | 		Name:        m.Name, | ||||||
|  | @ -35,8 +35,8 @@ func dbMemberToMember(u db.User, m db.Member, names []db.Name, pronouns []db.Pro | ||||||
| 		AvatarURLs:  m.AvatarURLs, | 		AvatarURLs:  m.AvatarURLs, | ||||||
| 		Links:       m.Links, | 		Links:       m.Links, | ||||||
| 
 | 
 | ||||||
| 		Names:    names, | 		Names:    m.Names, | ||||||
| 		Pronouns: pronouns, | 		Pronouns: m.Pronouns, | ||||||
| 		Fields:   fields, | 		Fields:   fields, | ||||||
| 
 | 
 | ||||||
| 		User: PartialUser{ | 		User: PartialUser{ | ||||||
|  | @ -77,22 +77,12 @@ func (s *Server) getMember(w http.ResponseWriter, r *http.Request) error { | ||||||
| 		return err | 		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) | 	fields, err := s.DB.MemberFields(ctx, m.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	render.JSON(w, r, dbMemberToMember(u, m, names, pronouns, fields)) | 	render.JSON(w, r, dbMemberToMember(u, m, fields)) | ||||||
| 	return nil | 	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) | 	fields, err := s.DB.MemberFields(ctx, m.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	render.JSON(w, r, dbMemberToMember(u, m, names, pronouns, fields)) | 	render.JSON(w, r, dbMemberToMember(u, m, fields)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,14 +14,14 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type PatchMemberRequest struct { | type PatchMemberRequest struct { | ||||||
| 	Name        *string       `json:"name"` | 	Name        *string            `json:"name"` | ||||||
| 	Bio         *string       `json:"bio"` | 	Bio         *string            `json:"bio"` | ||||||
| 	DisplayName *string       `json:"display_name"` | 	DisplayName *string            `json:"display_name"` | ||||||
| 	Links       *[]string     `json:"links"` | 	Links       *[]string          `json:"links"` | ||||||
| 	Names       *[]db.Name    `json:"names"` | 	Names       *[]db.FieldEntry   `json:"names"` | ||||||
| 	Pronouns    *[]db.Pronoun `json:"pronouns"` | 	Pronouns    *[]db.PronounEntry `json:"pronouns"` | ||||||
| 	Fields      *[]db.Field   `json:"fields"` | 	Fields      *[]db.Field        `json:"fields"` | ||||||
| 	Avatar      *string       `json:"avatar"` | 	Avatar      *string            `json:"avatar"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { | 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 ( | 	if req.Names != nil || req.Pronouns != nil { | ||||||
| 		names    []db.Name | 		names := m.Names | ||||||
| 		pronouns []db.Pronoun | 		pronouns := m.Pronouns | ||||||
| 		fields   []db.Field |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	if req.Names != nil { | 		if req.Names != nil { | ||||||
| 		err = s.DB.SetMemberNames(ctx, tx, id, *req.Names) | 			names = *req.Names | ||||||
|  | 		} | ||||||
|  | 		if req.Pronouns != nil { | ||||||
|  | 			pronouns = *req.Pronouns | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = s.DB.SetMemberNamesPronouns(ctx, tx, id, names, pronouns) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Errorf("setting names for member %v: %v", id, err) | 			log.Errorf("setting names for member %v: %v", id, err) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		names = *req.Names | 		m.Names = names | ||||||
| 	} else { | 		m.Pronouns = pronouns | ||||||
| 		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 |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var fields []db.Field | ||||||
| 	if req.Fields != nil { | 	if req.Fields != nil { | ||||||
| 		err = s.DB.SetMemberFields(ctx, tx, id, *req.Fields) | 		err = s.DB.SetMemberFields(ctx, tx, id, *req.Fields) | ||||||
| 		if err != nil { | 		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 | 	// 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 | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -12,16 +12,16 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type GetUserResponse struct { | type GetUserResponse struct { | ||||||
| 	ID          xid.ID          `json:"id"` | 	ID          xid.ID            `json:"id"` | ||||||
| 	Username    string          `json:"name"` | 	Username    string            `json:"name"` | ||||||
| 	DisplayName *string         `json:"display_name"` | 	DisplayName *string           `json:"display_name"` | ||||||
| 	Bio         *string         `json:"bio"` | 	Bio         *string           `json:"bio"` | ||||||
| 	AvatarURLs  []string        `json:"avatar_urls"` | 	AvatarURLs  []string          `json:"avatar_urls"` | ||||||
| 	Links       []string        `json:"links"` | 	Links       []string          `json:"links"` | ||||||
| 	Names       []db.Name       `json:"names"` | 	Names       []db.FieldEntry   `json:"names"` | ||||||
| 	Pronouns    []db.Pronoun    `json:"pronouns"` | 	Pronouns    []db.PronounEntry `json:"pronouns"` | ||||||
| 	Members     []PartialMember `json:"members"` | 	Members     []PartialMember   `json:"members"` | ||||||
| 	Fields      []db.Field      `json:"fields"` | 	Fields      []db.Field        `json:"fields"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GetMeResponse struct { | type GetMeResponse struct { | ||||||
|  | @ -38,7 +38,7 @@ type PartialMember struct { | ||||||
| 	AvatarURLs  []string `json:"avatar_urls"` | 	AvatarURLs  []string `json:"avatar_urls"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func dbUserToResponse(u db.User, fields []db.Field, names []db.Name, pronouns []db.Pronoun, members []db.Member) GetUserResponse { | func dbUserToResponse(u db.User, fields []db.Field, members []db.Member) GetUserResponse { | ||||||
| 	resp := GetUserResponse{ | 	resp := GetUserResponse{ | ||||||
| 		ID:          u.ID, | 		ID:          u.ID, | ||||||
| 		Username:    u.Username, | 		Username:    u.Username, | ||||||
|  | @ -46,8 +46,8 @@ func dbUserToResponse(u db.User, fields []db.Field, names []db.Name, pronouns [] | ||||||
| 		Bio:         u.Bio, | 		Bio:         u.Bio, | ||||||
| 		AvatarURLs:  u.AvatarURLs, | 		AvatarURLs:  u.AvatarURLs, | ||||||
| 		Links:       u.Links, | 		Links:       u.Links, | ||||||
| 		Names:       names, | 		Names:       u.Names, | ||||||
| 		Pronouns:    pronouns, | 		Pronouns:    u.Pronouns, | ||||||
| 		Fields:      fields, | 		Fields:      fields, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -78,25 +78,13 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			names, err := s.DB.UserNames(ctx, u.ID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Errorf("getting user names: %v", err) |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			pronouns, err := s.DB.UserPronouns(ctx, u.ID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Errorf("getting user pronouns: %v", err) |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			members, err := s.DB.UserMembers(ctx, u.ID) | 			members, err := s.DB.UserMembers(ctx, u.ID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Errorf("Error getting user members: %v", err) | 				log.Errorf("Error getting user members: %v", err) | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			render.JSON(w, r, dbUserToResponse(u, fields, names, pronouns, members)) | 			render.JSON(w, r, dbUserToResponse(u, fields, members)) | ||||||
| 			return nil | 			return nil | ||||||
| 		} else if err != db.ErrUserNotFound { | 		} else if err != db.ErrUserNotFound { | ||||||
| 			log.Errorf("Error getting user by ID: %v", err) | 			log.Errorf("Error getting user by ID: %v", err) | ||||||
|  | @ -116,18 +104,6 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	names, err := s.DB.UserNames(ctx, u.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Errorf("getting user names: %v", err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pronouns, err := s.DB.UserPronouns(ctx, u.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Errorf("getting user pronouns: %v", err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fields, err := s.DB.UserFields(ctx, u.ID) | 	fields, err := s.DB.UserFields(ctx, u.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Errorf("Error getting user fields: %v", err) | 		log.Errorf("Error getting user fields: %v", err) | ||||||
|  | @ -140,7 +116,7 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	render.JSON(w, r, dbUserToResponse(u, fields, names, pronouns, members)) | 	render.JSON(w, r, dbUserToResponse(u, fields, members)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -154,18 +130,6 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	names, err := s.DB.UserNames(ctx, u.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Errorf("getting user names: %v", err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pronouns, err := s.DB.UserPronouns(ctx, u.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Errorf("getting user pronouns: %v", err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fields, err := s.DB.UserFields(ctx, u.ID) | 	fields, err := s.DB.UserFields(ctx, u.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Errorf("Error getting user fields: %v", err) | 		log.Errorf("Error getting user fields: %v", err) | ||||||
|  | @ -179,7 +143,7 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	render.JSON(w, r, GetMeResponse{ | 	render.JSON(w, r, GetMeResponse{ | ||||||
| 		GetUserResponse: dbUserToResponse(u, fields, names, pronouns, members), | 		GetUserResponse: dbUserToResponse(u, fields, members), | ||||||
| 		Discord:         u.Discord, | 		Discord:         u.Discord, | ||||||
| 		DiscordUsername: u.DiscordUsername, | 		DiscordUsername: u.DiscordUsername, | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -12,14 +12,14 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type PatchUserRequest struct { | type PatchUserRequest struct { | ||||||
| 	Username    *string       `json:"username"` | 	Username    *string            `json:"username"` | ||||||
| 	DisplayName *string       `json:"display_name"` | 	DisplayName *string            `json:"display_name"` | ||||||
| 	Bio         *string       `json:"bio"` | 	Bio         *string            `json:"bio"` | ||||||
| 	Links       *[]string     `json:"links"` | 	Links       *[]string          `json:"links"` | ||||||
| 	Names       *[]db.Name    `json:"names"` | 	Names       *[]db.FieldEntry   `json:"names"` | ||||||
| 	Pronouns    *[]db.Pronoun `json:"pronouns"` | 	Pronouns    *[]db.PronounEntry `json:"pronouns"` | ||||||
| 	Fields      *[]db.Field   `json:"fields"` | 	Fields      *[]db.Field        `json:"fields"` | ||||||
| 	Avatar      *string       `json:"avatar"` | 	Avatar      *string            `json:"avatar"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // patchUser parses a PatchUserRequest and updates the user with the given ID. | // patchUser parses a PatchUserRequest and updates the user with the given ID. | ||||||
|  | @ -159,42 +159,27 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var ( | 	if req.Names != nil || req.Pronouns != nil { | ||||||
| 		names    []db.Name | 		names := u.Names | ||||||
| 		pronouns []db.Pronoun | 		pronouns := u.Pronouns | ||||||
| 		fields   []db.Field |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	if req.Names != nil { | 		if req.Names != nil { | ||||||
| 		err = s.DB.SetUserNames(ctx, tx, claims.UserID, *req.Names) | 			names = *req.Names | ||||||
|  | 		} | ||||||
|  | 		if req.Pronouns != nil { | ||||||
|  | 			pronouns = *req.Pronouns | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = s.DB.SetUserNamesPronouns(ctx, tx, claims.UserID, names, pronouns) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Errorf("setting names for user %v: %v", claims.UserID, err) | 			log.Errorf("setting names for member %v: %v", claims.UserID, err) | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		names = *req.Names |  | ||||||
| 	} else { |  | ||||||
| 		names, err = s.DB.UserNames(ctx, claims.UserID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Errorf("getting names for user %v: %v", claims.UserID, err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	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 |  | ||||||
| 		} |  | ||||||
| 		pronouns = *req.Pronouns |  | ||||||
| 	} else { |  | ||||||
| 		pronouns, err = s.DB.UserPronouns(ctx, claims.UserID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Errorf("getting fields for user %v: %v", claims.UserID, err) |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 		u.Names = names | ||||||
|  | 		u.Pronouns = pronouns | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var fields []db.Field | ||||||
| 	if req.Fields != nil { | 	if req.Fields != nil { | ||||||
| 		err = s.DB.SetUserFields(ctx, tx, claims.UserID, *req.Fields) | 		err = s.DB.SetUserFields(ctx, tx, claims.UserID, *req.Fields) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -217,7 +202,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// echo the updated user back on success | 	// echo the updated user back on success | ||||||
| 	render.JSON(w, r, dbUserToResponse(u, fields, names, pronouns, nil)) | 	render.JSON(w, r, dbUserToResponse(u, fields, nil)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ export type PartialMember = PartialPerson; | ||||||
| export interface _Person extends PartialPerson { | export interface _Person extends PartialPerson { | ||||||
|   bio: string | null; |   bio: string | null; | ||||||
|   links: Arr<string>; |   links: Arr<string>; | ||||||
|   names: Arr<Name>; |   names: Arr<FieldEntry>; | ||||||
|   pronouns: Arr<Pronoun>; |   pronouns: Arr<Pronoun>; | ||||||
|   fields: Arr<Field>; |   fields: Arr<Field>; | ||||||
| } | } | ||||||
|  | @ -38,11 +38,6 @@ export interface MeUser extends User { | ||||||
|   discord_username: string | null; |   discord_username: string | null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface Name { |  | ||||||
|   name: string; |  | ||||||
|   status: WordStatus; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface Pronoun { | export interface Pronoun { | ||||||
|   display_text?: string; |   display_text?: string; | ||||||
|   pronouns: string; |   pronouns: string; | ||||||
|  | @ -51,11 +46,12 @@ export interface Pronoun { | ||||||
| 
 | 
 | ||||||
| export interface Field { | export interface Field { | ||||||
|   name: string; |   name: string; | ||||||
|   favourite: Arr<string>; |   entries: Arr<FieldEntry>; | ||||||
|   okay: Arr<string>; | } | ||||||
|   jokingly: Arr<string>; | 
 | ||||||
|   friends_only: Arr<string>; | export interface FieldEntry { | ||||||
|   avoid: Arr<string>; |   value: string; | ||||||
|  |   status: WordStatus; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface APIError { | export interface APIError { | ||||||
|  |  | ||||||
|  | @ -164,11 +164,11 @@ export class Label { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class Name extends Label { | export class Name extends Label { | ||||||
|   constructor({ name, status }: API.Name) { |   constructor({ value, status }: API.FieldEntry) { | ||||||
|     super({ |     super({ | ||||||
|       type: LabelType.Name, |       type: LabelType.Name, | ||||||
|       displayText: null, |       displayText: null, | ||||||
|       text: name, |       text: value, | ||||||
|       status, |       status, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | @ -199,31 +199,11 @@ export class Pronouns extends Label { | ||||||
| export class Field { | export class Field { | ||||||
|   name: string; |   name: string; | ||||||
|   labels: Label[]; |   labels: Label[]; | ||||||
|   constructor({ |   constructor({ name, entries }: API.Field) { | ||||||
|     name, |  | ||||||
|     favourite, |  | ||||||
|     okay, |  | ||||||
|     jokingly, |  | ||||||
|     friends_only, |  | ||||||
|     avoid, |  | ||||||
|   }: API.Field) { |  | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     function transpose(arr: API.Arr<string>, status: LabelStatus): Label[] { |     this.labels = | ||||||
|       return (arr ?? []).map( |       entries?.map( | ||||||
|         (text) => |         (e) => new Label({ displayText: null, text: e.value, status: e.status }) | ||||||
|           new Label({ |       ) ?? []; | ||||||
|             displayText: null, |  | ||||||
|             text, |  | ||||||
|             status, |  | ||||||
|           }) |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|     this.labels = [ |  | ||||||
|       ...transpose(favourite, LabelStatus.Favourite), |  | ||||||
|       ...transpose(okay, LabelStatus.Okay), |  | ||||||
|       ...transpose(jokingly, LabelStatus.Jokingly), |  | ||||||
|       ...transpose(friends_only, LabelStatus.FriendsOnly), |  | ||||||
|       ...transpose(avoid, LabelStatus.Avoid), |  | ||||||
|     ]; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ create table users ( | ||||||
|     discord          text unique, -- for Discord oauth |     discord          text unique, -- for Discord oauth | ||||||
|     discord_username text, |     discord_username text, | ||||||
| 
 | 
 | ||||||
|     max_invites int default 10 |     max_invites int not null default 10 | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| create table user_names ( | create table user_names ( | ||||||
|  |  | ||||||
							
								
								
									
										35
									
								
								scripts/migrate/004_field_arrays.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								scripts/migrate/004_field_arrays.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | -- +migrate Up | ||||||
|  | 
 | ||||||
|  | -- 2023-01-03: change names, pronouns, and fields to be columns instead of separate tables | ||||||
|  | 
 | ||||||
|  | create type field_entry as ( | ||||||
|  |     value  text, | ||||||
|  |     status int | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create type pronoun_entry as ( | ||||||
|  |     value         text, | ||||||
|  |     display_value text, | ||||||
|  |     status        int | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | alter table users add column names field_entry[]; | ||||||
|  | alter table users add column pronouns pronoun_entry[]; | ||||||
|  | 
 | ||||||
|  | alter table members add column names field_entry[]; | ||||||
|  | alter table members add column pronouns pronoun_entry[]; | ||||||
|  | 
 | ||||||
|  | alter table user_fields add column entries field_entry[]; | ||||||
|  | alter table member_fields add column entries field_entry[]; | ||||||
|  | 
 | ||||||
|  | alter table user_fields drop column favourite; | ||||||
|  | alter table user_fields drop column okay; | ||||||
|  | alter table user_fields drop column jokingly; | ||||||
|  | alter table user_fields drop column friends_only; | ||||||
|  | alter table user_fields drop column avoid; | ||||||
|  | 
 | ||||||
|  | alter table member_fields drop column favourite; | ||||||
|  | alter table member_fields drop column okay; | ||||||
|  | alter table member_fields drop column jokingly; | ||||||
|  | alter table member_fields drop column friends_only; | ||||||
|  | alter table member_fields drop column avoid; | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue