mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 21:56:50 -04:00
Added database backup/restore.
This commit is contained in:
12
main.go
12
main.go
@@ -9,18 +9,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const eventDbPath = "./event.db"
|
||||
|
||||
var db *Database
|
||||
var eventSettings *EventSettings
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
initDb()
|
||||
|
||||
ServeWebInterface()
|
||||
}
|
||||
|
||||
func initDb() {
|
||||
var err error
|
||||
db, err = OpenDatabase("test.db")
|
||||
db, err = OpenDatabase(eventDbPath)
|
||||
checkErr(err)
|
||||
eventSettings, err = db.GetEventSettings()
|
||||
checkErr(err)
|
||||
|
||||
ServeWebInterface()
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
|
||||
@@ -6,10 +6,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Shows the event settings editing page.
|
||||
@@ -19,11 +25,6 @@ func SettingsGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Saves the event settings.
|
||||
func SettingsPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
eventSettings.Name = r.PostFormValue("name")
|
||||
eventSettings.Code = r.PostFormValue("code")
|
||||
match, _ := regexp.MatchString("^#([0-9A-Fa-f]{3}){1,2}$", r.PostFormValue("displayBackgroundColor"))
|
||||
@@ -41,12 +42,94 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
eventSettings.SelectionRound1Order = r.PostFormValue("selectionRound1Order")
|
||||
eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order")
|
||||
eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order")
|
||||
err = db.SaveEventSettings(eventSettings)
|
||||
err := db.SaveEventSettings(eventSettings)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
renderSettings(w, r, "")
|
||||
http.Redirect(w, r, "/setup/settings", 302)
|
||||
}
|
||||
|
||||
// Sends a copy of the event database file to the client as a download.
|
||||
func SaveDbHandler(w http.ResponseWriter, r *http.Request) {
|
||||
dbFile, err := os.Open(db.path)
|
||||
defer dbFile.Close()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
filename := fmt.Sprintf("%s-%s.db", strings.Replace(eventSettings.Name, " ", "_", -1),
|
||||
time.Now().Format("20060102150405"))
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
http.ServeContent(w, r, "", time.Now(), dbFile)
|
||||
}
|
||||
|
||||
// Accepts an event database file as an upload and loads it.
|
||||
func RestoreDbHandler(w http.ResponseWriter, r *http.Request) {
|
||||
file, _, err := r.FormFile("databaseFile")
|
||||
if err != nil {
|
||||
renderSettings(w, r, "No database backup file was specified.")
|
||||
return
|
||||
}
|
||||
|
||||
// Write the file to a temporary location on disk and verify that it can be opened as a database.
|
||||
tempFile, err := ioutil.TempFile(".", "uploaded-db-")
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
defer tempFile.Close()
|
||||
tempFilePath := tempFile.Name()
|
||||
defer os.Remove(tempFilePath)
|
||||
_, err = io.Copy(tempFile, file)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
tempFile.Close()
|
||||
tempDb, err := OpenDatabase(tempFilePath)
|
||||
if err != nil {
|
||||
renderSettings(w, r, "Could not read uploaded database backup file. Please verify that it a valid "+
|
||||
"database file.")
|
||||
return
|
||||
}
|
||||
tempDb.Close()
|
||||
|
||||
// Replace the current database with the new one.
|
||||
db.Close()
|
||||
err = os.Rename(tempFilePath, eventDbPath)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
initDb()
|
||||
|
||||
http.Redirect(w, r, "/setup/settings", 302)
|
||||
}
|
||||
|
||||
// Deletes all data except for the team list.
|
||||
func ClearDbHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := db.TruncateMatches()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
err = db.TruncateMatchResults()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
err = db.TruncateRankings()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
err = db.TruncateAllianceTeams()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/setup/settings", 302)
|
||||
}
|
||||
|
||||
func renderSettings(w http.ResponseWriter, r *http.Request, errorMessage string) {
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -28,7 +33,8 @@ func TestSetupSettings(t *testing.T) {
|
||||
// Change the settings and check the response.
|
||||
recorder = postHttpResponse("/setup/settings", "name=Chezy Champs&code=CC&displayBackgroundColor=#ff00ff&"+
|
||||
"numElimAlliances=16")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/settings")
|
||||
assert.Contains(t, recorder.Body.String(), "Chezy Champs")
|
||||
assert.Contains(t, recorder.Body.String(), "CC")
|
||||
assert.Contains(t, recorder.Body.String(), "#ff00ff")
|
||||
@@ -52,3 +58,88 @@ func TestSetupSettingsInvalidValues(t *testing.T) {
|
||||
recorder = postHttpResponse("/setup/settings", "numAlliances=1&displayBackgroundColor=#000")
|
||||
assert.Contains(t, recorder.Body.String(), "must be between 2 and 16")
|
||||
}
|
||||
|
||||
func TestSetupSettingsClearDb(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
|
||||
db.CreateTeam(new(Team))
|
||||
db.CreateMatch(&Match{Type: "qualification"})
|
||||
db.CreateMatchResult(new(MatchResult))
|
||||
db.CreateRanking(new(Ranking))
|
||||
db.CreateAllianceTeam(new(AllianceTeam))
|
||||
recorder := postHttpResponse("/setup/db/clear", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
|
||||
teams, _ := db.GetAllTeams()
|
||||
assert.NotEmpty(t, teams)
|
||||
matches, _ := db.GetMatchesByType("qualification")
|
||||
assert.Empty(t, matches)
|
||||
rankings, _ := db.GetAllRankings()
|
||||
assert.Empty(t, rankings)
|
||||
db.CalculateRankings()
|
||||
assert.Empty(t, rankings)
|
||||
alliances, _ := db.GetAllAlliances()
|
||||
assert.Empty(t, alliances)
|
||||
}
|
||||
|
||||
func TestSetupSettingsBackupRestoreDb(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
|
||||
// Modify a parameter so that we know when the database has been restored.
|
||||
eventSettings.Name = "Chezy Champs"
|
||||
db.SaveEventSettings(eventSettings)
|
||||
|
||||
// Back up the database.
|
||||
recorder := getHttpResponse("/setup/db/save")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, "application/octet-stream", recorder.HeaderMap["Content-Type"][0])
|
||||
backupBody := recorder.Body
|
||||
|
||||
// Wipe the database to reset the defaults.
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
assert.NotEqual(t, "Chezy Champs", eventSettings.Name)
|
||||
|
||||
// Check restoring with a missing file.
|
||||
recorder = postHttpResponse("/setup/db/restore", "")
|
||||
assert.Contains(t, recorder.Body.String(), "No database backup file was specified")
|
||||
assert.NotEqual(t, "Chezy Champs", eventSettings.Name)
|
||||
|
||||
// Check restoring with a corrupt file.
|
||||
recorder = postFileHttpResponse("/setup/db/restore", "databaseFile", bytes.NewBufferString("invalid"))
|
||||
assert.Contains(t, recorder.Body.String(), "Could not read uploaded database backup file")
|
||||
assert.NotEqual(t, "Chezy Champs", eventSettings.Name)
|
||||
|
||||
// Check restoring with the backup retrieved before.
|
||||
recorder = postFileHttpResponse("/setup/db/restore", "databaseFile", backupBody)
|
||||
assert.Equal(t, "Chezy Champs", eventSettings.Name)
|
||||
}
|
||||
|
||||
func postFileHttpResponse(path string, paramName string, file *bytes.Buffer) *httptest.ResponseRecorder {
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
part, _ := writer.CreateFormFile(paramName, "file.ext")
|
||||
io.Copy(part, file)
|
||||
writer.Close()
|
||||
recorder := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", path, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
newHandler().ServeHTTP(recorder, req)
|
||||
return recorder
|
||||
}
|
||||
|
||||
@@ -34,11 +34,6 @@ func TeamsPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
var teamNumbers []int
|
||||
for _, teamNumberString := range strings.Split(r.PostFormValue("teamNumbers"), "\r\n") {
|
||||
teamNumber, err := strconv.Atoi(teamNumberString)
|
||||
@@ -59,7 +54,7 @@ func TeamsPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
renderTeams(w, r, false)
|
||||
http.Redirect(w, r, "/setup/teams", 302)
|
||||
}
|
||||
|
||||
// Clears the team list.
|
||||
@@ -74,7 +69,7 @@ func TeamsClearHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
renderTeams(w, r, false)
|
||||
http.Redirect(w, r, "/setup/teams", 302)
|
||||
}
|
||||
|
||||
// Shows the page to edit a team's fields.
|
||||
@@ -121,11 +116,6 @@ func TeamEditPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
team.Name = r.PostFormValue("name")
|
||||
team.Nickname = r.PostFormValue("nickname")
|
||||
team.City = r.PostFormValue("city")
|
||||
@@ -138,7 +128,7 @@ func TeamEditPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
renderTeams(w, r, false)
|
||||
http.Redirect(w, r, "/setup/teams", 302)
|
||||
}
|
||||
|
||||
// Removes a team from the team list.
|
||||
@@ -164,7 +154,7 @@ func TeamDeletePostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
renderTeams(w, r, false)
|
||||
http.Redirect(w, r, "/setup/teams", 302)
|
||||
}
|
||||
|
||||
func renderTeams(w http.ResponseWriter, r *http.Request, showErrorMessage bool) {
|
||||
|
||||
@@ -37,14 +37,16 @@ func TestSetupTeams(t *testing.T) {
|
||||
|
||||
// Add some teams.
|
||||
recorder = postHttpResponse("/setup/teams", "teamNumbers=254\r\nnotateam\r\n1114\r\n")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.Contains(t, recorder.Body.String(), "2 teams")
|
||||
assert.Contains(t, recorder.Body.String(), "The Cheesy Poofs")
|
||||
assert.Contains(t, recorder.Body.String(), "1114")
|
||||
|
||||
// Add another team.
|
||||
recorder = postHttpResponse("/setup/teams", "teamNumbers=33")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.Contains(t, recorder.Body.String(), "3 teams")
|
||||
assert.Contains(t, recorder.Body.String(), "33")
|
||||
|
||||
@@ -53,17 +55,20 @@ func TestSetupTeams(t *testing.T) {
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "The Cheesy Poofs")
|
||||
recorder = postHttpResponse("/setup/teams/254/edit", "nickname=Teh Chezy Pofs")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.Contains(t, recorder.Body.String(), "Teh Chezy Pofs")
|
||||
|
||||
// Delete a team.
|
||||
recorder = postHttpResponse("/setup/teams/1114/delete", "")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.Contains(t, recorder.Body.String(), "2 teams")
|
||||
|
||||
// Test clearing all teams.
|
||||
recorder = postHttpResponse("/setup/teams/clear", "")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.Contains(t, recorder.Body.String(), "0 teams")
|
||||
}
|
||||
|
||||
@@ -98,6 +103,8 @@ func TestSetupTeamsDisallowModification(t *testing.T) {
|
||||
|
||||
// Allow editing a team.
|
||||
recorder = postHttpResponse("/setup/teams/254/edit", "nickname=Teh Chezy Pofs")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/teams")
|
||||
assert.NotContains(t, recorder.Body.String(), "can't modify")
|
||||
assert.Contains(t, recorder.Body.String(), "1 teams")
|
||||
assert.Contains(t, recorder.Body.String(), "Teh Chezy Pofs")
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{{define "title"}}Settings{{end}}
|
||||
{{define "body"}}
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert alert-dismissable alert-danger">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
{{.ErrorMessage}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="col-lg-6 col-lg-offset-1">
|
||||
<div class="well">
|
||||
<form class="form-horizontal" action="/setup/settings" method="POST">
|
||||
<fieldset>
|
||||
<legend>Event Settings</legend>
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert alert-dismissable alert-danger">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
{{.ErrorMessage}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label for="textArea" class="col-lg-5 control-label">Name</label>
|
||||
<div class="col-lg-7">
|
||||
@@ -113,6 +113,63 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="well">
|
||||
<legend>Database</legend>
|
||||
<p>
|
||||
<a href="/setup/db/save"><button class="btn btn-info">Save Copy of Database</button></a>
|
||||
</p>
|
||||
<p>
|
||||
<button class="btn btn-primary"onclick="$('#uploadDatabase').modal('show'); return false;">
|
||||
Load Database from Backup
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button class="btn btn-primary"onclick="$('#confirmClearData').modal('show'); return false;">
|
||||
Clear All Match Data
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="uploadDatabase" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Choose Backup File</h4>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/setup/db/restore" enctype="multipart/form-data" method="POST">
|
||||
<div class="modal-body">
|
||||
<p>Select the database file to load from. <b>This will overwrite any existing data.</b></p>
|
||||
<input type="file" name="databaseFile">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Load Database from Backup</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirmClearData" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to clear all match, ranking, and alliance selection data?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form class="form-horizontal" action="/setup/db/clear" method="POST">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Clear All Match Data</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{{define "title"}}Team List{{end}}
|
||||
{{define "body"}}
|
||||
{{if .ShowErrorMessage}}
|
||||
<div class="alert alert-dismissable alert-danger">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
You can't modify the team list once the qualification schedule has been generated. If you need to change the
|
||||
team list, clear all other data first on the Settings page.
|
||||
</div>
|
||||
<div class="alert alert-dismissable alert-danger">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
You can't modify the team list once the qualification schedule has been generated. If you need to change
|
||||
the team list, clear all other data first on the Settings page.
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
@@ -67,24 +67,25 @@
|
||||
</table>
|
||||
<b>{{len .Teams}} teams</b>
|
||||
</div>
|
||||
<div id="confirmClearTeams" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to clear the team list?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form class="form-horizontal" action="/setup/teams/clear" method="POST">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Clear Team List</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirmClearTeams" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to clear the team list?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form class="form-horizontal" action="/setup/teams/clear" method="POST">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Clear Team List</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}{{end}}
|
||||
|
||||
3
web.go
3
web.go
@@ -65,6 +65,9 @@ func newHandler() http.Handler {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/setup/settings", SettingsGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/settings", SettingsPostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/db/save", SaveDbHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/db/restore", RestoreDbHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/db/clear", ClearDbHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/teams", TeamsGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/teams", TeamsPostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/teams/clear", TeamsClearHandler).Methods("POST")
|
||||
|
||||
Reference in New Issue
Block a user