-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
202 lines (165 loc) · 5.4 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
package main
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
"log"
"os"
"strings"
)
type Post struct {
Id int32 `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
Content string `json:"content"`
Created string `json:"created"`
Published bool `json:"published"`
}
type Configuration struct {
Title string `json:"title"`
Author string `json:"author"`
DatabaseUrlEnv string `json:"database_url_env"`
GithubUrl string `json:"github_url"`
LinkedInUrl string `json:"linkedin_url"`
HttpPort string `json:"http_port"`
}
/*
Utilizes the existing MySQL connection to query for all published posts ordered by date created.
*/
func getPosts(conn *sql.DB) []Post {
listSlice := make([]Post, 0, 10)
rows, err := conn.Query("select * from post where published = 1 order by created desc")
if err != nil {
log.Fatal("Could not make a connection to the MySQL database.")
}
for rows.Next() {
var id int32
var slug string
var title string
var content string
var created string
var published bool
err := rows.Scan(&id, &slug, &title, &content, &created, &published)
if err != nil {
log.Fatal("One of the columns in the row could not be mapped and assigned to a variable.")
}
post := Post{id, slug, title, content, created, published}
listSlice = append(listSlice, post)
}
return listSlice
}
/*
Gets the configuration.json file at the same directory of the app and deserializes it to a configuration struct
*/
func getConfiguration() Configuration {
data, err := os.ReadFile(makeFullPath("configuration.json"))
if err != nil {
log.Fatal("Could not read configuration.json. Does it exist in the same directory as the app?")
}
var configuration Configuration
err = json.Unmarshal(data, &configuration)
if err != nil {
log.Fatal("Could not deserialize JSON to a Configuration structure")
}
return configuration
}
/* Handy helper to always build render template variables so that pages ender consistently with the correct data */
func buildTemplateArguments(params fiber.Map, configuration *Configuration) fiber.Map {
result := fiber.Map{
"PageTitle": configuration.Title,
"GitHubUrl": configuration.GithubUrl,
"LinkedInUrl": configuration.LinkedInUrl,
"Author": configuration.Author,
}
for k, v := range params {
result[k] = v
}
return result
}
func makeFullPath(relativePath string) string {
pwd, err := os.Getwd()
if err != nil {
log.Fatal("Cannot get the current working directory.")
}
return fmt.Sprintf("%s/%s", pwd, relativePath)
}
func main() {
configuration := getConfiguration()
// `database_url_env` value is actually the environment variable name.
//
// If the database_url_env value in configuration.json is "DATABASE_URL"
// then os.GetEnv("DATABASE_URL") will contain the real endpoint.
databaseUrlEnvVar := os.Getenv(configuration.DatabaseUrlEnv)
conn, err := sql.Open("mysql", databaseUrlEnvVar)
if err != nil {
log.Fatal("Unable to connect to the database. Either this machine is offline, or the server is offline.")
}
defer conn.Close()
// Initialize the view engine and set the directory to /views
viewEngine := html.New(makeFullPath("views"), ".html")
app := fiber.New(fiber.Config{
Views: viewEngine,
})
// Register static path to be at the root. The files will be found in /public.
// So if a request comes in for /picture.jpg, then the server will attempt to find
// picture.jpg within the /public folder locally. Ex: /public/picture.jpg.
app.Static("/", makeFullPath("public"))
app.Get("/", func(c *fiber.Ctx) error {
listSlice := getPosts(conn)
return c.Render("index", buildTemplateArguments(fiber.Map{
"Posts": listSlice,
}, &configuration), "layout")
})
app.Get("/about", func(c *fiber.Ctx) error {
return c.Render("about", buildTemplateArguments(fiber.Map{}, &configuration), "layout")
})
app.Get("/resume", func(c *fiber.Ctx) error {
return c.Render("resume", buildTemplateArguments(fiber.Map{}, &configuration), "layout")
})
app.Get("/p/:slug", func(c *fiber.Ctx) error {
userSlug := strings.ToLower(c.Params("slug"))
// Really lame way to sanitize for now
invalidCharacters := []string{
"!", "#", "$", "%", "^",
"&", "*", "(", ")", " ",
";", ":", "\"", "\\", "/",
"'", "?", ".", "<", ">",
"{", "}", "[", "]",
}
for _, invalidCharacter := range invalidCharacters {
userSlug = strings.ReplaceAll(userSlug, invalidCharacter, "")
}
query := fmt.Sprintf("select * from post where slug = '%s' and published = 1 order by created desc", userSlug)
rows, err := conn.Query(query)
if err != nil {
return c.SendStatus(404)
}
if rows.Next() {
var id int32
var slug string
var title string
var content string
var created string
var published bool
err := rows.Scan(&id, &slug, &title, &content, &created, &published)
if err != nil {
c.SendStatus(500)
}
post := Post{id, slug, title, content, created, published}
return c.Render("post", buildTemplateArguments(fiber.Map{
"Title": post.Title,
"Content": post.Content,
"Created": post.Created,
}, &configuration), "layout")
}
// Otherwise we couldn't find anything.
return c.SendStatus(404)
})
listenErr := app.Listen(fmt.Sprintf("0.0.0.0:%s", configuration.HttpPort))
if listenErr != nil {
log.Fatal("Could not bind to the port.")
}
}