Skip to content

Commit

Permalink
Adds subscription types and db setters/getters
Browse files Browse the repository at this point in the history
  • Loading branch information
emmdim committed Sep 11, 2024
1 parent 9b1bb20 commit f5d187d
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 16 deletions.
1 change: 1 addition & 0 deletions db/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type MongoStorage struct {

users *mongo.Collection
organizations *mongo.Collection
subscriptions *mongo.Collection
}

type Options struct {
Expand Down
4 changes: 4 additions & 0 deletions db/mongo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ type OrganizationCollection struct {
Organizations []Organization `json:"organizations" bson:"organizations"`
}

type SubscriptionCollection struct {
Subscriptions []Subscription `json:"subscriptions" bson:"subscriptions"`
}

type Collection struct {
UserCollection
OrganizationCollection
Expand Down
30 changes: 30 additions & 0 deletions db/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,33 @@ func (ms *MongoStorage) OrganizationsMembers(address string) ([]User, error) {
}
return users, nil
}

// addSubscriptionToOrganization internal method adds the subscription to the organiation with
// the given email. If an error occurs, it returns the error. This method must
// be called with the keysLock held.
func (ms *MongoStorage) addSubscriptionToOrganization(address string, subscriptionID int, startDate, endDate time.Time, active bool) error {
ms.keysLock.Lock()
defer ms.keysLock.Unlock()
// create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// prepare the document to be updated in the database
filter := bson.M{
"organizations": bson.M{
"$elemMatch": bson.M{
"_id": address,
},
},
}
updateDoc := bson.M{"$addToSet": bson.M{"subscription": OrganizationSubscription{
SubscriptionID: subscriptionID,
StartDate: startDate,
EndDate: endDate,
Active: active,
}}}
// update the organization in the database
if _, err := ms.organizations.UpdateOne(ctx, filter, updateDoc); err != nil {
return errors.New("failed to add subscription to organization")
}
return nil
}
68 changes: 68 additions & 0 deletions db/subscriptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package db

import (
"context"
"errors"
"log"
"time"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

func (ms *MongoStorage) SetSubscription(subscription *Subscription) error {
ms.keysLock.Lock()
defer ms.keysLock.Unlock()
// create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// prepare the document to be updated in the database modifying only the
// fields that have changed
// define 'active' parameter to be updated always to update it even its new
// value is false
updateDoc, err := dynamicUpdateDocument(subscription, []string{"active"})
if err != nil {
return err
}
// set upsert to true to create the document if it doesn't exist
opts := options.Update().SetUpsert(true)
if _, err := ms.organizations.UpdateOne(ctx, bson.M{"_id": subscription.ID}, updateDoc, opts); err != nil {
return err
}
if err != nil {
log.Printf("Failed to add subscription: %v", err)
return errors.New("failed to add subscription")
}
return nil
}

func (ms *MongoStorage) Subscription(subscriptionID string) (*Subscription, error) {
ms.keysLock.RLock()
defer ms.keysLock.RUnlock()
// create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// find the subscription in the database
filter := bson.M{"_id": subscriptionID}
subscription := &Subscription{}
err := ms.subscriptions.FindOne(ctx, filter).Decode(subscription)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, ErrNotFound // Subscription not found
}
return nil, errors.New("failed to get subscription")
}
return subscription, nil
}

func (ms *MongoStorage) DelSubscription(subscription *Subscription) error {
ms.keysLock.Lock()
defer ms.keysLock.Unlock()
// create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// delete the organization from the database
_, err := ms.organizations.DeleteOne(ctx, bson.M{"_id": subscription.ID})
return err
}
67 changes: 51 additions & 16 deletions db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,55 @@ type OrganizationMember struct {
}

type Organization struct {
Address string `json:"address" bson:"_id"`
Name string `json:"name" bson:"name"`
Type OrganizationType `json:"type" bson:"type"`
Creator string `json:"creator" bson:"creator"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
Nonce string `json:"nonce" bson:"nonce"`
Description string `json:"description" bson:"description"`
Size uint64 `json:"size" bson:"size"`
Color string `json:"color" bson:"color"`
Logo string `json:"logo" bson:"logo"`
Subdomain string `json:"subdomain" bson:"subdomain"`
Timezone string `json:"timezone" bson:"timezone"`
Active bool `json:"active" bson:"active"`
TokensPurchased uint64 `json:"tokensPurchased" bson:"tokensPurchased"`
TokensRemaining uint64 `json:"tokensRemaining" bson:"tokensRemaining"`
Parent string `json:"parent" bson:"parent"`
Address string `json:"address" bson:"_id"`
Name string `json:"name" bson:"name"`
Type OrganizationType `json:"type" bson:"type"`
Creator string `json:"creator" bson:"creator"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
Nonce string `json:"nonce" bson:"nonce"`
Description string `json:"description" bson:"description"`
Size uint64 `json:"size" bson:"size"`
Color string `json:"color" bson:"color"`
Logo string `json:"logo" bson:"logo"`
Subdomain string `json:"subdomain" bson:"subdomain"`
Timezone string `json:"timezone" bson:"timezone"`
Active bool `json:"active" bson:"active"`
TokensPurchased uint64 `json:"tokensPurchased" bson:"tokensPurchased"`
TokensRemaining uint64 `json:"tokensRemaining" bson:"tokensRemaining"`
Parent string `json:"parent" bson:"parent"`
Subscription OrganizationSubscription `json:"subscription" bson:"subscription"`
}

type OrganizationLimits struct {
Memberships int `bson:"memberships"`
SubOrgs int `bson:"subOrgs"`
MaxCensusSize int `bson:"maxCensusSize"`
}

type VotingTypes struct {
Approval bool `bson:"approval"`
Ranked bool `bson:"ranked"`
Weighted bool `bson:"weighted"`
}

type Features struct {
Personalization bool `bson:"personalization"`
EmailReminder bool `bson:"emailReminder"`
SmsNotification bool `bson:"smsNotification"`
}

type Subscription struct {
ID int `bson:"_id"`
Name string `bson:"name"`
StripeID string `bson:"stripeID"`
Organization OrganizationLimits `bson:"organization"`
VotingTypes VotingTypes `bson:"votingTypes"`
Features Features `bson:"features"`
}

type OrganizationSubscription struct {
SubscriptionID int `bson:"subscriptionID"`
StartDate time.Time `bson:"startDate"`
EndDate time.Time `bson:"endDate"`
Active bool `bson:"active"`
}
8 changes: 8 additions & 0 deletions db/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,11 @@ func (ms *MongoStorage) IsMemberOf(userEmail, organizationAddress string, role U
}
return false, ErrNotFound
}

func (ms *MongoStorage) MemberOrganizations(userEmail string) ([]OrganizationMember, error) {
user, err := ms.UserByEmail(userEmail)
if err != nil {
return nil, err
}
return user.Organizations, nil
}
113 changes: 113 additions & 0 deletions db/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,116 @@ var usersCollectionValidator = bson.M{
},
},
}

var organizationsCollectionValidator = bson.M{
"$jsonSchema": bson.M{
"bsonType": "object",
"required": []string{"_id", "name", "subscriptionID", "hasPaid"},
"properties": bson.M{
"id": bson.M{
"bsonType": "int",
"description": "must be an integer and is required",
"minimum": 1,
},
"name": bson.M{
"bsonType": "string",
"description": "must be a string and is required",
},
"subscriptionID": bson.M{
"bsonType": "int",
"description": "must be a integer and is required",
"minimum": 1,
},
"hasPaid": bson.M{
"bsonType": "bool",
"description": "must be a boolean and is required",
},
},
},
}

// example of a boolean check (features + voting types)
// `if organization.hasPaid && subscription[organization.subrscriptionID].features.emailReminder“
// example of number check?
// for the number checks maybe the organization should keep a local cache of the results?
var subscriptionCollectionValidator = bson.M{
"$jsonSchema": bson.M{
"bsonType": "object",
"required": []string{"_id", "name", "stripeID", "organization", "votingTypes", "features"},
"properties": bson.M{
"id": bson.M{
"bsonType": "int",
"description": "must be an integer and is required",
"minimum": 1,
},
"name": bson.M{
"bsonType": "string",
"description": "the name of the subscription plan must be a string and is required",
},
"stripeID": bson.M{
"bsonType": "string",
"description": "the corresponding plan ID must be a string and is required",
},
"organization": bson.M{
"bsonType": "object",
"description": "the organization limits must be an object and is required",
"required": []string{"memberships", "subOrgs", "maxCensusSize"},
"properties": bson.M{
"memberships": bson.M{
"bsonType": "int",
"description": "the max number of memberships allowed must be an integer and is required",
"minimum": 1,
},
"subOrgs": bson.M{
"bsonType": "int",
"description": "the max number of sub organizations allowed must be an integer and is required",
"minimum": 1,
},
"maxCensusSize": bson.M{
"bsonType": "int",
"description": "the max number of participants allowed in the each election must be an integer and is required",
"minimum": 1,
},
},
},
"votingTypes": bson.M{
"bsonType": "object",
"description": "the voting types allowed must be an object and is required",
"required": []string{"approval", "ranked", "weighted"},
"properties": bson.M{
"approval": bson.M{
"bsonType": "bool",
"description": "approval voting must be a boolean and is required",
},
"ranked": bson.M{
"bsonType": "bool",
"description": "ranked voting must be a boolean and is required",
},
"weighted": bson.M{
"bsonType": "bool",
"description": "weighted voting must be a boolean and is required",
},
},
},
"features": bson.M{
"bsonType": "object",
"description": "the features enabled must be an object and is required",
"required": []string{"personalization", "emailReminder", "smsNotification"},
"properties": bson.M{
"personalization": bson.M{
"bsonType": "bool",
"description": "personalization must be a boolean and is required",
},
"emailReminder": bson.M{
"bsonType": "bool",
"description": "emailReminder must be a boolean and is required",
},
"smsNotification": bson.M{
"bsonType": "bool",
"description": "smsNotification must be a boolean and is required",
},
},
},
},
},
}

0 comments on commit f5d187d

Please sign in to comment.