Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/Themes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Themes

Inside of the [themes folder](../run/themes/), you may find the CSS files for
each theme. Each theme lives inside a single file CSS. The name of the theme
will be inherited from the name of the file (omitting '.css').

The themes will be loaded via a link HTML tag, so expect the entire CSS file to
have an effect on the site.

ALL CSS variables inside a theme must be followed by `!important`. This will let
the browser know to use our CSS variable definitions instead the defaults from
GreenScoutJS.

```css
--font-color: #1e2226 !important;
```

If a theme does not provide a definition for a CSS variable, its default will be
used in place. The default theme can be found
[here in the GreenScoutJS repo. **Reference this file.**](https://github.com/TheGreenMachine/GreenScoutJS/blob/main/greenscoutjs/src/index.css)
**for all available CSS variables to override.** Again, make sure to include
`!important` when copy and pasting.
182 changes: 172 additions & 10 deletions internal/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -141,6 +142,7 @@ func SetupServer() *http.Server {
http.HandleFunc("/getPfp", handleWithCORS(handlePfpRequest, true))
http.HandleFunc("/generalInfo", handleWithCORS(handleGeneralInfoRequest, true))
http.HandleFunc("/allEvents", handleWithCORS(handleEventsRequest, true))
http.HandleFunc("/allThemes", handleWithCORS(handleThemesRequest, false))
http.HandleFunc("/gallery", handleWithCORS(handleGalleryRequest, true))

//Provides Authentication
Expand All @@ -151,13 +153,15 @@ func SetupServer() *http.Server {
http.HandleFunc("/dataEntry", handleWithCORS(postTeamData, true))
http.HandleFunc("/pitScout", handleWithCORS(postPitScout, true))
http.HandleFunc("/singleSchedule", handleWithCORS(serveScouterSchedule, true))
http.HandleFunc("/theme", handleWithCORS(serveTheme, false))
http.HandleFunc("/getTheme", handleWithCORS(serveTheme, false))

//Admin or curr user
http.HandleFunc("/setDisplayName", handleWithCORS(setDisplayName, true))
http.HandleFunc("/setUserPfp", handleWithCORS(setPfp, true))
http.HandleFunc("/provideAdditions", handleWithCORS(handleFrontendAdditions, true))
http.HandleFunc("/setColor", handleWithCORS(handleColorChange, true))
http.HandleFunc("/setTheme", handleWithCORS(setTheme, true))
http.HandleFunc("/currTheme", handleWithCORS(getTheme, false))

//Admin or verified
http.HandleFunc("/spreadsheet", handleWithCORS(serveSpreadsheet, true))
Expand Down Expand Up @@ -459,19 +463,166 @@ func serveScouterSchedule(writer http.ResponseWriter, request *http.Request) {
httpResponsef(writer, "Problem serving scouter schedule", "%s", response)
}

// Serves a theme's css files
func serveTheme(writer http.ResponseWriter, request *http.Request) {
auth := getAuthFromCookies(request)
_ = auth

if auth.Preflight {
writer.WriteHeader(200)
return
}

if !auth.Authed {
writer.WriteHeader(401)
httpResponsef(writer, "Could not get user theme", "Not authenticated :(")
return
}

writer.Header().Set("Vary", "Cookie")
writer.Header().Set("Cache-Control", "private, max-age=0, must-revalidate")
writer.Header().Set("Cache-Control", "private, max-age=2, must-revalidate")
writer.Header().Set("Content-Type", "text/css; charset=utf-8")

theme := "light" // getThemeFromCookies(auth.UUID) not implemented
// Optionally if you want to grab a theme independent of the user
directTheme := request.Header.Get("theme")

theme := GetTheme(auth.UUID)

if directTheme != "" {
allThemes, err := ListAllThemes()
if err != nil {
LogError(err, "Failed to fetch all themes: ")

writer.WriteHeader(500)
httpResponsef(writer, "Could not find specified theme", "An unexpected error occurred preventing validation of the theme name")
return
}

// this prevents the client from deciding it wants just ANY file on our system (mucho bado)
if slices.Contains(allThemes, directTheme) {
theme = directTheme
} else {
writer.WriteHeader(404)
httpResponsef(writer, "Could not find specified theme", "Theme %s does not exist.", directTheme)
return
}

} else if theme == "" {
theme = "Light"
}

writer.WriteHeader(200)
http.ServeFile(writer, request, "run/themes/"+theme+".css")
}

// serves the current theme that the user is using
func getTheme(writer http.ResponseWriter, request *http.Request) {
auth := getAuthFromCookies(request)

if !auth.Authed {
writer.WriteHeader(401)
httpResponsef(writer, "Could not set specified theme", "Not authenticated :(")
return
}

data := struct {
Theme string `json:"theme"`
}{
Theme: GetTheme(auth.UUID),
}

writer.Header().Add("Content-Type", "application/json")
encodeErr := json.NewEncoder(writer).Encode(data)
if encodeErr != nil {
LogErrorf(encodeErr, "Problem encoding %v", data)
} else {
writer.WriteHeader(200)
}

}

// sets the theme of the authed user
func setTheme(writer http.ResponseWriter, request *http.Request) {
auth := getAuthFromCookies(request)

if !auth.Authed {
writer.WriteHeader(401)
httpResponsef(writer, "Could not set specified theme", "Not authenticated :(")
return
}

requestBytes, err := io.ReadAll(request.Body)
if err != nil {
LogErrorf(err, "Problem reading %v", request.Body)
writer.WriteHeader(500)
return
}

var data map[string]interface{}
err = json.Unmarshal(requestBytes, &data)
if err != nil {
LogErrorf(err, "Problem unmarshalling %v", requestBytes)
writer.WriteHeader(400)
httpResponsef(writer, "Could not set specified theme", "The json body coukd not be nnmarshalled")
return
}
wantedTheme, ok := data["theme"].(string)
if !ok {
writer.WriteHeader(400)
httpResponsef(writer, "Could not set specified theme", "Missing or invalid 'theme' field")
return
}

if wantedTheme != "" {
allThemes, err := ListAllThemes()
if err != nil {
LogError(err, "Failed to fetch all themes: ")

writer.WriteHeader(500)
httpResponsef(writer, "Could not set specified theme", "An unexpected error occurred preventing validation of the theme name")
return
}

// Do the checking early when we set the theme
if slices.Contains(allThemes, wantedTheme) {
SetTheme(auth.UUID, wantedTheme)

writer.WriteHeader(200)
httpResponsef(writer, "Could set specified theme", "Successfully switched to \"%s\".", wantedTheme)
} else {
writer.WriteHeader(404)
httpResponsef(writer, "Could not set specified theme", "Theme \"%s\" does not exist.", wantedTheme)
return
}
}

}

// Serves a list of every theme
func handleThemesRequest(writer http.ResponseWriter, request *http.Request) {
allThemes, err := ListAllThemes()
if err != nil {
LogError(err, "Failed to fetch all themes: ")

writer.WriteHeader(500)
httpResponsef(writer, "Could not fetch all themes", "An unexpected error occurred preventing the reading of the theme list")
return
}

data := struct {
Themes []string `json:"themes"`
}{
Themes: allThemes,
}

writer.Header().Add("Content-Type", "application/json")
encodeErr := json.NewEncoder(writer).Encode(data)
if encodeErr != nil {
LogErrorf(encodeErr, "Problem encoding %v", allThemes)
} else {
writer.WriteHeader(200)
}
}

// Handles adding schedules to a given scouter
func addIndividualSchedule(writer http.ResponseWriter, request *http.Request) {
auth := getAuthFromCookies(request)
Expand All @@ -497,12 +648,15 @@ func addIndividualSchedule(writer http.ResponseWriter, request *http.Request) {
// Handles requests for the various leaderboards
func serveLeaderboard(writer http.ResponseWriter, request *http.Request) {
var lbType string
var typeHeader string = request.Header.Get("type")
if typeHeader == "HighScore" {

wantedType := request.Header.Get("type")

switch wantedType {
case "HighScore":
lbType = "highscore"
} else if typeHeader == "LifeScore" {
case "LifeScore":
lbType = "lifescore"
} else {
default:
lbType = "score"
}

Expand Down Expand Up @@ -559,6 +713,7 @@ func handleWithCORS(handler http.HandlerFunc, okCode bool) http.HandlerFunc {
if okCode {
w.WriteHeader(200)
}

handler(w, r)
}
}
Expand Down Expand Up @@ -831,6 +986,7 @@ type RequestAuth struct {
Username string
Role string
Authed bool
Preflight bool
}

func (a RequestAuth) IsAdmin() bool {
Expand All @@ -840,15 +996,21 @@ func (a RequestAuth) IsAdmin() bool {
func getAuthFromCookies(request *http.Request) RequestAuth {
var auth RequestAuth

// checks for preflight requests
if request.Method == http.MethodOptions {
auth.Preflight = true
return auth
}

if c, err := request.Cookie("uuid"); err == nil && c != nil {
auth.UUID = c.Value
} else if err != nil {
} else if err != nil && err != http.ErrNoCookie {
LogError(err, "error getting request cookie 'uuid'")
}

if c, err := request.Cookie("certificate"); err == nil && c != nil {
auth.Certificate = c.Value
} else if err != nil {
} else if err != nil && err != http.ErrNoCookie {
LogError(err, "error getting request cookie 'certificate'")
}

Expand Down
24 changes: 21 additions & 3 deletions internal/sheet_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import (
yaml "sigs.k8s.io/yaml/goyaml.v2"
)

var useLocalAuth bool = false

func UseLocalAuth(val bool) {
useLocalAuth = val
}

// Early methods (setup) are from google's quickstart, so I didn't change much about them

// Retrieve a token, saves the token, then returns the generated client.
Expand Down Expand Up @@ -92,10 +98,22 @@ var Srv *sheets.Service
func SetupSheetsAPI(creds []byte) {
ctx := context.Background()

client, err := google.DefaultClient(context.Background(), sheets.SpreadsheetsScope)
if err != nil {
FatalError(err, "Unable to parse client secret file to config: %v")
var client *http.Client
var err error

if useLocalAuth {
config, err := google.ConfigFromJSON(creds, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
FatalError(err, "Unable to parse client secret file to config: %v")
}
client = getClient(config)
} else {
client, err = google.DefaultClient(context.Background(), sheets.SpreadsheetsScope)
if err != nil {
FatalError(err, "Unable to parse client secret file to config: %v")
}
}

Srv, err = sheets.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
FatalError(err, "Unable to retrieve Sheets client: %v")
Expand Down
Loading
Loading