* archive old referral offer doc * init referral manager * add Referral Manager CLI definition * add draft implementation steps * use existing registration_token table for storing invitation tokens * fix numbering * add open question about auth * wip pseudocode * add user interface design * separate token generation step from getting users step * change button to be a referral tab on the UI * simplify some sentences. clarified CLI referral manager interaction * add prevent two processes running at the same time * add cli code * clear users when CLI exites earlier or referral manager finishes sending tokens to satellites * use transaction for saving tokens on satellite so we can roll back if error occurs * add auth token and GetSatelliteURLs * add schema for satellites table * update to only have satelite to talk to referral manager * add definition for eligible users and owner's satellite * fix indentation * add saving user info during redeem pseudocode * wip * address reviews * add unredeemed token in users table * address comments
8.9 KiB
Referral Manager V1: Cross-Satellite Referral Link
Abstract
This document describes how to handle user referrals across satellites.
Background
The previous referral program only works per satellite. This means a user on us-east-1 satellite can only refer people to join us-east-1.
We want referrals to be valid on all Tardigrade satellites. This new design will allow the user to select any satellite, regardless of the satellite the referral was generated for.
Non-Goals
A registration page where a user can select their preferred Satellite during registration.
Design
Definitions
- Referral Manager
- A standalone process running separately from satellites. Only Tardigrade-branded satellites should be able to talk to it.
- Referral Manager CLI
- A CLI that will allow operators to invoke referral link generation.
- Invitation Token
- A random number generated by Referral Manager for each user in the referral program.
- Referral Link
- A one-time-use URL that contains a unique invitation token.
- Eligible Users
- Users who have fewer than the desired number of unredeemed referral links.
- Owner's Satellite
- The satellite that the owner of a particular token belongs to.
- Redeemed Satellite
- The satellite where the new user redeeming a token has registered on.
How it will work
User Interface
- Users will have a referral tab on the UI. When clicked, it will trigger the satellite to send a request to retrieve referral links from the Referral Manager:
- The Referral Manager will attempt to generate and/or fetch unredeemed tokens in the
tokens
table for this user ID and satellite.- If the user ID doesn't exist in the Referral Manager's
users
table, the Referral Manager will add an entry for this user ID and satellite in theusers
table and return an empty response to the satellite. - If the
users
table contains a value larger than0
fornew_tokens
for this user ID, the Referral Manager will generate that number of new tokens, add the new unredeemed tokens to thetokens
table, and set the value ofnew_tokens
for that user to0
in theusers
table. - If there are unredeemed tokens in the
tokens
table, the Referral Manager will respond to the satellite's request with the tokens after generating new tokens. Otherwise, it will return an empty response.
- If the user ID doesn't exist in the Referral Manager's
- The satellite receives the response from Referral Manager.
- If the response payload contains tokens, the satellite will display them in the UI.
- If it's an empty response payload, the satellite will display a message
No available referral links. Try again later.
- The Referral Manager will attempt to generate and/or fetch unredeemed tokens in the
Referral Manager CLI
referral-manager start --tokens-per-user=3 --max-unredeemed-tokens-per-user=1 <satelliteURL1> <satelliteURL2> ...
initiates the invitation token generation process. The--dry-run
flag can optionally be added to run the process without actually generating tokens. The dry run allows for estimating how many new tokens would be created by the command.- The Referral Manager queries both
users
andtokens
tables for all users wherenew_tokens + unredeemed_tokens < flags.tokens_per_user
and where the satellite matches one of the CLI-provided satellite URLs. These are the "eligible users". - The Referral Manager will count the number of eligible users and calculate the number of tokens needed to bring each user up to
tokens_per_user
. If--dry-run
is set, these values will be returned to the CLI for output and the process will end. Otherwise, theusers
table will be updated to setnew_tokens
totokens_per_user - unredeemed_tokens
for each eligible user, and the values will be returned to the CLI for output.
Referral Link Redemption
- User Alice tries to register a new account through a referral link, which triggers the redeemed satellite to verify invitation token using Referral manager. The satellite preemptively generates a new user ID and sends this ID along with the token to the Referral Manager.
- Referral Manager checks the status of the token:
- If the token exists in
tokens
table:- If it is not redeemed, Referral Manager sends back a success response to the redeemed satellite and marks the token as redeemed by the new user ID in the Referral Manager's
tokens
table. It also adds an entry for the new user ID in theusers
table. - If the token is already redeemed, the Referral Manager sends back an
invalid token
response to the redeemed satellite.
- If it is not redeemed, Referral Manager sends back a success response to the redeemed satellite and marks the token as redeemed by the new user ID in the Referral Manager's
- If the token doesn't exist in
tokens
table, Referral Manager sends back aninvalid token
response to the redeemed satellite.
- If the token exists in
- Redeemed satellite receives the response, which:
- If it is a success, the satellite will proceed with the account creation
- If it is an invalid token, the satellite will display a proper message in the UI.
Rationale
We will create an admin port that's only listening on localhost
so CLI could be only used by Referral Manager operators.
We could also set the admin port to only accept requests coming from storj vpn.
Implementation
- Create a private repository for Referral Manager.
- Create
tokens
andusers
table in Referral Manager database. - Create an admin endpoint on Referral Manager.
- Implementing a method
GenerateTokens
for updating theusers
table on Referral Manager to contain new tokens for eligible users.
- Implementing a method
- Implementing an endpoint
GetTokens
for requesting unredeemed tokens from Referral Manager. - Implementing an endpoint
Redeem
on Referral Manager for verifying invitation tokens and storing newly created user ID intousers
table. - Replace existing registration token logic.
Pseudocode
The Token struct represents both a Go struct (on satellite and Referral Manager) as well as the schema for the tokens
table on the Referral Manager
type Token struct {
Secret [32]byte
OwnerID uuid.UUID
RedeemedID uuid.UUID
OwnerSatelliteURL string
RedeemedSatelliteURL string
// we should store it as an integer in the db and cast it into enum when retrieve it
Status enum
}
Statuses: unsent
(owner's satellite doesn't know about it yet), unredeemed
(owner's satellite knows about it but it has not been used), redeemed
(someone has used this referral link to register alreadys)
The User struct represent both a Go struct as well as the schema for the users
table on Referral Manager.
type User struct {
ID uuid.UUID
satelliteURL string
NewTokens int
UnredeemedTokens int
}
Endpoints for satellites:
// GetTokens retrieves a list of unredeemed tokens for a user.
GetTokens(userID uuid.UUID, satelliteURL string) []tokens {
tx := db.Tx
user, err := tx.GetUser(userID, satelliteURL)
if err == UserNotFound {
tx.CreateUser(userID, satelliteURL)
return nil
}
if user.NewTokens > 0 {
for i:=0; i<user.NewTokens; i++ {
newToken := Token{data: generateRandomToken(), user: user.ID, satellite: user.satelliteURL}
db.CreateToken(newToken)
}
tx.UpdateUserNewTokens(userID, satelliteURL, 0)
}
tokens := db.GetTokensByUserIDAndSatelliteURL(userID, satelliteURL)
return tokens
}
// Redeem marks a token as redeemed and stores user info into database
func Redeem(ctx, token, userID, satelliteURL) error {
tx := db.Tx
// only update the status if the status of a token is unredeemed
tokenStatus, err := tx.RedeemToken(ctx, token, userID)
if err != nil {
return err
}
// decrease unredeemed token count in users table by 1
err := tx.UpdateUserInfo(ctx, user)
if err != nil {
return err
}
// save user info into users table
err := tx.CreateUser(ctx, userID, satelliteURL)
if err != nil {
// log the error, but we shouldn't return an error to stop user registration process if we don't get their info here
log(err)
return
}
}
Endpoints for CLI:
// GenerateTokens generates tokens, saves those in the referral manager db
GenerateTokens(tokensPerUser int, satelliteURLs []string, dryRun bool) (tokenCount, eligibleUserCount int, error) {
eligibleUsers := db.GetEligibleUsers(satelliteURLs, tokensPerUser)
if eligibleUsers == nil {
return nil, Error.New("No users to generate tokens for.")
}
newTokenCount := 0
eligibleUserCount := len(eligibleUsers)
for _, user := range eligibleUsers {
for i:=(user.NewTokens + user.UnredeemedTokens); i<tokensPerUser; i++ {
newTokenCount++
}
}
if !dryRun {
db.UpdateUserNewTokensBatch(eligibleUsers, tokensPerUser)
}
return newTokenCount, eligibleUserCount, nil
}
CLI code:
startCmd() {
tokenCount, userCount := referralManager.GenerateTokens(tokensPerUser, args.SatelliteURLs, flags.dryRun)
fmt.Printf("Successfully created %d tokens for %d users.\n", tokenCount, userCount)
if flags.dryRun {
fmt.Println("This was a dry run. Run again without the --dry-run flag to actually generate tokens.")
}
}