Browse Source

Rewrote API and created a better structure for it

master
stew3254 2 years ago
parent
commit
9b3f51fe22
  1. 4
      .gitignore
  2. 19
      build.sh
  3. 5
      go.mod
  4. 2
      go.sum
  5. 4
      html/error.html
  6. 0
      html/index.html
  7. 0
      sql/drop.sql
  8. 0
      sql/setup.sql
  9. 107
      src/db.go
  10. 19
      src/helper.go
  11. 39
      src/main.go
  12. 308
      src/server.go

4
.gitignore

@ -1 +1,3 @@
.password
*.env
!.env-template
bin/

19
build.sh

@ -1,19 +0,0 @@
#!/bin/sh
# full path to the image
app="$1"
if [ -z $app ]; then
app="/srv/main"
fi
# image name
name="$2"
if [ -z $name ]; then
name="compile_container"
fi
docker build -t $name -f Dockerfile.comp .
docker container create --name temp $name
docker container cp temp:$app bin
docker container rm temp
docker-compose up --build -d

5
go.mod

@ -2,4 +2,7 @@ module captain
go 1.14
require github.com/go-sql-driver/mysql v1.5.0
require (
github.com/go-sql-driver/mysql v1.5.0
github.com/pkg/errors v0.9.1 // indirect
)

2
go.sum

@ -1,2 +1,4 @@
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

4
html/404.html → html/error.html

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 - Page Not Found</title>
<title>{{.StatusCode}} Error - {{.Msg}}</title>
</head>
<body>
<h1>404 - Page Not Found</h1>
<h1>{{.StatusCode}} Error - {{.Msg}}</h1>
</body>
</html>

0
html/index.tmpl → html/index.html

0
drop.sql → sql/drop.sql

0
setup.sql → sql/setup.sql

107
src/db.go

@ -2,11 +2,110 @@ package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
)
func runSQL(db *sql.DB, name string) error {
func connectDB() (*sql.DB, error) {
// Get user credentials
user, exists := os.LookupEnv("MYSQL_USER")
checkExists(exists, "Couldn't find database user")
password, exists := os.LookupEnv("MYSQL_PASSWORD")
checkExists(exists, "Couldn't find database password")
// Get database params
dbServer, exists := os.LookupEnv("MYSQL_SERVER")
checkExists(exists, "Couldn't find database server")
dbPort, exists := os.LookupEnv("MYSQL_PORT")
checkExists(exists, "Couldn't find database port")
dbName, exists := os.LookupEnv("MYSQL_DATABASE")
checkExists(exists, "Couldn't find database name")
connectionString := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s",
user,
password,
dbServer,
dbPort,
dbName,
)
// Check how many times to try the db before quitting
attemptsStr, exists := os.LookupEnv("DB_ATTEMPTS")
if !exists {
attemptsStr = "5"
}
attempts, err := strconv.Atoi(attemptsStr)
if err != nil {
attempts = 5
}
timeoutStr, exists := os.LookupEnv("DB_CONNECTION_TIMEOUT")
if !exists {
timeoutStr = "5"
}
timeout, err := strconv.Atoi(timeoutStr)
if err != nil {
timeout = 5
}
db, err = sql.Open("mysql", connectionString)
if err != nil {
return db, err
}
for i := 1; i <= attempts; i++ {
//// DEBUG: Making sure table is good
// if err := dropDB(db); err != nil {
// log.Fatal(err)
// }
// Create database if it doesn't exist
err = createDB(db)
if err != nil {
// Check to see if the error is a connection issue
if !strings.HasPrefix(err.Error(), "dial") {
return db, err
}
if i != attempts {
log.Printf(
"WARNING: Could not connect to db on attempt %d. Trying again in %d seconds.\n",
i,
timeout,
)
} else {
return db, errors.Errorf("Could not connect to db after %d attempts\n", attempts)
}
time.Sleep(time.Duration(timeout) * time.Second)
} else {
// No error to worry about
break
}
}
log.Println("Connection to db succeeded!")
return db, nil
}
// func queryRow(db *sql.DB, name string) (*sql.Rows, error) {
// b, err := ioutil.ReadFile(name)
// if err != nil {
// return nil, err
// }
// return db.QueryRow(string(b)), nil
// }
// func query(db *sql.DB, name string) (interface{}, error) {
// db.Qu
// }
func execSQL(db *sql.DB, name string) error {
b, err := ioutil.ReadFile(name)
if err != nil {
return err
@ -21,9 +120,9 @@ func runSQL(db *sql.DB, name string) error {
}
func createDB(db *sql.DB) error {
return runSQL(db, "setup.sql")
return execSQL(db, "sql/setup.sql")
}
func dropDB(db *sql.DB) error {
return runSQL(db, "drop.sql")
return execSQL(db, "sql/drop.sql")
}

19
src/helper.go

@ -0,0 +1,19 @@
package main
import (
"log"
)
// Check errors and fail if they're bad
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
// Check if a value exists and fail if it doesn't
func checkExists(exists bool, msg string) {
if !exists {
log.Fatal(msg)
}
}

39
src/main.go

@ -0,0 +1,39 @@
package main
import (
"net/http"
"log"
"os"
)
// func init() {
// // Loads values from .env into the system
// if err := godotenv.Load(); err != nil {
// log.Println("No .env file found")
// }
// }
func main() {
var err error
db, err = connectDB();
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Get listen address and port
addr, exists := os.LookupEnv("LISTEN")
checkExists(exists, "Couldn't find listen address")
port, exists := os.LookupEnv("PORT")
checkExists(exists, "Couldn't find port")
// Handle hooks
http.HandleFunc("/hook/", hookHandler)
http.HandleFunc("/", index)
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("html/css"))))
// Start the server
log.Println("Starting server")
log.Fatal(http.ListenAndServe(addr+":"+port, nil))
}

308
src/server.go

@ -3,41 +3,57 @@ package main
import (
"database/sql"
"fmt"
"time"
// "github.com/joho/godotenv"
"html/template"
"log"
"net/http"
"os"
"strconv"
_ "github.com/go-sql-driver/mysql"
)
//Global db object
var db *sql.DB
// Check errors and fail if they're bad
func check(err error) {
// Used in writing out errors when they occur
type errorMessage struct {
StatusCode int
Msg string
}
// Writes the given error message in the template. If not, just writes the message raw
func writeError(w http.ResponseWriter, e errorMessage) {
// Try to use error template
w.WriteHeader(e.StatusCode)
t := template.Must(template.New("error.html").ParseFiles("html/error.html"))
err := t.Execute(w, e)
// If something goes wrong just tell them the error in an ugly way
if err != nil {
log.Fatal(err)
// Can't tell the user since the writer already wrote out
log.Println(err)
}
}
// Get all the currently available hooks
func getHooks() []Hook {
func getHooks(w http.ResponseWriter) []Hook {
// Get all hooks from db
rows, err := db.Query("SELECT ID FROM HOOKS")
if err != nil {
log.Fatal(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
}
defer rows.Close()
// Look through hooks
var hooks []Hook
for rows.Next() {
var hook Hook
err = rows.Scan(&hook.Name)
// Hook doesn't have a name (This shouldn't really happen)
if err != nil {
log.Fatal(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
}
hooks = append(hooks, hook)
}
@ -46,75 +62,122 @@ func getHooks() []Hook {
// Show the index
func index(w http.ResponseWriter, r *http.Request) {
// Log request to this url
log.Println(r.URL.String())
if r.URL.String() == "/" {
t := template.Must(template.New("index.tmpl").ParseFiles("html/index.tmpl"))
for i, j := range getHooks() {
log.Printf("%d: %s\n", i, j.Name)
}
err := t.Execute(w, getHooks())
// Serve index template
t := template.Must(template.New("index.html").ParseFiles("html/index.html"))
err := t.Execute(w, getHooks(w))
if err != nil {
log.Fatal(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
}
// Can't find the page
} else {
http.ServeFile(w, r, "html/404.html")
writeError(w, errorMessage {
StatusCode: http.StatusNotFound,
Msg: "Not found",
})
}
}
//Run a particular hook (will probably come up with a better solution)
// Run a particular hook (will probably come up with a better solution)
func runHook(w http.ResponseWriter, r *http.Request, hook Hook) {
fmt.Fprintln(w, hook.Name)
}
// Handles all of the hooks
// Handles requests to /hook/
func hookHandler(w http.ResponseWriter, r *http.Request) {
// Log request to this url
log.Println(r.URL.String())
name := r.URL.String()[len("/hook/"):]
row := db.QueryRow("SELECT EXISTS(SELECT id FROM HOOKS WHERE HOOKS.id = ?)", name)
var res int
row.Scan(&res)
if res == 0 {
http.ServeFile(w, r, "html/404.html")
if r.URL.String() == "/hook/" {
switch r.Method {
// Display all hooks
case http.MethodGet:
showHooks(w)
// Method unsupported
default:
log.Println("y tho")
writeError(w, errorMessage {
StatusCode: http.StatusMethodNotAllowed,
Msg: "Method Unsupported",
})
}
return
}
hook := Hook{
Name: name,
params: nil,
actions: nil,
// Hook name
name := r.URL.String()[len("/hook/"):]
switch r.Method {
// Handle the hook
case http.MethodGet, http.MethodPost:
// See if the hook exists
row := db.QueryRow("SELECT EXISTS(SELECT id FROM HOOKS WHERE HOOKS.id = ?)", name)
var res int
row.Scan(&res)
// Hook doesn't exist
if res == 0 {
writeError(w, errorMessage {
StatusCode: http.StatusNotFound,
Msg: "Not Found",
})
return
}
// Construct the hook from the results
hook := Hook {
Name: name,
params: nil,
actions: nil,
}
// Run the user's hook
runHook(w, r, hook)
// Create / Update the hook
case http.MethodPut:
createHook(w, name)
// Delete the hook
case http.MethodDelete:
deleteHook(w, name)
// Method unsupported
default:
writeError(w, errorMessage {
StatusCode: http.StatusMethodNotAllowed,
Msg: "Method Not Allowed",
})
}
runHook(w, r, hook)
}
// Shows all of the hooks
func showHooks(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
if r.URL.String() != "/hooks/" {
http.ServeFile(w, r, "html/404.html")
return
}
hooks := getHooks()
func showHooks(w http.ResponseWriter) {
hooks := getHooks(w)
allHooks := ""
for _, h := range hooks {
allHooks += h.Name + "\n"
if hooks != nil {
for _, h := range hooks {
allHooks += h.Name + "\n"
}
} else {
allHooks = "Nothing Found\n"
}
fmt.Fprint(w, allHooks)
}
// Creates a new hook
func createHook(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
// Doesn't actually work yet
if r.URL.String() == "/hook/create/" {
http.ServeFile(w, r, "html/create.html")
return
}
// Create a hook
name := r.URL.String()[len("/hook/create/"):]
func createHook(w http.ResponseWriter, name string) {
statement, err := db.Prepare("INSERT IGNORE INTO HOOKS(id) VALUES(?)")
// Shouldn't really happen, but incase the insert goes wrong
if err != nil {
log.Fatal(err)
fmt.Fprintln(w, err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
return
}
defer statement.Close()
@ -122,141 +185,48 @@ func createHook(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, name)
}
// Delete one hook
func deleteHook(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
name := r.URL.String()[len("/hook/delete/"):]
// Delete a hook
func deleteHook(w http.ResponseWriter, name string) {
statement, err := db.Prepare("DELETE FROM HOOKS where ID=(?)")
//Shouldn't ever error
if err != nil {
log.Println(err)
fmt.Fprintln(w, err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
return
}
defer statement.Close()
// Get results
res, err := statement.Exec(name)
// Shouldn't ever error
if err != nil {
log.Println(err)
fmt.Fprintln(w, err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
return
}
// Probably also shouldn't error
n, err := res.RowsAffected()
if err != nil {
log.Println(err)
fmt.Fprintln(w, err)
return
}
// Nothing was deleted
if n == 0 {
http.ServeFile(w, r, "html/404.html")
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: "Hook was not deleted",
})
return
}
fmt.Fprintln(w, name)
// log.Println(cap(Hooks))
// //Clear hooks (this is perfectly safe to do)
// Hooks = nil
}
// func init() {
// // Loads values from .env into the system
// if err := godotenv.Load(); err != nil {
// log.Println("No .env file found")
// }
// }
func main() {
// Check if a value exists and fail if it doesn't
checkExists := func(exists bool, msg string) {
if !exists {
log.Fatal(msg)
}
}
// Get user credentials
user, exists := os.LookupEnv("MYSQL_USER")
checkExists(exists, "Couldn't find database user")
password, exists := os.LookupEnv("MYSQL_PASSWORD")
checkExists(exists, "Couldn't find database password")
// Get database params
dbServer, exists := os.LookupEnv("MYSQL_SERVER")
checkExists(exists, "Couldn't find database server")
dbPort, exists := os.LookupEnv("MYSQL_PORT")
checkExists(exists, "Couldn't find database port")
dbName, exists := os.LookupEnv("MYSQL_DATABASE")
checkExists(exists, "Couldn't find database name")
connectionString := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s",
user,
password,
dbServer,
dbPort,
dbName,
)
// Check how many times to try the db before quitting
attemptsStr, exists := os.LookupEnv("DB_ATTEMPTS")
if !exists {
attemptsStr = "5"
}
attempts, err := strconv.Atoi(attemptsStr)
if err != nil {
attempts = 5
}
timeoutStr, exists := os.LookupEnv("DB_CONNECTION_TIMEOUT")
if !exists {
timeoutStr = "5"
}
timeout, err := strconv.Atoi(timeoutStr)
if err != nil {
timeout = 5
}
for i := 1; i <= attempts; i++ {
db, err = sql.Open("mysql", connectionString)
if err != nil && i != attempts {
log.Printf(
"WARNING: Could not connect to db on attempt %d. Trying again in %d seconds.\n",
attempts,
timeout,
)
} else if err != nil {
log.Fatalf("Could not connect to db after %d attempts\n", attempts)
}
time.Sleep(time.Duration(timeout) * time.Second)
}
log.Println("Connection to db succeeded!")
defer db.Close()
// Open the db and initialize the table if it doesn't exist
db, err = sql.Open("mysql", connectionString)
//// DEBUG: Making sure table is good
// if err := dropDB(db); err != nil {
// log.Fatal(err)
// }
// Create database if it doesn't exist
if err := createDB(db); err != nil {
log.Fatal(err)
}
// Get listen address and port
addr, exists := os.LookupEnv("LISTEN")
checkExists(exists, "Couldn't find listen address")
port, exists := os.LookupEnv("PORT")
checkExists(exists, "Couldn't find port")
// Handle hooks
http.HandleFunc("/hook/delete/", deleteHook)
http.HandleFunc("/hook/create/", createHook)
http.HandleFunc("/hook/", hookHandler)
http.HandleFunc("/hooks/", showHooks)
http.HandleFunc("/", index)
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("html/css"))))
// Start the server
log.Println("Starting server")
log.Fatal(http.ListenAndServe(addr+":"+port, nil))
}
fmt.Fprintln(w, name)
}
Loading…
Cancel
Save