Documentation
¶
Index ¶
- Constants
- Variables
- func AssignDefaultUsername(ctx context.Context, base string, held HeldUsernamesFunc, ...) (string, error)
- func DefaultFlipcardCustomization() *profilepb.FlipcardCustomization
- func DefaultUsernameBase(displayName string) (string, bool)
- func FlipcardCustomizationFromStored(storedColorHex *string) *profilepb.FlipcardCustomization
- func IsUsernameReserved(username string) bool
- func MinDmChatInitFeeFromStored(storedCurrency *string, storedNativeAmount *float64) *commonpb.FiatPaymentAmount
- func NewChatProfileReader(store Store, media Media) chat.ProfileReader
- func NormalizeColorHex(hex string) string
- func NormalizeUsername(username string) string
- func ValidateMinDmChatInitFee(fee *commonpb.FiatPaymentAmount) error
- func ValidateUsername(username string) error
- type ClaimUsernameFunc
- type DefaultUsernameResult
- type FirstUsernameHandler
- type HeldUsernamesFunc
- type Media
- type PhoneForPayment
- type Server
- func (s *Server) GetProfile(ctx context.Context, req *profilepb.GetProfileRequest) (*profilepb.GetProfileResponse, error)
- func (s *Server) LinkSocialAccount(ctx context.Context, req *profilepb.LinkSocialAccountRequest) (*profilepb.LinkSocialAccountResponse, error)
- func (s *Server) SetDisplayName(ctx context.Context, req *profilepb.SetDisplayNameRequest) (*profilepb.SetDisplayNameResponse, error)
- func (s *Server) SetMinDmChatInitFee(ctx context.Context, req *profilepb.SetMinDmChatInitFeeRequest) (*profilepb.SetMinDmChatInitFeeResponse, error)
- func (s *Server) SetProfilePicture(ctx context.Context, req *profilepb.SetProfilePictureRequest) (*profilepb.SetProfilePictureResponse, error)
- func (s *Server) SetUsername(ctx context.Context, req *profilepb.SetUsernameRequest) (*profilepb.SetUsernameResponse, error)
- func (s *Server) UnlinkSocialAccount(ctx context.Context, req *profilepb.UnlinkSocialAccountRequest) (*profilepb.UnlinkSocialAccountResponse, error)
- func (s *Server) UpdateFlipcard(ctx context.Context, req *profilepb.UpdateFlipcardRequest) (*profilepb.UpdateFlipcardResponse, error)
- type ServerOption
- type Store
Constants ¶
const DefaultFlipcardColorHex = "#000000"
DefaultFlipcardColorHex is the Flipcard colour a user gets before they pick one of their own.
Variables ¶
var ErrExistingSocialLink = errors.New("existing social link")
var ErrInvalidDisplayName = errors.New("invalid display name")
var ErrInvalidMinDmChatInitFee = errors.New("invalid min dm chat init fee")
var ErrInvalidUsername = errors.New("invalid username")
var ErrNoDefaultUsername = errors.New("no default username available")
ErrNoDefaultUsername is returned when an assignment claims no handle: every candidate it is allowed to consider is held, unusable, or lost to another holder while it tried.
var ErrNotFound = errors.New("not found")
var ErrUsernameTaken = errors.New("username taken")
Functions ¶
func AssignDefaultUsername ¶ added in v1.33.0
func AssignDefaultUsername(ctx context.Context, base string, held HeldUsernamesFunc, claim ClaimUsernameFunc) (string, error)
AssignDefaultUsername claims a default handle for base and returns it, or "" when claim reported the user is no longer eligible for one.
Its first attempt claims the lowest free number. Each time a concurrent assignment takes a number first it searches again, and claims one picked at random from the lowest defaultUsernameRetrySpread free numbers rather than the lowest, so racing assignments of the same name drift apart instead of meeting again. If it loses maxLowestDefaultUsernameAttempts times, or every number the search considers is held, it falls back to claiming random numbers from minRandomDefaultUsernameNumber to maxRandomDefaultUsernameNumber — drawn only from the digit counts whose cut of base leaves a usable stem, so that no draw is wasted on a number that could never be a handle.
ErrNoDefaultUsername is returned when no handle could be claimed: no number considered yields a usable handle, or every one tried was lost to another holder. Either way the caller leaves the user without a handle rather than failing, since a contended name has all but run out of numbers anyway.
func DefaultFlipcardCustomization ¶ added in v1.33.0
func DefaultFlipcardCustomization() *profilepb.FlipcardCustomization
DefaultFlipcardCustomization returns the Flipcard customization for a user who has customized nothing. UserProfile.flipcard_customization is a required field, so every profile the store hands out carries one, resolved from the user's choices where they exist and from this default where they do not.
func DefaultUsernameBase ¶ added in v1.33.0
DefaultUsernameBase returns the stem default handles for displayName are built from, or false when displayName is not eligible for one: it must contain only ASCII letters, digits and spaces, and must not name a reserved word.
Leading and trailing spaces are dropped, and each run of spaces in between becomes a single underscore, so "Jeff Yanta" cannot mint a handle that differs from "Jeff Yanta"'s by an extra underscore.
func FlipcardCustomizationFromStored ¶ added in v1.33.0
func FlipcardCustomizationFromStored(storedColorHex *string) *profilepb.FlipcardCustomization
FlipcardCustomizationFromStored resolves a user's stored Flipcard choices into the customization their profile carries, filling in the default for anything they have not picked. storedColorHex is nil for a user who never chose a colour. Every store resolves through here, so the "always set" contract on UserProfile.flipcard_customization holds no matter which one served the read.
func IsUsernameReserved ¶ added in v1.27.0
IsUsernameReserved reports whether username contains a word the platform keeps for itself, and so may not be claimed by anyone.
A reserved word is recognized through every spelling a handle can hide it in: spaced out with underscores, or with digits standing in for its letters, so "s_u_p_p_o_r_t", "supp0rt" and "f1ipcash" are each the word they read as. A handle spelled in letters alone is only ever caught by the word it actually is.
It expects a handle in canonical form, which is lower case, so the comparison needs no folding of its own — NormalizeUsername first if that is in doubt.
func MinDmChatInitFeeFromStored ¶ added in v1.28.0
func MinDmChatInitFeeFromStored(storedCurrency *string, storedNativeAmount *float64) *commonpb.FiatPaymentAmount
MinDmChatInitFeeFromStored resolves a user's stored minimum DM chat initialization fee into the amount their profile carries, or nil for a user who has not set one. Both columns are set together, so a row with only one of them is treated as unset rather than half-read.
func NewChatProfileReader ¶ added in v1.16.0
func NewChatProfileReader(store Store, media Media) chat.ProfileReader
NewChatProfileReader returns a chat.ProfileReader backed by the given profile store and blob storage, for wiring the Chat service. The media dependency is what lets member avatars come back with resolved blob metadata, so chat itself never touches blob storage.
func NormalizeColorHex ¶ added in v1.25.0
NormalizeColorHex puts a colour into the canonical upper-case form it is stored and handed back in, so a colour round-trips as the same string regardless of the casing the client sent it in.
func NormalizeUsername ¶ added in v1.27.0
NormalizeUsername puts a handle into the canonical form it is stored and compared in, so a lookup finds its holder regardless of the casing it was typed in.
func ValidateMinDmChatInitFee ¶ added in v1.28.0
func ValidateMinDmChatInitFee(fee *commonpb.FiatPaymentAmount) error
ValidateMinDmChatInitFee reports whether fee is one a user may set as their minimum DM chat initialization fee. The fee may be in any currency that has send presets, and must be finite and no smaller than that currency's preset minimum, so the floor enforced on a payment that opens a DM is also the floor on what a user may ask of whoever initializes a DM with them. Returns ErrInvalidMinDmChatInitFee otherwise.
func ValidateUsername ¶ added in v1.27.0
ValidateUsername returns ErrInvalidUsername unless username is a handle in canonical form. Every store checks through here before persisting, so nothing but the canonical form is ever held — which is what makes a case-insensitive lookup enough to find a holder.
Types ¶
type ClaimUsernameFunc ¶ added in v1.33.0
ClaimUsernameFunc gives the user being assigned a handle the given one. It returns ErrUsernameTaken when another user holds it, which AssignDefaultUsername answers by trying another number, and false when the user turned out to be no longer eligible for one (a concurrent write gave them a handle first), in which case nothing was claimed.
type DefaultUsernameResult ¶ added in v1.33.0
type DefaultUsernameResult struct {
// Username is the handle assigned, or "" when none was.
Username string
// NoneAvailable is set when the user was eligible for a handle — they held
// none — but no number yielded one both free and usable, or every one tried
// was lost to a concurrent assignment, so they were left without. It is the one
// outcome that leaves an eligible user with no handle. The display name is set
// regardless, and the user stays eligible, so a later display name tries again.
NoneAvailable bool
}
DefaultUsernameResult is what SetDisplayNameWithDefaultUsername did about the user's handle.
type FirstUsernameHandler ¶ added in v1.33.0
type FirstUsernameHandler interface {
OnFirstUsername(ctx context.Context, userID *commonpb.UserId, username string)
}
FirstUsernameHandler is told when a user is given a handle while holding none: claimed with SetUsername, or assigned by default with a display name (see SetDisplayNameWithDefaultUsername). A handle is never released without another taking its place, so this is the first handle the user ever holds.
It is called on the request path, after the handle is written, and cannot fail the request: an implementation returns promptly and does its work elsewhere. It may be called more than once for a user, as when two claims by them race, so what it does is idempotent per user. username is in canonical form, as held.
type HeldUsernamesFunc ¶ added in v1.33.0
HeldUsernamesFunc reports which of the given canonical handles currently have a holder. Handles nobody holds are absent from the returned set.
type Media ¶ added in v1.22.0
type Media interface {
// SetAsProfilePicture validates that ownerID owns the blob and that it can back
// a picture, then grants ownerID's public profile read access to it. It returns
// one of blob.ErrBlobNotFound, blob.ErrBlobNotReady, blob.ErrBlobRejected, or
// blob.ErrBlobInvalid when the blob cannot be used.
SetAsProfilePicture(ctx context.Context, ownerID *commonpb.UserId, blobID *blobpb.BlobId) error
// ResolveRenditions returns each original's full rendition set — the ORIGINAL
// plus every derived rendition, each with a freshly minted, short-lived download
// URL — keyed by string(BlobId.Value). It performs no authorization.
ResolveRenditions(ctx context.Context, ids []*blobpb.BlobId) (map[string][]*blobpb.Rendition, error)
}
Media is the blob storage surface the profile service depends on, satisfied by blob.Integration. It is declared here, rather than depending on the blob server directly, so the dependency runs one way: profile states what it needs of blob storage and the wiring supplies it.
type PhoneForPayment ¶ added in v1.14.0
type PhoneForPayment struct {
PhoneNumber *commonpb.PhoneNumber
UserID *commonpb.UserId
JoinedAt time.Time
}
PhoneForPayment is a payment-enabled phone number together with the user that owns it.
type Server ¶
type Server struct {
profilepb.UnimplementedProfileServer
// contains filtered or unexported fields
}
func (*Server) GetProfile ¶
func (s *Server) GetProfile(ctx context.Context, req *profilepb.GetProfileRequest) (*profilepb.GetProfileResponse, error)
func (*Server) LinkSocialAccount ¶
func (s *Server) LinkSocialAccount(ctx context.Context, req *profilepb.LinkSocialAccountRequest) (*profilepb.LinkSocialAccountResponse, error)
func (*Server) SetDisplayName ¶
func (s *Server) SetDisplayName(ctx context.Context, req *profilepb.SetDisplayNameRequest) (*profilepb.SetDisplayNameResponse, error)
func (*Server) SetMinDmChatInitFee ¶ added in v1.28.0
func (s *Server) SetMinDmChatInitFee(ctx context.Context, req *profilepb.SetMinDmChatInitFeeRequest) (*profilepb.SetMinDmChatInitFeeResponse, error)
func (*Server) SetProfilePicture ¶ added in v1.22.0
func (s *Server) SetProfilePicture(ctx context.Context, req *profilepb.SetProfilePictureRequest) (*profilepb.SetProfilePictureResponse, error)
func (*Server) SetUsername ¶ added in v1.27.0
func (s *Server) SetUsername(ctx context.Context, req *profilepb.SetUsernameRequest) (*profilepb.SetUsernameResponse, error)
func (*Server) UnlinkSocialAccount ¶
func (s *Server) UnlinkSocialAccount(ctx context.Context, req *profilepb.UnlinkSocialAccountRequest) (*profilepb.UnlinkSocialAccountResponse, error)
func (*Server) UpdateFlipcard ¶ added in v1.33.0
func (s *Server) UpdateFlipcard(ctx context.Context, req *profilepb.UpdateFlipcardRequest) (*profilepb.UpdateFlipcardResponse, error)
type ServerOption ¶ added in v1.33.0
type ServerOption func(*Server)
ServerOption configures a Server beyond its dependencies.
func WithFirstUsernameHandler ¶ added in v1.33.0
func WithFirstUsernameHandler(h FirstUsernameHandler) ServerOption
WithFirstUsernameHandler has the Server tell h about every user's first handle (see FirstUsernameHandler). Nil, the default, tells no one.
type Store ¶
type Store interface {
// GetProfile returns the user profile for a user, or ErrNotFound when the store
// does not know the user. The private fields — phone number, email address and
// whether the handle is auto-assigned (see IsUsernameAutoAssigned) — are set
// only when includePrivateProfile is.
GetProfile(ctx context.Context, id *commonpb.UserId, includePrivateProfile bool) (*profilepb.UserProfile, error)
// SetDisplayName sets the display name for a user, provided they exist.
//
// ErrInvalidDisplayName is returned if there is an issue with the display name.
SetDisplayName(ctx context.Context, id *commonpb.UserId, displayName string) error
// SetDisplayNameWithDefaultUsername sets the display name exactly as
// SetDisplayName does and, when the user holds no handle, assigns them a
// default handle for usernameBase: the
// lowest-numbered one nobody holds, or under contention one of the next few or
// a random one (see AssignDefaultUsername). Both are written in one
// transaction, so a handle is never assigned without the display name that
// earned it, and no reader sees that display name before its handle. A handle
// assigned here is recorded as auto-assigned (see IsUsernameAutoAssigned).
//
// The result carries the handle assigned, if any. None is when the user
// already held a handle, or when no number yields a handle
// both free and usable (NoneAvailable), in which case the display name is
// still set. A handle taken concurrently by another user is retried against
// another number, and running out of retries is NoneAvailable too: contention
// never fails the display name.
SetDisplayNameWithDefaultUsername(ctx context.Context, id *commonpb.UserId, displayName, usernameBase string) (DefaultUsernameResult, error)
// SetUsername claims username as the user's handle, replacing any handle they
// already hold — which, having no holder any more, is immediately claimable by
// anyone else. A new handle is recorded as chosen, not auto-assigned (see
// IsUsernameAutoAssigned).
//
// username must already be in canonical form, so callers normalize what a user
// typed before claiming it; ErrInvalidUsername is returned otherwise. A handle
// has one holder at a time: ErrUsernameTaken is returned when another user
// holds it, while re-claiming the handle the user already holds is a no-op
// that leaves it as it was, auto-assigned included.
SetUsername(ctx context.Context, id *commonpb.UserId, username string) error
// IsUsernameAutoAssigned reports whether the user's current handle was
// assigned by SetDisplayNameWithDefaultUsername rather than claimed through
// SetUsername. It is false for a user who holds no handle, and for a handle
// assigned before this was recorded, until those are backfilled. Returns
// ErrNotFound when the store does not know the user.
IsUsernameAutoAssigned(ctx context.Context, id *commonpb.UserId) (bool, error)
// GetUserIdByUsername returns the user currently holding the given handle,
// matched case-insensitively since handles are only ever held in canonical
// form. Returns ErrNotFound when nobody holds it.
GetUserIdByUsername(ctx context.Context, username string) (*commonpb.UserId, error)
// GetPublicProfiles returns, for each of the given users the store knows, the
// public part of that user's profile keyed by string(userID.Value): the
// fields any viewer may see, carrying no private ones. Unknown users are
// absent from the map. It resolves the whole set in a single lookup.
//
// Display name is empty, and username, profile picture and minimum DM chat
// initialization fee nil, for a user who has set none of them, while the join
// timestamp and Flipcard customization are always set — so a present entry
// means "this user exists", not "this user filled in a profile".
//
// The returned picture carries only the blob holding its ORIGINAL rendition;
// resolving that blob's metadata is left to the caller.
GetPublicProfiles(ctx context.Context, userIDs []*commonpb.UserId) (map[string]*profilepb.UserProfile, error)
// SetProfilePicture sets the user's profile picture to the blob holding its
// ORIGINAL rendition, replacing any picture already set.
SetProfilePicture(ctx context.Context, id *commonpb.UserId, blobID *blobpb.BlobId) error
// SetFlipcardColor sets the colour of the user's Flipcard, provided they
// exist, replacing any colour already picked. colorHex is stored as given, so
// callers normalize it first.
SetFlipcardColor(ctx context.Context, id *commonpb.UserId, colorHex string) error
// SetMinDmChatInitFee sets the minimum fee another user must pay to
// initialize a DM chat with the user, provided they exist, replacing any fee
// already set. fee is stored as given, so callers validate it first.
SetMinDmChatInitFee(ctx context.Context, id *commonpb.UserId, fee *commonpb.FiatPaymentAmount) error
// LinkPhoneNumber links the phone number and its precomputed hash to a user, provided
// they exist. Any other user previously holding the same phone number has both fields
// cleared.
LinkPhoneNumber(ctx context.Context, id *commonpb.UserId, phoneNumber string, phoneNumberHash *commonpb.Hash) error
// UnlinkPhoneNumber removes the link for the phone number
UnlinkPhoneNumber(ctx context.Context, userID *commonpb.UserId, phoneNumber string) error
// LinkPhoneNumberForPayment marks the user's linked phone number as enabled for
// payment, provided the given phone number is currently linked to the user. It
// reports whether the flag transitioned from false to true (false if it was
// already enabled).
//
// ErrNotFound is returned when the phone number is not associated with the user.
LinkPhoneNumberForPayment(ctx context.Context, userID *commonpb.UserId, phoneNumber string) (bool, error)
// IsPhoneNumberLinkedForPayment reports whether the given phone number is
// currently linked to the user and enabled for payment.
IsPhoneNumberLinkedForPayment(ctx context.Context, userID *commonpb.UserId, phoneNumber string) (bool, error)
// GetPhonesByHashes returns the phone numbers for users whose stored
// phoneNumberHash matches any of the provided hashes. Order is unspecified.
GetPhonesByHashes(ctx context.Context, hashes []*commonpb.Hash) ([]*commonpb.PhoneNumber, error)
// GetPhonesByHashesForPayment returns the payment-enabled phone numbers,
// each paired with the user that owns it, for users whose stored
// phoneNumberHash matches any of the provided hashes and who have enabled
// their phone number for payment. Order is unspecified.
GetPhonesByHashesForPayment(ctx context.Context, hashes []*commonpb.Hash) ([]*PhoneForPayment, error)
// GetPhoneNumbersForPayment returns, for each of the given users that has a
// phone number enabled for payment, that phone number keyed by
// string(userID.Value). Users without a payment-enabled phone number are
// absent from the map. It resolves the whole set in a single lookup.
GetPhoneNumbersForPayment(ctx context.Context, userIDs []*commonpb.UserId) (map[string]*commonpb.PhoneNumber, error)
// GetUserIdByPhoneNumber returns the UserId currently linked to the given
// phone number. Returns ErrNotFound when no user holds the number.
GetUserIdByPhoneNumber(ctx context.Context, phoneNumber string) (*commonpb.UserId, error)
// GetUserIdByPhoneNumberForPayment returns the UserId currently linked to the
// given phone number, but only when that user has enabled the phone number for
// payment. Returns ErrNotFound when no user holds the number or the number is
// not enabled for payment.
GetUserIdByPhoneNumberForPayment(ctx context.Context, phoneNumber string) (*commonpb.UserId, error)
// LinkEmailAddress links the email address to a user, provided they exist. Any other
// user previously holding the same email address has it cleared.
LinkEmailAddress(ctx context.Context, id *commonpb.UserId, emailAddress string) error
// UnlinkPhoneNumber removes the link for the email address
UnlinkEmailAddress(ctx context.Context, userID *commonpb.UserId, emailAddress string) error
// LinkXAccount links a X account to a user ID
LinkXAccount(ctx context.Context, userID *commonpb.UserId, xProfile *profilepb.XProfile, accessToken string) error
// UnlinkXAccount removes the link to the X account
UnlinkXAccount(ctx context.Context, userID *commonpb.UserId, xUserID string) error
// GetXProfile gets a user's X profile if it has been linked
GetXProfile(ctx context.Context, userID *commonpb.UserId) (*profilepb.XProfile, error)
}