• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / saas-backend / 17914181837

22 Sep 2025 11:31AM UTC coverage: 57.332%. First build
17914181837

Pull #247

github

altergui
db: implement migrations
Pull Request #247: db: implement migrations

220 of 349 new or added lines in 6 files covered. (63.04%)

5794 of 10106 relevant lines covered (57.33%)

33.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cmd/service/main.go
1
// Package main is the entry point for the Vocdoni SaaS backend service.
2
// It initializes and configures all components including database connections,
3
// API endpoints, authentication services, and notification systems.
4
package main
5

6
import (
7
        "context"
8
        "os"
9
        "os/signal"
10
        "syscall"
11

12
        flag "github.com/spf13/pflag"
13
        "github.com/spf13/viper"
14
        "github.com/vocdoni/saas-backend/account"
15
        "github.com/vocdoni/saas-backend/api"
16
        "github.com/vocdoni/saas-backend/csp"
17
        "github.com/vocdoni/saas-backend/db"
18
        "github.com/vocdoni/saas-backend/internal"
19
        "github.com/vocdoni/saas-backend/notifications/mailtemplates"
20
        "github.com/vocdoni/saas-backend/notifications/smtp"
21
        "github.com/vocdoni/saas-backend/notifications/twilio"
22
        "github.com/vocdoni/saas-backend/objectstorage"
23
        "github.com/vocdoni/saas-backend/subscriptions"
24
        "go.vocdoni.io/dvote/apiclient"
25
        "go.vocdoni.io/dvote/log"
26

27
        // Invoke init() functions within migrations pkg.
28
        _ "github.com/vocdoni/saas-backend/migrations"
29
)
30

31
func main() {
×
32
        // define flags
×
33
        flag.String("serverURL", "http://localhost:8080", "The full URL of the server (http or https)")
×
34
        flag.StringP("host", "h", "0.0.0.0", "listen address")
×
35
        flag.IntP("port", "p", 8080, "listen port")
×
36
        flag.StringP("secret", "s", "", "API secret")
×
37
        flag.StringP("vocdoniApi", "v", "https://api-dev.vocdoni.net/v2", "vocdoni node remote API URL")
×
38
        flag.StringP("webURL", "w", "https://saas-dev.vocdoni.app", "The URL of the web application")
×
39
        flag.StringP("mongoURL", "m", "", "The URL of the MongoDB server")
×
40
        flag.StringP("mongoDB", "d", "", "The name of the MongoDB database")
×
41
        flag.StringP("privateKey", "k", "", "private key for the Vocdoni account")
×
42
        flag.BoolP("fullTransparentMode", "a", false, "allow all transactions and do not modify any of them")
×
43
        flag.String("smtpServer", "", "SMTP server")
×
44
        flag.Int("smtpPort", 587, "SMTP port")
×
45
        flag.String("smtpUsername", "", "SMTP username")
×
46
        flag.String("smtpPassword", "", "SMTP password")
×
47
        flag.String("emailFromAddress", "", "Email service from address")
×
48
        flag.String("emailFromName", "Vocdoni", "Email service from name")
×
49
        flag.String("twilioAccountSid", "", "Twilio account SID")
×
50
        flag.String("twilioAuthToken", "", "Twilio auth token")
×
51
        flag.String("twilioFromNumber", "", "Twilio from number")
×
52
        flag.String("stripeApiSecret", "", "Stripe API secret")
×
53
        flag.String("stripeWebhookSecret", "", "Stripe Webhook secret")
×
54
        flag.String("oauthServiceURL", "http://oauth.vocdoni.net", "OAuth service URL")
×
NEW
55
        flag.Bool("skip-migrations", false, "Skip database migrations")
×
NEW
56
        flag.Bool("migrate-only", false, "Run database migrations and exit")
×
57
        // parse flags
×
58
        flag.Parse()
×
59
        // initialize Viper
×
60
        viper.SetEnvPrefix("VOCDONI")
×
61
        if err := viper.BindPFlags(flag.CommandLine); err != nil {
×
62
                panic(err)
×
63
        }
64
        viper.AutomaticEnv()
×
65
        // read the configuration
×
66
        server := viper.GetString("serverURL")
×
67
        host := viper.GetString("host")
×
68
        port := viper.GetInt("port")
×
69
        apiEndpoint := viper.GetString("vocdoniApi")
×
70
        webURL := viper.GetString("webURL")
×
71
        secret := viper.GetString("secret")
×
72
        if secret == "" {
×
73
                log.Fatal("secret is required")
×
74
        }
×
75
        // MongoDB vars
76
        mongoURL := viper.GetString("mongoURL")
×
77
        mongoDB := viper.GetString("mongoDB")
×
78
        // email vars
×
79
        smtpServer := viper.GetString("smtpServer")
×
80
        smtpPort := viper.GetInt("smtpPort")
×
81
        smtpUsername := viper.GetString("smtpUsername")
×
82
        smtpPassword := viper.GetString("smtpPassword")
×
83
        emailFromAddress := viper.GetString("emailFromAddress")
×
84
        emailFromName := viper.GetString("emailFromName")
×
85
        // sms vars
×
86
        twilioAccountSid := viper.GetString("twilioAccountSid")
×
87
        twilioAuthToken := viper.GetString("twilioAuthToken")
×
88
        twilioFromNumber := viper.GetString("twilioFromNumber")
×
89
        // stripe vars
×
90
        stripeAPISecret := viper.GetString("stripeApiSecret")
×
91
        stripeWebhookSecret := viper.GetString("stripeWebhookSecret")
×
92
        // oauth vars
×
93
        oauthServiceURL := viper.GetString("oauthServiceURL")
×
94

×
95
        log.Init("debug", "stdout", os.Stderr)
×
96

×
97
        // Validate Stripe configuration
×
98
        if stripeAPISecret == "" || stripeWebhookSecret == "" {
×
99
                log.Fatalf("stripeApiSecret and stripeWebhookSecret are required")
×
100
        }
×
101

102
        // initialize the MongoDB database (fresh plans will be loaded by Stripe service)
103
        database, err := db.New(mongoURL, mongoDB, db.PlansStub)
×
104
        if err != nil {
×
105
                log.Fatalf("could not create the MongoDB database: %v", err)
×
106
        }
×
107
        defer database.Close()
×
NEW
108

×
NEW
109
        // Run database migrations by default (unless explicitly skipped)
×
NEW
110
        skipMigrations := viper.GetBool("skip-migrations")
×
NEW
111
        migrateOnly := viper.GetBool("migrate-only")
×
NEW
112

×
NEW
113
        if !skipMigrations {
×
NEW
114
                log.Infow("running database migrations")
×
NEW
115
                if err := database.RunMigrationsUp(); err != nil {
×
NEW
116
                        log.Fatalf("migration failed: %v", err)
×
NEW
117
                }
×
NEW
118
                log.Infow("migrations completed successfully")
×
NEW
119
        } else {
×
NEW
120
                log.Warnw("skipping database migrations - this may cause issues if schema is outdated")
×
NEW
121
        }
×
122

123
        // If migrate-only flag is set, exit after running migrations
NEW
124
        if migrateOnly {
×
NEW
125
                log.Infow("migrate-only mode, exiting after migrations")
×
NEW
126
                return
×
NEW
127
        }
×
128
        // create the remote API client
129
        apiClient, err := apiclient.New(apiEndpoint)
×
130
        if err != nil {
×
131
                log.Fatalf("could not create the remote API client: %v", err)
×
132
        }
×
133
        privKey := viper.GetString("privateKey")
×
134
        fullTransparentMode := viper.GetBool("fullTransparentMode")
×
135
        // check the required parameters
×
136
        if secret == "" || privKey == "" {
×
137
                log.Fatal("secret and privateKey are required")
×
138
        }
×
139
        bPrivKey := internal.HexBytes{}
×
140
        if err := bPrivKey.ParseString(privKey); err != nil {
×
141
                log.Fatalf("could not parse the private key: %v", err)
×
142
        }
×
143
        // create the Vocdoni client account with the private key
144
        acc, err := account.New(privKey, apiEndpoint)
×
145
        if err != nil {
×
146
                log.Fatal(err)
×
147
        }
×
148
        log.Infow("API client created", "endpoint", apiEndpoint, "chainID", apiClient.ChainID())
×
149
        // init the API configuration
×
150
        apiConf := &api.Config{
×
151
                Host:                host,
×
152
                Port:                port,
×
153
                Secret:              secret,
×
154
                DB:                  database,
×
155
                Client:              apiClient,
×
156
                Account:             acc,
×
157
                WebAppURL:           webURL,
×
158
                ServerURL:           server,
×
159
                FullTransparentMode: fullTransparentMode,
×
160
                OAuthServiceURL:     oauthServiceURL,
×
161
        }
×
162

×
163
        cspConf := &csp.Config{
×
164
                RootKey: bPrivKey,
×
165
                DB:      database,
×
166
        }
×
167
        // overwrite the email notifications service with the SMTP service if the
×
168
        // required parameters are set and include it in the API configuration
×
169
        if smtpServer != "" && smtpUsername != "" && smtpPassword != "" {
×
170
                if emailFromAddress == "" || emailFromName == "" {
×
171
                        log.Fatal("emailFromAddress and emailFromName are required")
×
172
                }
×
173
                apiConf.MailService = new(smtp.Email)
×
174
                if err := apiConf.MailService.New(&smtp.Config{
×
175
                        FromName:     emailFromName,
×
176
                        FromAddress:  emailFromAddress,
×
177
                        SMTPServer:   smtpServer,
×
178
                        SMTPPort:     smtpPort,
×
179
                        SMTPUsername: smtpUsername,
×
180
                        SMTPPassword: smtpPassword,
×
181
                }); err != nil {
×
182
                        log.Fatalf("could not create the email service: %v", err)
×
183
                }
×
184
                cspConf.MailService = apiConf.MailService
×
185
                // load email templates
×
186
                if err := mailtemplates.Load(); err != nil {
×
187
                        log.Fatalf("could not load email templates: %v", err)
×
188
                }
×
189
                log.Infow("email templates loaded",
×
190
                        "templates", len(mailtemplates.Available()))
×
191
        }
192
        // create SMS notifications service if the required parameters are set and
193
        // include it in the API configuration
194
        if twilioAccountSid != "" && twilioAuthToken != "" && twilioFromNumber != "" {
×
195
                apiConf.SMSService = new(twilio.SMS)
×
196
                if err := apiConf.SMSService.New(&twilio.Config{
×
197
                        AccountSid: twilioAccountSid,
×
198
                        AuthToken:  twilioAuthToken,
×
199
                        FromNumber: twilioFromNumber,
×
200
                }); err != nil {
×
201
                        log.Fatalf("could not create the SMS service: %v", err)
×
202
                }
×
203
                cspConf.SMSService = apiConf.SMSService
×
204
                log.Infow("SMS service created", "from", twilioFromNumber)
×
205
        }
206

207
        // create the CSP service and include it in the API configuration
208
        ctx, cancel := context.WithCancel(context.Background())
×
209
        defer cancel()
×
210
        if apiConf.CSP, err = csp.New(ctx, cspConf); err != nil {
×
211
                log.Fatalf("could not create the CSP service: %v", err)
×
212
                return
×
213
        }
×
214
        apiConf.Subscriptions = subscriptions.New(&subscriptions.Config{
×
215
                DB: database,
×
216
        })
×
217
        // initialize the s3 like  object storage
×
218
        apiConf.ObjectStorage, err = objectstorage.New(&objectstorage.Config{
×
219
                DB: database,
×
220
        })
×
221
        if err != nil {
×
222
                log.Fatal(err)
×
223
        }
×
224
        // create the local API server
225
        api.New(apiConf).Start()
×
226
        log.Infow("server started", "host", host, "port", port)
×
227
        // wait forever, as the server is running in a goroutine
×
228
        c := make(chan os.Signal, 1)
×
229
        signal.Notify(c, os.Interrupt, syscall.SIGTERM)
×
230
        <-c
×
231
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc