-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy pathmain.go
224 lines (198 loc) · 5.45 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package main
import (
"errors"
"flag"
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nigelbabu/pppconverter/helper"
"github.com/nigelbabu/pppconverter/model"
)
var (
cached cachedData
db *gorm.DB
startTime time.Time
validate bool
cacheDuration = 1 * time.Hour
ErrorBadData = errors.New("bad PPP data in the database")
)
type cachedData struct {
countries []model.Country
conversionRate string
Time time.Time
}
type result struct {
SourceAmount string
TargetAmount string
SourceCountry string
SourceCurrency string
TargetCountry string
TargetCurrency string
}
type formData struct {
SourceCountry int `form:"from_country"`
Salary int64 `form:"salary"`
TargetCountry int `form:"to_country"`
}
func fetchCountries() ([]model.Country, error) {
if time.Now().Before(cached.Time) && len(cached.countries) != 0 {
return cached.countries, nil
}
var countries []model.Country
results := db.Order("name").Find(&countries)
if results.Error != nil {
return nil, results.Error
}
cached.countries = countries
cached.Time = time.Now().Add(cacheDuration)
return countries, nil
}
func fetchConversionRate() (string, error) {
if time.Now().Before(cached.Time) && cached.conversionRate != "" {
return cached.conversionRate, nil
}
var cfg model.Config
results := db.Where(model.Config{Key: "gbp_rate"}).First(&cfg)
if results.Error != nil {
return "", results.Error
}
cached.conversionRate = cfg.Value
cached.Time = time.Now().Add(cacheDuration)
return cfg.Value, nil
}
// Deal with the home page.
func homePage(c *gin.Context) {
countries, err := fetchCountries()
if err != nil {
log.Println("Failed to read DB: ", err)
}
conversionRate, err := fetchConversionRate()
if err != nil {
log.Println("Failed to read DB: ", err)
}
var srcCountry, targetCountry model.Country
var form formData
var r result
if c.ShouldBind(&form) == nil && c.Request.Method == http.MethodPost {
srcCountry.ID = form.SourceCountry
targetCountry.ID = form.TargetCountry
db.First(&srcCountry)
db.First(&targetCountry)
sourceAmt, err := helper.FormatMoney(float64(form.Salary))
if err != nil {
log.Printf("Failed to convert money: %s", err)
}
targetAmt, err := helper.FormatMoney((float64(form.Salary) / srcCountry.PPP) * targetCountry.PPP)
if err != nil {
log.Printf("Failed to convert money: %s", err)
}
r = result{
SourceAmount: sourceAmt,
TargetAmount: targetAmt,
SourceCountry: srcCountry.Name,
SourceCurrency: srcCountry.Currency,
TargetCountry: targetCountry.Name,
TargetCurrency: targetCountry.Currency,
}
log.Println("Salary", form.Salary)
}
c.HTML(http.StatusOK, "index.html", gin.H{
//TODO: Fetch from DB and round up correctly
"ConversionRate": conversionRate,
"Countries": countries,
"FormData": form,
"Result": r,
"GoogleAnalytics": viper.GetString("google_analytics"),
})
}
// Redirect old pages to home page.
func archivedRedirect(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/")
}
func setupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.Default()
// Serve static files
r.Static("/static", viper.GetString("static"))
// Load all the template files
r.LoadHTMLGlob(viper.GetString("templates"))
// Ping test
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, fmt.Sprintf("pong: %s", time.Now().Sub(startTime).Round(time.Second)))
})
// Home Page
r.GET("/", homePage)
r.POST("/", homePage)
// Archive Old Pages
r.GET("/about", archivedRedirect)
r.GET("/data", archivedRedirect)
r.GET("/contact", archivedRedirect)
return r
}
func setupDatabase() (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open(viper.GetString("database")), nil)
if err != nil {
return nil, err
}
return db, nil
}
func setupConfig() error {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.SetDefault("database", "temp.db")
viper.SetDefault("static", "./static")
viper.SetDefault("templates", "./templates/*.html")
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Ignore if config file not found
return nil
}
return fmt.Errorf("Failed to load config: %s", err)
}
return nil
}
func validateData() error {
var srcCountry, targetCountry model.Country
if err := db.Where("code3 = ?", "GBR").First(&srcCountry).Error; err != nil {
return fmt.Errorf("validateData - GBR: %w", err)
}
if err := db.Where("code3 = ?", "USA").First(&targetCountry).Error; err != nil {
return fmt.Errorf("validateData - USA: %w", err)
}
targetAmt := (float64(100) / srcCountry.PPP) * targetCountry.PPP
// If this is as low as 1, something has gone horribly wrong.
if targetAmt <= 1 {
return fmt.Errorf("100 GBP = %.2fUSD: %w", targetAmt, ErrorBadData)
}
return nil
}
func main() {
var err error
if err = setupConfig(); err != nil {
log.Fatal(err)
}
// Assigning to global DB variable
db, err = setupDatabase()
if err != nil {
log.Fatal(err)
}
// Fail if the data does not exist to convert from USD to INR
if err := validateData(); err != nil && validate {
log.Fatal(err)
}
r := setupRouter()
startTime = time.Now()
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
func init() {
flag.BoolVar(&validate, "validate", false, "If true, crash on startup if the data is invalid")
flag.Parse()
}