feat(backend): add unlisted members, private member list, custom members header
This commit is contained in:
		
							parent
							
								
									ba48ba0eb2
								
							
						
					
					
						commit
						ef9b186e66
					
				
					 14 changed files with 135 additions and 34 deletions
				
			
		|  | @ -26,6 +26,7 @@ type Member struct { | |||
| 	Links       []string | ||||
| 	Names       []FieldEntry | ||||
| 	Pronouns    []PronounEntry | ||||
| 	Unlisted    bool | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
|  | @ -68,10 +69,15 @@ func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) ( | |||
| } | ||||
| 
 | ||||
| // UserMembers returns all of a user's members, sorted by name. | ||||
| func (db *DB) UserMembers(ctx context.Context, userID xid.ID) (ms []Member, err error) { | ||||
| 	sql, args, err := sq.Select("*"). | ||||
| func (db *DB) UserMembers(ctx context.Context, userID xid.ID, showHidden bool) (ms []Member, err error) { | ||||
| 	builder := sq.Select("*"). | ||||
| 		From("members").Where("user_id = ?", userID). | ||||
| 		OrderBy("name", "id").ToSql() | ||||
| 		OrderBy("name", "id") | ||||
| 	if !showHidden { | ||||
| 		builder = builder.Where("unlisted = ?", false) | ||||
| 	} | ||||
| 
 | ||||
| 	sql, args, err := builder.ToSql() | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "building sql") | ||||
| 	} | ||||
|  | @ -148,6 +154,7 @@ func (db *DB) UpdateMember( | |||
| 	ctx context.Context, | ||||
| 	tx pgx.Tx, id xid.ID, | ||||
| 	name, displayName, bio *string, | ||||
| 	unlisted *bool, | ||||
| 	links *[]string, | ||||
| 	avatar *string, | ||||
| ) (m Member, err error) { | ||||
|  | @ -190,6 +197,9 @@ func (db *DB) UpdateMember( | |||
| 	if links != nil { | ||||
| 		builder = builder.Set("links", *links) | ||||
| 	} | ||||
| 	if unlisted != nil { | ||||
| 		builder = builder.Set("unlisted", *unlisted) | ||||
| 	} | ||||
| 
 | ||||
| 	if avatar != nil { | ||||
| 		if *avatar == "" { | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ type User struct { | |||
| 	Username    string | ||||
| 	DisplayName *string | ||||
| 	Bio         *string | ||||
| 	MemberTitle *string | ||||
| 
 | ||||
| 	Avatar *string | ||||
| 	Links  []string | ||||
|  | @ -35,8 +36,9 @@ type User struct { | |||
| 	FediverseAppID    *int64 | ||||
| 	FediverseInstance *string | ||||
| 
 | ||||
| 	MaxInvites int | ||||
| 	IsAdmin    bool | ||||
| 	MaxInvites  int | ||||
| 	IsAdmin     bool | ||||
| 	ListPrivate bool | ||||
| 
 | ||||
| 	DeletedAt    *time.Time | ||||
| 	SelfDelete   *bool | ||||
|  | @ -317,10 +319,11 @@ func (db *DB) UpdateUser( | |||
| 	ctx context.Context, | ||||
| 	tx pgx.Tx, id xid.ID, | ||||
| 	displayName, bio *string, | ||||
| 	memberTitle *string, listPrivate *bool, | ||||
| 	links *[]string, | ||||
| 	avatar *string, | ||||
| ) (u User, err error) { | ||||
| 	if displayName == nil && bio == nil && links == nil && avatar == nil { | ||||
| 	if displayName == nil && bio == nil && links == nil && avatar == nil && memberTitle == nil && listPrivate == nil { | ||||
| 		sql, args, err := sq.Select("*").From("users").Where("id = ?", id).ToSql() | ||||
| 		if err != nil { | ||||
| 			return u, errors.Wrap(err, "building sql") | ||||
|  | @ -349,9 +352,19 @@ func (db *DB) UpdateUser( | |||
| 			builder = builder.Set("bio", *bio) | ||||
| 		} | ||||
| 	} | ||||
| 	if memberTitle != nil { | ||||
| 		if *memberTitle == "" { | ||||
| 			builder = builder.Set("member_title", nil) | ||||
| 		} else { | ||||
| 			builder = builder.Set("member_title", *memberTitle) | ||||
| 		} | ||||
| 	} | ||||
| 	if links != nil { | ||||
| 		builder = builder.Set("links", *links) | ||||
| 	} | ||||
| 	if listPrivate != nil { | ||||
| 		builder = builder.Set("list_private", *listPrivate) | ||||
| 	} | ||||
| 
 | ||||
| 	if avatar != nil { | ||||
| 		if *avatar == "" { | ||||
|  | @ -492,7 +505,7 @@ func (db *DB) CleanUser(ctx context.Context, id xid.ID) error { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	members, err := db.UserMembers(ctx, u.ID) | ||||
| 	members, err := db.UserMembers(ctx, u.ID, true) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "getting members") | ||||
| 	} | ||||
|  |  | |||
|  | @ -175,7 +175,7 @@ func (s *server) doExport(u db.User) { | |||
| 		log.Debugf("[%v] exported user avatar", u.ID) | ||||
| 	} | ||||
| 
 | ||||
| 	members, err := s.DB.UserMembers(ctx, u.ID) | ||||
| 	members, err := s.DB.UserMembers(ctx, u.ID, true) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("[%v] getting user members: %v", u.ID, err) | ||||
| 		return | ||||
|  |  | |||
|  | @ -161,7 +161,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error | |||
| 		return errors.Wrap(err, "committing transaction") | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, cmr.Fields)) | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, cmr.Fields, true)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,10 +24,12 @@ type GetMemberResponse struct { | |||
| 	Fields   []db.Field        `json:"fields"` | ||||
| 
 | ||||
| 	User PartialUser `json:"user"` | ||||
| 
 | ||||
| 	Unlisted *bool `json:"unlisted,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func dbMemberToMember(u db.User, m db.Member, fields []db.Field) GetMemberResponse { | ||||
| 	return GetMemberResponse{ | ||||
| func dbMemberToMember(u db.User, m db.Member, fields []db.Field, isOwnMember bool) GetMemberResponse { | ||||
| 	r := GetMemberResponse{ | ||||
| 		ID:          m.ID, | ||||
| 		Name:        m.Name, | ||||
| 		DisplayName: m.DisplayName, | ||||
|  | @ -46,6 +48,12 @@ func dbMemberToMember(u db.User, m db.Member, fields []db.Field) GetMemberRespon | |||
| 			Avatar:      u.Avatar, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	if isOwnMember { | ||||
| 		r.Unlisted = &m.Unlisted | ||||
| 	} | ||||
| 
 | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| type PartialUser struct { | ||||
|  | @ -81,12 +89,17 @@ func (s *Server) getMember(w http.ResponseWriter, r *http.Request) error { | |||
| 		return server.APIError{Code: server.ErrMemberNotFound} | ||||
| 	} | ||||
| 
 | ||||
| 	isOwnMember := false | ||||
| 	if claims, ok := server.ClaimsFromContext(ctx); ok && claims.UserID == u.ID { | ||||
| 		isOwnMember = true | ||||
| 	} | ||||
| 
 | ||||
| 	fields, err := s.DB.MemberFields(ctx, m.ID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields)) | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields, isOwnMember)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -104,6 +117,11 @@ func (s *Server) getUserMember(w http.ResponseWriter, r *http.Request) error { | |||
| 		return server.APIError{Code: server.ErrUserNotFound} | ||||
| 	} | ||||
| 
 | ||||
| 	isOwnMember := false | ||||
| 	if claims, ok := server.ClaimsFromContext(ctx); ok && claims.UserID == u.ID { | ||||
| 		isOwnMember = true | ||||
| 	} | ||||
| 
 | ||||
| 	m, err := s.DB.UserMember(ctx, u.ID, chi.URLParam(r, "memberRef")) | ||||
| 	if err != nil { | ||||
| 		return server.APIError{ | ||||
|  | @ -116,7 +134,7 @@ func (s *Server) getUserMember(w http.ResponseWriter, r *http.Request) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields)) | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields, isOwnMember)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,7 +52,16 @@ func (s *Server) getUserMembers(w http.ResponseWriter, r *http.Request) error { | |||
| 		return server.APIError{Code: server.ErrUserNotFound} | ||||
| 	} | ||||
| 
 | ||||
| 	ms, err := s.DB.UserMembers(ctx, u.ID) | ||||
| 	isSelf := false | ||||
| 	if claims, ok := server.ClaimsFromContext(ctx); ok && claims.UserID == u.ID { | ||||
| 		isSelf = true | ||||
| 	} | ||||
| 
 | ||||
| 	if u.ListPrivate && !isSelf { | ||||
| 		return server.APIError{Code: server.ErrMemberListPrivate} | ||||
| 	} | ||||
| 
 | ||||
| 	ms, err := s.DB.UserMembers(ctx, u.ID, isSelf) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -65,7 +74,7 @@ func (s *Server) getMeMembers(w http.ResponseWriter, r *http.Request) error { | |||
| 	ctx := r.Context() | ||||
| 	claims, _ := server.ClaimsFromContext(ctx) | ||||
| 
 | ||||
| 	ms, err := s.DB.UserMembers(ctx, claims.UserID) | ||||
| 	ms, err := s.DB.UserMembers(ctx, claims.UserID, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ type PatchMemberRequest struct { | |||
| 	Pronouns    *[]db.PronounEntry `json:"pronouns"` | ||||
| 	Fields      *[]db.Field        `json:"fields"` | ||||
| 	Avatar      *string            `json:"avatar"` | ||||
| 	Unlisted    *bool              `json:"unlisted"` | ||||
| } | ||||
| 
 | ||||
| func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { | ||||
|  | @ -62,6 +63,7 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { | |||
| 	if req.DisplayName == nil && | ||||
| 		req.Name == nil && | ||||
| 		req.Bio == nil && | ||||
| 		req.Unlisted == nil && | ||||
| 		req.Links == nil && | ||||
| 		req.Fields == nil && | ||||
| 		req.Names == nil && | ||||
|  | @ -213,7 +215,7 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { | |||
| 	} | ||||
| 	defer tx.Rollback(ctx) | ||||
| 
 | ||||
| 	m, err = s.DB.UpdateMember(ctx, tx, id, req.Name, req.DisplayName, req.Bio, req.Links, avatarHash) | ||||
| 	m, err = s.DB.UpdateMember(ctx, tx, id, req.Name, req.DisplayName, req.Bio, req.Unlisted, req.Links, avatarHash) | ||||
| 	if err != nil { | ||||
| 		switch errors.Cause(err) { | ||||
| 		case db.ErrNothingToUpdate: | ||||
|  | @ -274,6 +276,6 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { | |||
| 	} | ||||
| 
 | ||||
| 	// echo the updated member back on success | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields)) | ||||
| 	render.JSON(w, r, dbMemberToMember(u, m, fields, true)) | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ type GetUserResponse struct { | |||
| 	Username    string            `json:"name"` | ||||
| 	DisplayName *string           `json:"display_name"` | ||||
| 	Bio         *string           `json:"bio"` | ||||
| 	MemberTitle *string           `json:"member_title"` | ||||
| 	Avatar      *string           `json:"avatar"` | ||||
| 	Links       []string          `json:"links"` | ||||
| 	Names       []db.FieldEntry   `json:"names"` | ||||
|  | @ -27,8 +28,9 @@ type GetUserResponse struct { | |||
| type GetMeResponse struct { | ||||
| 	GetUserResponse | ||||
| 
 | ||||
| 	MaxInvites int  `json:"max_invites"` | ||||
| 	IsAdmin    bool `json:"is_admin"` | ||||
| 	MaxInvites  int  `json:"max_invites"` | ||||
| 	IsAdmin     bool `json:"is_admin"` | ||||
| 	ListPrivate bool `json:"list_private"` | ||||
| 
 | ||||
| 	Discord         *string `json:"discord"` | ||||
| 	DiscordUsername *string `json:"discord_username"` | ||||
|  | @ -55,6 +57,7 @@ func dbUserToResponse(u db.User, fields []db.Field, members []db.Member) GetUser | |||
| 		Username:    u.Username, | ||||
| 		DisplayName: u.DisplayName, | ||||
| 		Bio:         u.Bio, | ||||
| 		MemberTitle: u.MemberTitle, | ||||
| 		Avatar:      u.Avatar, | ||||
| 		Links:       db.NotNull(u.Links), | ||||
| 		Names:       db.NotNull(u.Names), | ||||
|  | @ -87,16 +90,28 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error { | |||
| 	if id, err := xid.FromString(userRef); err == nil { | ||||
| 		u, err := s.DB.User(ctx, id) | ||||
| 		if err == nil { | ||||
| 			if u.DeletedAt != nil { | ||||
| 				return server.APIError{Code: server.ErrUserNotFound} | ||||
| 			} | ||||
| 
 | ||||
| 			isSelf := false | ||||
| 			if claims, ok := server.ClaimsFromContext(ctx); ok && claims.UserID == u.ID { | ||||
| 				isSelf = true | ||||
| 			} | ||||
| 
 | ||||
| 			fields, err := s.DB.UserFields(ctx, u.ID) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error getting user fields: %v", err) | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			members, err := s.DB.UserMembers(ctx, u.ID) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error getting user members: %v", err) | ||||
| 				return err | ||||
| 			var members []db.Member | ||||
| 			if !u.ListPrivate || isSelf { | ||||
| 				members, err = s.DB.UserMembers(ctx, u.ID, isSelf) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error getting user members: %v", err) | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			render.JSON(w, r, dbUserToResponse(u, fields, members)) | ||||
|  | @ -123,16 +138,24 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		return server.APIError{Code: server.ErrUserNotFound} | ||||
| 	} | ||||
| 
 | ||||
| 	isSelf := false | ||||
| 	if claims, ok := server.ClaimsFromContext(ctx); ok && claims.UserID == u.ID { | ||||
| 		isSelf = true | ||||
| 	} | ||||
| 
 | ||||
| 	fields, err := s.DB.UserFields(ctx, u.ID) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Error getting user fields: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	members, err := s.DB.UserMembers(ctx, u.ID) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Error getting user members: %v", err) | ||||
| 		return err | ||||
| 	var members []db.Member | ||||
| 	if !u.ListPrivate || isSelf { | ||||
| 		members, err = s.DB.UserMembers(ctx, u.ID, isSelf) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Error getting user members: %v", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	render.JSON(w, r, dbUserToResponse(u, fields, members)) | ||||
|  | @ -155,7 +178,7 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	members, err := s.DB.UserMembers(ctx, u.ID) | ||||
| 	members, err := s.DB.UserMembers(ctx, u.ID, true) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Error getting user members: %v", err) | ||||
| 		return err | ||||
|  | @ -165,6 +188,7 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		GetUserResponse:   dbUserToResponse(u, fields, members), | ||||
| 		MaxInvites:        u.MaxInvites, | ||||
| 		IsAdmin:           u.IsAdmin, | ||||
| 		ListPrivate:       u.ListPrivate, | ||||
| 		Discord:           u.Discord, | ||||
| 		DiscordUsername:   u.DiscordUsername, | ||||
| 		Fediverse:         u.Fediverse, | ||||
|  |  | |||
|  | @ -15,11 +15,13 @@ type PatchUserRequest struct { | |||
| 	Username    *string            `json:"username"` | ||||
| 	DisplayName *string            `json:"display_name"` | ||||
| 	Bio         *string            `json:"bio"` | ||||
| 	MemberTitle *string            `json:"member_title"` | ||||
| 	Links       *[]string          `json:"links"` | ||||
| 	Names       *[]db.FieldEntry   `json:"names"` | ||||
| 	Pronouns    *[]db.PronounEntry `json:"pronouns"` | ||||
| 	Fields      *[]db.Field        `json:"fields"` | ||||
| 	Avatar      *string            `json:"avatar"` | ||||
| 	ListPrivate *bool              `json:"list_private"` | ||||
| } | ||||
| 
 | ||||
| // patchUser parses a PatchUserRequest and updates the user with the given ID. | ||||
|  | @ -48,6 +50,8 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { | |||
| 	if req.Username == nil && | ||||
| 		req.DisplayName == nil && | ||||
| 		req.Bio == nil && | ||||
| 		req.MemberTitle == nil && | ||||
| 		req.ListPrivate == nil && | ||||
| 		req.Links == nil && | ||||
| 		req.Fields == nil && | ||||
| 		req.Names == nil && | ||||
|  | @ -72,6 +76,13 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { | |||
| 			Details: fmt.Sprintf("Bio too long (max %d, current %d)", db.MaxUserBioLength, len(*req.Bio)), | ||||
| 		} | ||||
| 	} | ||||
| 	// this is considered a name | ||||
| 	if req.MemberTitle != nil && len(*req.MemberTitle) > db.MaxDisplayNameLength { | ||||
| 		return server.APIError{ | ||||
| 			Code:    server.ErrBadRequest, | ||||
| 			Details: fmt.Sprintf("Member title too long (max %d, current %d)", db.MaxDisplayNameLength, len(*req.MemberTitle)), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// validate links | ||||
| 	if req.Links != nil { | ||||
|  | @ -175,7 +186,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.Links, avatarHash) | ||||
| 	u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.MemberTitle, req.ListPrivate, req.Links, avatarHash) | ||||
| 	if err != nil && errors.Cause(err) != db.ErrNothingToUpdate { | ||||
| 		log.Errorf("updating user: %v", err) | ||||
| 		return err | ||||
|  |  | |||
|  | @ -99,7 +99,8 @@ const ( | |||
| 	ErrLastProvider        = 1016 // unlinking provider would leave account with no authentication method | ||||
| 
 | ||||
| 	// User-related error codes | ||||
| 	ErrUserNotFound = 2001 | ||||
| 	ErrUserNotFound      = 2001 | ||||
| 	ErrMemberListPrivate = 2002 | ||||
| 
 | ||||
| 	// Member-related error codes | ||||
| 	ErrMemberNotFound     = 3001 | ||||
|  | @ -141,7 +142,8 @@ var errCodeMessages = map[int]string{ | |||
| 	ErrNotLinked:           "Your account is already not linked to an account of this type", | ||||
| 	ErrLastProvider:        "This is your account's only authentication provider", | ||||
| 
 | ||||
| 	ErrUserNotFound: "User not found", | ||||
| 	ErrUserNotFound:      "User not found", | ||||
| 	ErrMemberListPrivate: "This user's member list is private.", | ||||
| 
 | ||||
| 	ErrMemberNotFound:     "Member not found", | ||||
| 	ErrMemberLimitReached: "Member limit reached", | ||||
|  | @ -180,7 +182,8 @@ var errCodeStatuses = map[int]int{ | |||
| 	ErrNotLinked:           http.StatusBadRequest, | ||||
| 	ErrLastProvider:        http.StatusBadRequest, | ||||
| 
 | ||||
| 	ErrUserNotFound: http.StatusNotFound, | ||||
| 	ErrUserNotFound:      http.StatusNotFound, | ||||
| 	ErrMemberListPrivate: http.StatusForbidden, | ||||
| 
 | ||||
| 	ErrMemberNotFound:     http.StatusNotFound, | ||||
| 	ErrMemberLimitReached: http.StatusBadRequest, | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ export interface User { | |||
|   bio: string | null; | ||||
|   avatar: string | null; | ||||
|   links: string[]; | ||||
|   member_title: string | null; | ||||
| 
 | ||||
|   names: FieldEntry[]; | ||||
|   pronouns: Pronoun[]; | ||||
|  | @ -24,6 +25,7 @@ export interface MeUser extends User { | |||
|   fediverse: string | null; | ||||
|   fediverse_username: string | null; | ||||
|   fediverse_instance: string | null; | ||||
|   list_private: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface Field { | ||||
|  | @ -66,6 +68,7 @@ export interface Member extends PartialMember { | |||
|   fields: Field[]; | ||||
| 
 | ||||
|   user: MemberPartialUser; | ||||
|   unlisted?: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface MemberPartialUser { | ||||
|  |  | |||
|  | @ -83,7 +83,7 @@ func run(c *cli.Context) error { | |||
| 	} | ||||
| 
 | ||||
| 	for _, u := range users { | ||||
| 		members, err := db.UserMembers(ctx, u.ID) | ||||
| 		members, err := db.UserMembers(ctx, u.ID, true) | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("error getting members for user %v: %v\n", u.ID, err) | ||||
| 			continue | ||||
|  |  | |||
							
								
								
									
										8
									
								
								scripts/migrate/012_custom_options.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								scripts/migrate/012_custom_options.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| -- +migrate Up | ||||
| 
 | ||||
| -- 2023-04-01: Add a couple customization options to users and members | ||||
| 
 | ||||
| alter table users add column member_title text; | ||||
| alter table users add column list_private boolean not null default false; | ||||
| 
 | ||||
| alter table members add column unlisted boolean not null default false; | ||||
|  | @ -48,7 +48,7 @@ func run(c *cli.Context) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), &[]string{"https://pronouns.cc"}, nil) | ||||
| 	_, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), nil, ptr(false), &[]string{"https://pronouns.cc"}, nil) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error setting user info:", err) | ||||
| 		return err | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue