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 12, 2024
1 parent c5550ec commit 5a0c22e
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 17 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
33 changes: 33 additions & 0 deletions db/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,36 @@ 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()
if _, err := ms.Subscription(subscriptionID); err != nil {
return ErrInvalidData
}
// 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
}
40 changes: 40 additions & 0 deletions db/organizations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"testing"
"time"

qt "github.com/frankban/quicktest"
)
Expand Down Expand Up @@ -189,3 +190,42 @@ func TestOrganizationsMembers(t *testing.T) {
singleMember := members[0]
c.Assert(singleMember.Email, qt.Equals, testUserEmail)
}
func TestAddOrganizationSubscription(t *testing.T) {
defer func() {
if err := db.Reset(); err != nil {
t.Error(err)
}
}()
c := qt.New(t)
// create a new organization
address := "orgToAddSubscription"
orgName := "Organization"
c.Assert(db.SetOrganization(&Organization{
Address: address,
Name: orgName,
}), qt.IsNil)
// add a subscription to the organization
subscriptionID := 123
subscriptionName := "testSubscription"
startDate := time.Now()
endDate := startDate.AddDate(1, 0, 0)
active := true
// using a non existing subscription should fail
c.Assert(db.AddSubscriptionToOrganization(address, subscriptionID, startDate, endDate, active), qt.IsNotNil)
c.Assert(db.SetSubscription(&Subscription{
ID: subscriptionID,
Name: subscriptionName,
}), qt.IsNil)

c.Assert(db.AddSubscriptionToOrganization(address, subscriptionID, startDate, endDate, active), qt.IsNil)
// retrieve the organization and check the subscription details
org, _, err := db.Organization(address, false)
c.Assert(err, qt.IsNil)
c.Assert(org, qt.Not(qt.IsNil))
c.Assert(org.Address, qt.Equals, address)
c.Assert(org.Name, qt.Equals, orgName)
c.Assert(org.Subscription.SubscriptionID, qt.Equals, subscriptionID)
c.Assert(org.Subscription.StartDate, qt.Equals, startDate)
c.Assert(org.Subscription.EndDate, qt.Equals, endDate)
c.Assert(org.Subscription.Active, qt.Equals, active)
}
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 int) (*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
}
61 changes: 61 additions & 0 deletions db/subscriptions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// FILEPATH: /home/user/Projects/vocdoni/saas-backend/db/subscriptions_test.go

package db

import (
"testing"

qt "github.com/frankban/quicktest"
)

func TestSetSubscription(t *testing.T) {
defer func() {
if err := db.Reset(); err != nil {
t.Error(err)
}
}()
c := qt.New(t) // Create a new quicktest instance

ms := &MongoStorage{} // Create a mock MongoStorage instance

subscription := &Subscription{
ID: 123,
Name: "Test Subscription",
Organization: OrganizationLimits{},
VotingTypes: VotingTypes{},
Features: Features{},
}

err := ms.SetSubscription(subscription)
c.Assert(err, qt.IsNil)
}

func TestSubscription(t *testing.T) {
c := qt.New(t) // Create a new quicktest instance

ms := &MongoStorage{} // Create a mock MongoStorage instance

subscriptionID := 123

subscription, err := ms.Subscription(subscriptionID)
c.Assert(err, qt.IsNil)
c.Assert(subscription, qt.Not(qt.IsNil))
c.Assert(subscription.ID, qt.Equals, subscriptionID)
}

func TestDelSubscription(t *testing.T) {
c := qt.New(t) // Create a new quicktest instance

ms := &MongoStorage{} // Create a mock MongoStorage instance

subscription := &Subscription{
ID: 123,
Name: "Test Subscription",
Organization: OrganizationLimits{},
VotingTypes: VotingTypes{},
Features: Features{},
}

err := ms.DelSubscription(subscription)
c.Assert(err, qt.IsNil)
}
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
}
Loading

0 comments on commit 5a0c22e

Please sign in to comment.