Full app, v1.0. Contains web app and cli.

This commit is contained in:
Harry Culpan 2024-01-20 21:45:53 -05:00
parent 9f310f98db
commit 9f894671b0
42 changed files with 2245 additions and 0 deletions

46
.air.toml Normal file
View file

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "areweplaying"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = [".*_templ.go", "_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "templ", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

10
.gitignore vendored
View file

@ -7,6 +7,16 @@
*.dll
*.so
*.dylib
.env
areweplaying
areweplaying.*
awp-cli
awp-cli.*
players.json
.DS_Store
# air-related stuff
tmp
# Test binary, built with `go test -c`
*.test

17
Makefile Normal file
View file

@ -0,0 +1,17 @@
all: build
build:
templ generate
go build -o areweplaying cmd/web/*.go
go build -o awp-cli cmd/cli/*.go
linuxbuild:
templ generate
GOOS=linux GOARCH=amd64 go build -o areweplaying.linux cmd/web/*.go
GOOS=linux GOARCH=amd64 go build -o awp-cli.linux cmd/cli/*.go
clean:
rm -rf areweplaying
rm -rf areweplaying.*
test:

82
cmd/cli/cmd/check.go Normal file
View file

@ -0,0 +1,82 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"log"
"strings"
"github.com/hculpan/areweplaying/pkg/data"
"github.com/hculpan/areweplaying/pkg/email"
"github.com/spf13/cobra"
)
func isAttending(body string) string {
body = strings.ToLower(body)
lines := strings.Fields(body)
if strings.Contains(lines[0], "yes") {
return "yes"
} else if strings.Contains(lines[0], "no") {
return "no"
} else {
return "unknown"
}
}
// checkCmd represents the check command
var checkCmd = &cobra.Command{
Use: "check",
Short: "Checks for new emails on player status",
Long: `Checks at culpanvtt@gmail.com for any new
emails sent by players to indicate if they
will attend the next session.`,
RunE: func(cmd *cobra.Command, args []string) error {
log.Default().Println("Checking for emails")
emails, err := email.FetchEmails()
if err != nil {
return err
}
if len(emails) == 0 {
log.Default().Println("No new emails")
} else {
session, err := data.ReadSessionData()
if err != nil {
log.Default().Println(err)
return err
}
for _, email := range emails {
if len(email.Body) > 0 {
player := session.GetPlayerByEmail(email.FromAddress)
if player != nil {
attending := isAttending(email.Body)
if attending == "yes" || attending == "no" {
player.Attending = attending
log.Default().Printf("Received player response for %s: %s", player.Name, player.Attending)
}
}
}
}
data.PersistSession(session)
}
return nil
},
}
func init() {
rootCmd.AddCommand(checkCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// checkCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// checkCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

42
cmd/cli/cmd/root.go Normal file
View file

@ -0,0 +1,42 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "awp-cli",
Short: "CLI utility for Are We Playing? web app",
Long: `CLI utility for Are We Playing? web app`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.CompletionOptions.DisableDefaultCmd = true
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

27
cmd/cli/main.go Normal file
View file

@ -0,0 +1,27 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package main
import (
"log"
"os"
"github.com/hculpan/areweplaying/cmd/cli/cmd"
"github.com/hculpan/areweplaying/pkg/utils"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
logFile := os.Getenv("CLI_LOG_FILE")
if len(logFile) > 0 {
utils.SetLogFile(logFile)
}
cmd.Execute()
}

123
cmd/cli/main.go.bak Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"fmt"
"log"
"net/smtp"
"os"
"strconv"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/joho/godotenv"
)
func main() {
var username string
var password string
var fetchServer string
var sendServer string
err := godotenv.Load()
if err != nil {
log.Fatalf("unable to load env: %s", err)
}
username = os.Getenv("USERNAME")
password = os.Getenv("PASSWORD")
fetchServer = os.Getenv("IMAP_SERVER")
sendServer = os.Getenv("SMTP_SERVER")
port := 993
if len(username) == 0 || len(password) == 0 || len(fetchServer) == 0 || len(sendServer) == 0 {
log.Fatal("missing configuration variables")
}
imapClient, err := connectToServer(username, password, fetchServer, port)
if err != nil {
log.Fatal(err)
}
defer imapClient.Logout()
if err := fetchEmails(imapClient); err != nil {
log.Fatal(err)
}
/*
to := "harry@culpan.org"
subject := "Test Email"
body := "This is a test email sent from a Go-based email client."
if err := sendEmail(username, password, sendServer, 587, to, subject, body); err != nil {
log.Fatal(err)
}
*/
}
func fetchEmails(imapClient *client.Client) error {
// Select the mailbox you want to read
_, err := imapClient.Select("INBOX", false)
if err != nil {
return err
}
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{"\\Seen"}
uids, err := imapClient.Search(criteria)
if err != nil {
log.Printf("Search error: %s\n", err)
}
if len(uids) > 0 {
// Define the range of emails to fetch
seqSet := new(imap.SeqSet)
seqSet.AddNum(uids...)
// Fetch the required message attributes
messages := make(chan *imap.Message, 10)
section := &imap.BodySectionName{}
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
go func() {
if err := imapClient.Fetch(seqSet, items, messages); err != nil {
log.Fatal("Fetch error: " + err.Error())
}
}()
for msg := range messages {
fmt.Println("Subject:", msg.Envelope.Subject)
}
}
return nil
}
func sendEmail(username, password, server string, port int, to, subject, body string) error {
auth := smtp.PlainAuth("", username, password, server)
msg := "From: " + username + "\r\n" +
"To: " + to + "\r\n" +
"Subject: " + subject + "\r\n" +
"\r\n" +
body
err := smtp.SendMail(server+":"+strconv.Itoa(port), auth, username, []string{to}, []byte(msg))
if err != nil {
return err
}
return nil
}
func connectToServer(username, password, server string, port int) (*client.Client, error) {
c, err := client.DialTLS(fmt.Sprintf("%s:%d", server, port), nil)
if err != nil {
return nil, err
}
if err := c.Login(username, password); err != nil {
return nil, err
}
return c, nil
}

68
cmd/web/main.go Normal file
View file

@ -0,0 +1,68 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-chi/chi/v5"
"github.com/hculpan/areweplaying/pkg/utils"
"github.com/joho/godotenv"
)
var appKey []byte
func main() {
var port string
err := godotenv.Load()
if err != nil {
port = "8080"
}
port = os.Getenv("PORT")
appKey = []byte(os.Getenv("APP_KEY"))
if len(appKey) == 0 {
log.Fatal("unable to load APP_KEY")
}
logFile := os.Getenv("WEB_LOG_FILE")
if len(logFile) > 0 {
utils.SetLogFile(logFile)
}
r := chi.NewRouter()
workDir, _ := os.Getwd()
filesDir := filepath.Join(workDir, "static")
FileServer(r, "/static", http.Dir(filesDir))
routes(r)
log.Default().Printf("Starting up server on port :%s\n", port)
http.ListenAndServe(fmt.Sprintf(":%s", port), r)
}
// FileServer conveniently sets up a http.FileServer handler to serve
// static files from a http.FileSystem.
func FileServer(r chi.Router, path string, root http.FileSystem) {
if strings.ContainsAny(path, "{}*") {
panic("FileServer does not permit URL parameters.")
}
if path != "/" && path[len(path)-1] != '/' {
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
path += "/"
}
path += "*"
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
rctx := chi.RouteContext(r.Context())
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
fs := http.StripPrefix(pathPrefix, http.FileServer(root))
fs.ServeHTTP(w, r)
})
}

156
cmd/web/routes.go Normal file
View file

@ -0,0 +1,156 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/golang-jwt/jwt/v4"
"github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/data"
)
func routes(r *chi.Mux) {
r.Post("/save-setup", func(w http.ResponseWriter, r *http.Request) {
saveSetupHandler(w, r)
})
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
comp := templates.MainPage()
comp.Render(context.Background(), w)
})
r.Post("/setup", func(w http.ResponseWriter, r *http.Request) {
setupHandler(w, r)
})
r.Get("/setup", func(w http.ResponseWriter, r *http.Request) {
setupHandler(w, r)
})
r.Post("/send-email", func(w http.ResponseWriter, r *http.Request) {
sendEmailHandler(w, r)
})
r.Get("/login", func(w http.ResponseWriter, r *http.Request) {
if ValidateJWTFromCookie(r, appKey) {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
}
comp := templates.Login()
comp.Render(context.Background(), w)
})
r.Get("/send-reminder", func(w http.ResponseWriter, r *http.Request) {
sendReminderHandler(w, r)
})
r.Get("/sessioninfo", func(w http.ResponseWriter, r *http.Request) {
session, dataErr := data.ReadSessionData()
if dataErr != nil {
http.Error(w, dataErr.Error(), http.StatusInternalServerError)
return
}
playerName := r.URL.Query().Get("playerName")
playerAttending := r.URL.Query().Get("playerAttending")
if len(playerName) > 0 && len(playerAttending) > 0 {
if err := updatePlayerAttending(playerName, playerAttending, session); err != nil {
log.Default().Println(err.Error())
}
}
comp := templates.PageBody(session)
comp.Render(context.Background(), w)
})
}
func ValidateJWTFromCookie(r *http.Request, key []byte) bool {
// Retrieve the JWT token from the cookie
cookie, err := r.Cookie("jwt_token")
if err != nil {
// Cookie not found
return false
}
tokenString := cookie.Value
// Parse the token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the algorithm used for the token
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, nil
}
return key, nil
})
if err != nil {
return false
}
// Check if token is valid
if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// Here you can also add more checks on claims if needed
// For example, check the issuer, subject, or expiration
return true
}
return false
}
func GenerateJWTAndStoreInCookie(w http.ResponseWriter, key []byte) error {
// Create a new token object, specifying signing method and the claims you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"exp": time.Now().Add(90 * 24 * time.Hour).Unix(), // 90 days expiration
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(key)
if err != nil {
return err
}
// Create a cookie
cookie := http.Cookie{
Name: "jwt_token",
Value: tokenString,
Expires: time.Now().Add(90 * 24 * time.Hour),
HttpOnly: true, // HttpOnly if you want to prevent access to the cookie from JavaScript
}
// Set the cookie
http.SetCookie(w, &cookie)
return nil
}
func updatePlayerAttending(name, attending string, session data.Session) error {
for i, p := range session.Players {
if p.Name == name {
switch strings.ToLower(attending) {
case "yes", "no":
session.Players[i].Attending = strings.ToLower(attending)
return data.PersistSession(session)
case "toggle":
newAttending := session.Players[i].Attending
switch newAttending {
case "unknown", "no":
newAttending = "yes"
case "yes":
newAttending = "no"
}
session.Players[i].Attending = newAttending
return data.PersistSession(session)
default:
return fmt.Errorf("unrecognized attending indicator %q", attending)
}
}
}
return fmt.Errorf("player %q not found", name)
}

View file

@ -0,0 +1,67 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/data"
)
func saveSetupHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
nextSession := r.FormValue("nextSession")
incDays := r.FormValue("incDays")
sessionStatus := r.FormValue("sessionStatus")
log.Default().Printf("sessionStatus = %s", sessionStatus)
session, dataErr := data.ReadSessionData()
if dataErr != nil {
http.Error(w, dataErr.Error(), http.StatusInternalServerError)
return
}
msg := "Save successful!"
err := updateSession(session, incDays, nextSession, sessionStatus)
if err != nil {
msg = fmt.Sprintf("Error in update: %s", err.Error())
}
comp := templates.SaveSetup(msg)
comp.Render(context.Background(), w)
}
func updateSession(session data.Session, incDays, nextSession, sessionStatus string) error {
if val, err := strconv.Atoi(incDays); err == nil {
session.IncrementDays = val
} else {
return err
}
if nextSession == "nextSession" {
if d, err := time.Parse(data.DATE_FORMAT, session.Date); err == nil {
d = d.AddDate(0, 0, session.IncrementDays)
session.Date = d.Format(data.DATE_FORMAT)
} else {
return err
}
for i, p := range session.Players {
if p.Gm {
session.Players[i].Attending = "yes"
} else {
session.Players[i].Attending = "unknown"
}
}
}
if len(sessionStatus) > 0 {
session.Status = sessionStatus
}
return data.PersistSession(session)
}

View file

@ -0,0 +1,38 @@
package main
import (
"context"
"net/http"
"strings"
"github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/email"
)
func sendEmailHandler(w http.ResponseWriter, r *http.Request) {
if !ValidateJWTFromCookie(r, appKey) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
r.ParseForm()
to := r.FormValue("to")
from := "culpanvtt@gmail.com"
subject := r.FormValue("subject")
body := r.FormValue("text")
toArray := strings.Split(to, ",")
for i := range toArray {
toArray[i] = strings.Trim(toArray[i], " \t\n\r")
}
e := email.NewEmail(toArray, from, subject, body)
// log.Default().Printf("got email: To: %s, From: %s, Subject: %q, Body: %q", to, from, subject, body)
err := email.SendEmail(e)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
comp := templates.SendEmail("Email sent!", "")
comp.Render(context.Background(), w)
}

View file

@ -0,0 +1,49 @@
package main
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/data"
)
func sendReminderHandler(w http.ResponseWriter, r *http.Request) {
if !ValidateJWTFromCookie(r, appKey) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
session, err := data.ReadSessionData()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
to := buildToList(session)
subject := fmt.Sprintf("Game Reminder for %s", session.Date)
text := fmt.Sprintf(
`This is a game reminder for %s. Please respond with a "yes" or "no" to indicate
if you plan to attend.
If you wish to check on the status of the game, you may do so at https://awp.culpanvtt.org.
Thank you,
Your friendly "Are We Playing?" automated system`, session.Date)
comp := templates.SendReminder(to, subject, text)
comp.Render(context.Background(), w)
}
func buildToList(session data.Session) string {
emails := []string{}
for _, player := range session.Players {
if len(player.Email) > 0 {
emails = append(emails, player.Email)
}
}
return strings.Join(emails, ", ")
}

31
cmd/web/setuphandler.go Normal file
View file

@ -0,0 +1,31 @@
package main
import (
"context"
"net/http"
"github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/data"
)
func setupHandler(w http.ResponseWriter, r *http.Request) {
session, dataErr := data.ReadSessionData()
if dataErr != nil {
http.Error(w, dataErr.Error(), http.StatusInternalServerError)
return
}
if !ValidateJWTFromCookie(r, appKey) {
r.ParseForm()
pword := r.FormValue("password")
if len(pword) == 0 || pword != session.Password {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
GenerateJWTAndStoreInCookie(w, appKey)
}
comp := templates.Setup(session)
comp.Render(context.Background(), w)
}

View file

@ -0,0 +1,14 @@
package templates
templ Header() {
<head>
<title>Are We Playing?</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
<script src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
</head>
}

View file

@ -0,0 +1,53 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func Header() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Are We Playing?`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC\" crossorigin=\"anonymous\"><script src=\"https://unpkg.com/htmx.org@1.9.10\" integrity=\"sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC\" crossorigin=\"anonymous\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := ``
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</script></head>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,33 @@
package templates
templ MainPage() {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container mt-4" id="session-info" hx-get="/sessioninfo" hx-swap="innerHTML" hx-trigger="load"
hx-target="#session-info">
</div>
<div class="container">
<div class="d-flex justify-content-end">
<a href="/login">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-gear text-secondary" viewBox="0 0 16 16">
<path
d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0" />
<path
d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z" />
</svg>
</a>
</div>
</div>
<script src=" https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous">
</script>
</body>
</html>
}

View file

@ -0,0 +1,53 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func MainPage() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container mt-4\" id=\"session-info\" hx-get=\"/sessioninfo\" hx-swap=\"innerHTML\" hx-trigger=\"load\" hx-target=\"#session-info\"></div><div class=\"container\"><div class=\"d-flex justify-content-end\"><a href=\"/login\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-gear text-secondary\" viewBox=\"0 0 16 16\"><path d=\"M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0\"></path> <path d=\"M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z\"></path></svg></a></div></div><script src=\" https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM\" crossorigin=\"anonymous\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `
`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,31 @@
package templates
templ Login() {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container">
<div class="row d-flex justify-content-center">
<div class="col-4">
<form action="/setup" method="POST">
<h1 class="h3 mb-3 mt-4 fw-normal">Please sign in</h1>
<div class="form-floating">
<input type="password" class="form-control mb-2" id="password" name="password"
placeholder="Password" />
<label for="floatingPassword">Password</label>
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
</form>
</div>
</div>
</div>
</body>
</html>
}

View file

@ -0,0 +1,70 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func Login() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container\"><div class=\"row d-flex justify-content-center\"><div class=\"col-4\"><form action=\"/setup\" method=\"POST\"><h1 class=\"h3 mb-3 mt-4 fw-normal\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Please sign in`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><div class=\"form-floating\"><input type=\"password\" class=\"form-control mb-2\" id=\"password\" name=\"password\" placeholder=\"Password\"> <label for=\"floatingPassword\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := `Password`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label></div><button class=\"w-100 btn btn-lg btn-primary\" type=\"submit\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := `Sign in`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button></form></div></div></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,50 @@
package templates
import (
"github.com/hculpan/areweplaying/pkg/data"
)
templ PageBody(session data.Session) {
<div class="row">
<div class="col h1 text-center">Next Session: { session.Date }</div>
</div>
<div class="row justify-content-center mt-4">
<div class="col col-lg-3">
<table class="table table-dark table-striped border border-secondary">
<thead>
<tr>
<th scope="col" class="col-1 text-center">Attending</th>
<th scope="col" class="col-2 text-center">Player</th>
</tr>
</thead>
<tbody>
for _, player := range session.Players {
<tr>
<td class="col col-lg-1 align-middle text-center" hx-get={ player.ToggleUrl } hx-swap="innerHTML"
hx-target="#session-info" hx-trigger="click">
if player.Attending == "yes" {
<img src="/static/check_green.png" width="30" height="30" />
} else if player.Attending == "no"{
<img src="/static/check_red.png" width="30" height="30" />
} else {
<img src="/static/check_grey.png" width="30" height="30" />
}
</td>
<td class="col col-lg-2 align-middle text-center" style="height: 50px;">{ player.Name }</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="row">
if session.Status == "planned" {
<div class="col h1 text-center text-success">Status: { session.Status }</div>
} else if session.Status == "canceled" {
<div class="col h1 text-center text-danger">Status: { session.Status }</div>
} else {
<div class="col h1 text-center">Status: { session.Status }</div>
}
</div>
}

View file

@ -0,0 +1,200 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"github.com/hculpan/areweplaying/pkg/data"
)
func PageBody(session data.Session) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\"><div class=\"col h1 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Next Session: `
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(session.Date)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/pagebody.templ`, Line: 8, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"row justify-content-center mt-4\"><div class=\"col col-lg-3\"><table class=\"table table-dark table-striped border border-secondary\"><thead><tr><th scope=\"col\" class=\"col-1 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := `Attending`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</th><th scope=\"col\" class=\"col-2 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var5 := `Player`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, player := range session.Players {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td class=\"col col-lg-1 align-middle text-center\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(player.ToggleUrl))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"innerHTML\" hx-target=\"#session-info\" hx-trigger=\"click\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if player.Attending == "yes" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"/static/check_green.png\" width=\"30\" height=\"30\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if player.Attending == "no" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"/static/check_red.png\" width=\"30\" height=\"30\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"/static/check_grey.png\" width=\"30\" height=\"30\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td class=\"col col-lg-2 align-middle text-center\" style=\"height: 50px;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(player.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/pagebody.templ`, Line: 32, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table></div></div><div class=\"row\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if session.Status == "planned" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col h1 text-center text-success\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var7 := `Status: `
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(session.Status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/pagebody.templ`, Line: 41, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if session.Status == "canceled" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col h1 text-center text-danger\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var9 := `Status: `
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(session.Status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/pagebody.templ`, Line: 43, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col h1 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var11 := `Status: `
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(session.Status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/pagebody.templ`, Line: 45, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,28 @@
package templates
templ SaveSetup(status string) {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container">
<div class="row">
<div class="col-3">
<a href="/" class="btn btn-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
</svg>Back to Main Page</a>
</div>
</div>
<div class="row mt-4">
<h1>{ status }</h1>
</div>
</div>
<footer class=" container">
</footer>
</body>
</html>
}

View file

@ -0,0 +1,65 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func SaveSetup(status string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container\"><div class=\"row\"><div class=\"col-3\"><a href=\"/\" class=\"btn btn-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Back to Main Page`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div><div class=\"row mt-4\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/save_setup.templ`, Line: 18, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div></div><footer class=\" container\"></footer></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,31 @@
package templates
templ SendEmail(status string, msg string) {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container">
<div class="row">
<div class="col-3">
<a href="/" class="btn btn-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
</svg>Back to Main Page</a>
</div>
</div>
<div class="row mt-4">
<h1>{ status }</h1>
</div>
<div class="row">
<p>{ msg }</p>
</div>
</div>
<footer class=" container">
</footer>
</body>
</html>
}

View file

@ -0,0 +1,78 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func SendEmail(status string, msg string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container\"><div class=\"row\"><div class=\"col-3\"><a href=\"/\" class=\"btn btn-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Back to Main Page`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div><div class=\"row mt-4\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/sendemail.templ`, Line: 18, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div><div class=\"row\"><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(msg)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/sendemail.templ`, Line: 21, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div><footer class=\" container\"></footer></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,47 @@
package templates
templ SendReminder(to string, subject string, text string) {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container">
<div class="row">
<div class="col-3">
<a href="/" class="btn btn-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
</svg>Back to Main Page</a>
</div>
</div>
<div class="row mt-4">
<h1>Send Reminder</h1>
</div>
<div class="row">
<form action="/send-email" method="post">
<div class="mb-3">
<label for="toControl" class="form-label">To</label>
<input type="text" id="toControl" name="to" value={to} />
</div>
<div class="mb-3">
<label for="subjectControl" class="form-label">Subject</label>
<input type="text" class="form-control" id="subjectControl" name="subject" value={ subject } />
</div>
<div class="mb-3">
<label for="textControl" class="form-label">Text to send</label>
<textarea class="form-control" id="subjectControl" name="text" rows="5">{ text }</textarea>
</div>
<button type="submit" class="btn btn-primary">Send Reminder</button>
</form>
</div>
</div>
<footer class=" container">
</footer>
</body>
</html>
}

View file

@ -0,0 +1,126 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func SendReminder(to string, subject string, text string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container\"><div class=\"row\"><div class=\"col-3\"><a href=\"/\" class=\"btn btn-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Back to Main Page`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div><div class=\"row mt-4\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := `Send Reminder`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div><div class=\"row\"><form action=\"/send-email\" method=\"post\"><div class=\"mb-3\"><label for=\"toControl\" class=\"form-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := `To`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <input type=\"text\" id=\"toControl\" name=\"to\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(to))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"mb-3\"><label for=\"subjectControl\" class=\"form-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var5 := `Subject`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <input type=\"text\" class=\"form-control\" id=\"subjectControl\" name=\"subject\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(subject))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"mb-3\"><label for=\"textControl\" class=\"form-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := `Text to send`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <textarea class=\"form-control\" id=\"subjectControl\" name=\"text\" rows=\"5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/sendreminder.templ`, Line: 33, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea></div><button type=\"submit\" class=\"btn btn-primary\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var8 := `Send Reminder`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button></form></div></div><footer class=\" container\"></footer></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -0,0 +1,73 @@
package templates
import (
"strconv"
"github.com/hculpan/areweplaying/pkg/data"
)
templ Setup(session data.Session) {
<html>
@Header()
<body class="bg-dark text-light">
<div class="container">
<div class="row">
<div class="col-3">
<a href="/" class="btn btn-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
</svg>Back to Main Page</a>
</div>
<div class="col-3">
<a href="/send-reminder" class="btn btn-secondary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-envelope-plus" viewBox="0 0 16 16">
<path
d="M2 2a2 2 0 0 0-2 2v8.01A2 2 0 0 0 2 14h5.5a.5.5 0 0 0 0-1H2a1 1 0 0 1-.966-.741l5.64-3.471L8 9.583l7-4.2V8.5a.5.5 0 0 0 1 0V4a2 2 0 0 0-2-2zm3.708 6.208L1 11.105V5.383zM1 4.217V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v.217l-7 4.2z" />
<path
d="M16 12.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0m-3.5-2a.5.5 0 0 0-.5.5v1h-1a.5.5 0 0 0 0 1h1v1a.5.5 0 0 0 1 0v-1h1a.5.5 0 0 0 0-1h-1v-1a.5.5 0 0 0-.5-.5" />
</svg>
Send Reminder
</a>
</div>
</div>
<div class="row mt-4">
<h1>Setup</h1>
</div>
<div class="row">
<form action="/save-setup" method="post">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="nextSession" id="nextSession"
name="nextSession" />
<label class="form-check-label" for="nextSession">Change to next session</label>
</div>
<div class="mb-3">
<label for="incrementDaysInput" class="form-label">Days to Increment</label>
<input type="number" class="form-control" id="incrementDaysInput" name="incDays" min="1"
style="max-width: 150px;" max="999" value={ strconv.Itoa(session.IncrementDays) } />
</div>
<div class="mb-3">
<label for="sessionStatus" class="form-label">Session Status</label>
<select class="form-select" aria-label="Default select example" name="sessionStatus"
style="max-width: 150px;">
<option selected></option>
<option value="planned">Planned</option>
<option value="canceled">Canceled</option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="sendEmail" id="sendEmail" name="sendEmail" />
<label class="form-check-label" for="sendEmail">Send Email</label>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
<footer class=" container">
</footer>
</body>
</html>
}

View file

@ -0,0 +1,146 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.513
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"github.com/hculpan/areweplaying/pkg/data"
"strconv"
)
func Setup(session data.Session) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body class=\"bg-dark text-light\"><div class=\"container\"><div class=\"row\"><div class=\"col-3\"><a href=\"/\" class=\"btn btn-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := `Back to Main Page`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"col-3\"><a href=\"/send-reminder\" class=\"btn btn-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-envelope-plus\" viewBox=\"0 0 16 16\"><path d=\"M2 2a2 2 0 0 0-2 2v8.01A2 2 0 0 0 2 14h5.5a.5.5 0 0 0 0-1H2a1 1 0 0 1-.966-.741l5.64-3.471L8 9.583l7-4.2V8.5a.5.5 0 0 0 1 0V4a2 2 0 0 0-2-2zm3.708 6.208L1 11.105V5.383zM1 4.217V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v.217l-7 4.2z\"></path> <path d=\"M16 12.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0m-3.5-2a.5.5 0 0 0-.5.5v1h-1a.5.5 0 0 0 0 1h1v1a.5.5 0 0 0 1 0v-1h1a.5.5 0 0 0 0-1h-1v-1a.5.5 0 0 0-.5-.5\"></path></svg> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := `Send Reminder`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div><div class=\"row mt-4\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := `Setup`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1></div><div class=\"row\"><form action=\"/save-setup\" method=\"post\"><div class=\"form-check mb-3\"><input class=\"form-check-input\" type=\"checkbox\" value=\"nextSession\" id=\"nextSession\" name=\"nextSession\"> <label class=\"form-check-label\" for=\"nextSession\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var5 := `Change to next session`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label></div><div class=\"mb-3\"><label for=\"incrementDaysInput\" class=\"form-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := `Days to Increment`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <input type=\"number\" class=\"form-control\" id=\"incrementDaysInput\" name=\"incDays\" min=\"1\" style=\"max-width: 150px;\" max=\"999\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(strconv.Itoa(session.IncrementDays)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"mb-3\"><label for=\"sessionStatus\" class=\"form-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var7 := `Session Status`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <select class=\"form-select\" aria-label=\"Default select example\" name=\"sessionStatus\" style=\"max-width: 150px;\"><option selected></option> <option value=\"planned\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var8 := `Planned`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option> <option value=\"canceled\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var9 := `Canceled`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option></select></div><div class=\"form-check mb-3\"><input class=\"form-check-input\" type=\"checkbox\" value=\"sendEmail\" id=\"sendEmail\" name=\"sendEmail\"> <label class=\"form-check-label\" for=\"sendEmail\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var10 := `Send Email`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label></div><button type=\"submit\" class=\"btn btn-primary\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var11 := `Save`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</button></form></div></div><footer class=\" container\"></footer></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

16
exmaple_players.json Normal file
View file

@ -0,0 +1,16 @@
{
"date": "Tuesday Jan 23, 2024 6:30pm",
"players": [
{
"name": "Player1",
"attending": "unknown"
},
{
"name": "Player2",
"attending": "yes"
}
],
"inc_days": 7,
"status": "planned",
"password": "ABC123"
}

22
go.mod Normal file
View file

@ -0,0 +1,22 @@
module github.com/hculpan/areweplaying
go 1.21.6
require (
github.com/a-h/templ v0.2.513
github.com/go-chi/chi/v5 v5.0.11
)
require (
github.com/emersion/go-imap v1.2.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/joho/godotenv v1.5.1
github.com/spf13/cobra v1.8.0
)
require (
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/text v0.3.7 // indirect
)

30
go.sum Normal file
View file

@ -0,0 +1,30 @@
github.com/a-h/templ v0.2.513 h1:ZmwGAOx4NYllnHy+FTpusc4+c5msoMpPIYX0Oy3dNqw=
github.com/a-h/templ v0.2.513/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

31
pkg/data/data.go Normal file
View file

@ -0,0 +1,31 @@
package data
const (
DATE_FORMAT string = "Monday Jan 2, 2006 3:04pm"
)
type Player struct {
Name string `json:"name"`
Attending string `json:"attending"`
ToggleUrl string `json:"-"`
Email string `json:"email"`
Gm bool `json:"gm"`
}
type Session struct {
Date string `json:"date"`
Players []Player `json:"players"`
IncrementDays int `json:"inc_days"`
Status string `json:"status"`
Password string `json:"password"`
}
func (s *Session) GetPlayerByEmail(addr string) *Player {
for i, p := range s.Players {
if addr == p.Email {
return &s.Players[i]
}
}
return nil
}

35
pkg/data/loaddata.go Normal file
View file

@ -0,0 +1,35 @@
package data
import (
"encoding/json"
"os"
)
func ReadSessionData() (Session, error) {
var session Session
data, err := os.ReadFile("players.json")
if err != nil {
return session, err
}
err = json.Unmarshal(data, &session)
if err == nil {
for i, p := range session.Players {
session.Players[i].ToggleUrl = "/sessioninfo?playerName=" + p.Name + "&playerAttending=toggle"
}
}
return session, err
}
func PersistSession(session Session) error {
// Convert the object to JSON
jsonData, err := json.MarshalIndent(session, "", " ")
if err != nil {
return err
}
// Write JSON data to file
err = os.WriteFile("players.json", jsonData, 0644)
return err
}

75
pkg/email/config.go Normal file
View file

@ -0,0 +1,75 @@
package email
import (
"fmt"
"os"
"github.com/emersion/go-imap/client"
"github.com/joho/godotenv"
)
const (
IMAP_SERVICE EmailService = 993
SMTP_SERVICE EmailService = 587
)
type EmailService int
type Config struct {
Username string
Password string
ImapServer string
SmtpServer string
}
func NewConfig(username, password, imapServer, smtpServer string) *Config {
return &Config{
Username: username,
Password: password,
ImapServer: imapServer,
SmtpServer: smtpServer,
}
}
func NewConfigFromEnv() (*Config, error) {
var username string
var password string
var fetchServer string
var sendServer string
err := godotenv.Load()
if err != nil {
return nil, fmt.Errorf("error loading .env file: %s", err)
}
username = os.Getenv("USERNAME")
password = os.Getenv("PASSWORD")
fetchServer = os.Getenv("IMAP_SERVER")
sendServer = os.Getenv("SMTP_SERVER")
if len(username) == 0 || len(password) == 0 || len(fetchServer) == 0 || len(sendServer) == 0 {
return nil, fmt.Errorf("missing configuration variables")
}
return NewConfig(username, password, fetchServer, sendServer), nil
}
func (c *Config) ConnectToServer(service EmailService) (*client.Client, error) {
port := int(service)
var host string
if service == SMTP_SERVICE {
host = c.SmtpServer
} else {
host = c.ImapServer
}
client, err := client.DialTLS(fmt.Sprintf("%s:%d", host, port), nil)
if err != nil {
return nil, err
}
if err := client.Login(c.Username, c.Password); err != nil {
return nil, err
}
return client, nil
}

27
pkg/email/email.go Normal file
View file

@ -0,0 +1,27 @@
package email
import "fmt"
type Email struct {
ToAddress []string
FromAddress string
Subject string
Body string
}
func NewEmail(to []string, from, subject, body string) *Email {
return &Email{
ToAddress: to,
FromAddress: from,
Subject: subject,
Body: body,
}
}
func (e *Email) String() string {
bodyLength := len(e.Body)
if bodyLength > 10 {
bodyLength = 10
}
return fmt.Sprintf("To: %s, From: %s, Subject: %s, Body: %s", e.ToAddress, e.FromAddress, e.Subject, e.Body[:bodyLength])
}

100
pkg/email/fetch.go Normal file
View file

@ -0,0 +1,100 @@
package email
import (
"fmt"
"io"
"log"
"net/mail"
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
)
func FetchEmails() ([]Email, error) {
config, err := NewConfigFromEnv()
if err != nil {
return nil, err
}
imapClient, err := config.ConnectToServer(IMAP_SERVICE)
if err != nil {
log.Fatal(err)
}
defer imapClient.Logout()
return fetchEmails(imapClient)
}
func concatAddresses(addresses []*imap.Address) string {
result := []string{}
for _, address := range addresses {
if len(address.PersonalName) > 0 {
result = append(result, fmt.Sprintf("%s <%s>", address.PersonalName, address.Address()))
} else {
result = append(result, address.Address())
}
}
return strings.Join(result, "; ")
}
func fetchEmails(imapClient *client.Client) ([]Email, error) {
result := []Email{}
// Select the mailbox you want to read
_, err := imapClient.Select("INBOX", false)
if err != nil {
return nil, err
}
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{imap.SeenFlag}
uids, err := imapClient.Search(criteria)
if err != nil {
log.Printf("Search error: %s\n", err)
}
if len(uids) > 0 {
// Define the range of emails to fetch
seqSet := new(imap.SeqSet)
seqSet.AddNum(uids...)
// Fetch the required message attributes
messages := make(chan *imap.Message, 10)
section := &imap.BodySectionName{}
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
go func() {
if err := imapClient.Fetch(seqSet, items, messages); err != nil {
log.Fatal("Fetch error: " + err.Error())
}
}()
for msg := range messages {
toAddress := concatAddresses(msg.Envelope.To)
fromAddress := msg.Envelope.From[0].Address()
r := msg.GetBody(section)
if r == nil {
return result, fmt.Errorf("server didn't returned message body")
}
m, err := mail.ReadMessage(r)
if err != nil {
return result, err
}
body, err := io.ReadAll(m.Body)
email := NewEmail(
[]string{toAddress},
fromAddress,
msg.Envelope.Subject,
string(body),
)
result = append(result, *email)
}
}
return result, nil
}

37
pkg/email/send.go Normal file
View file

@ -0,0 +1,37 @@
package email
import (
"net/smtp"
"strconv"
"strings"
)
func SendEmail(email *Email) error {
config, err := NewConfigFromEnv()
if err != nil {
return err
}
if err == nil {
err = sendEmail(config.Username, config.Password, config.SmtpServer, int(SMTP_SERVICE), email.ToAddress, email.Subject, email.Body)
}
return err
}
func sendEmail(username, password, server string, port int, to []string, subject, body string) error {
auth := smtp.PlainAuth("", username, password, server)
msg := "From: " + username + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: " + subject + "\r\n" +
"\r\n" +
body
err := smtp.SendMail(server+":"+strconv.Itoa(port), auth, username, to, []byte(msg))
if err != nil {
return err
}
return nil
}

18
pkg/utils/utils.go Normal file
View file

@ -0,0 +1,18 @@
package utils
import (
"log"
"os"
)
func SetLogFile(path string) error {
logFile, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return err
}
log.SetOutput(logFile)
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
return nil
}

BIN
static/check_green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
static/check_grey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
static/check_red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB