diff --git a/README.md b/README.md
index 0557cc9..9edfecc 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,11 @@
## Usage
```bash
-# Development Mode: also starts "npm run dev" inside "frontend/"
+# Development Mode: also starts "npm run dev" inside "_frontend/"
$ MODE=dev go run .
# Production Mode
-$ cd frontend
+$ cd _frontend
$ npm run build
$ go build
```
diff --git a/frontend/crea-partita.html b/_frontend/crea-partita.html
similarity index 100%
rename from frontend/crea-partita.html
rename to _frontend/crea-partita.html
diff --git a/frontend/game.html b/_frontend/game.html
similarity index 100%
rename from frontend/game.html
rename to _frontend/game.html
diff --git a/frontend/index.html b/_frontend/index.html
similarity index 100%
rename from frontend/index.html
rename to _frontend/index.html
diff --git a/frontend/login.html b/_frontend/login.html
similarity index 100%
rename from frontend/login.html
rename to _frontend/login.html
diff --git a/frontend/package-lock.json b/_frontend/package-lock.json
similarity index 100%
rename from frontend/package-lock.json
rename to _frontend/package-lock.json
diff --git a/frontend/package.json b/_frontend/package.json
similarity index 100%
rename from frontend/package.json
rename to _frontend/package.json
diff --git a/frontend/profile.html b/_frontend/profile.html
similarity index 100%
rename from frontend/profile.html
rename to _frontend/profile.html
diff --git a/frontend/register.html b/_frontend/register.html
similarity index 100%
rename from frontend/register.html
rename to _frontend/register.html
diff --git a/_frontend/src/crea-partita/main.jsx b/_frontend/src/crea-partita/main.jsx
new file mode 100644
index 0000000..e5ccf8d
--- /dev/null
+++ b/_frontend/src/crea-partita/main.jsx
@@ -0,0 +1,144 @@
+import { render } from 'preact'
+import { useState, useEffect } from 'preact/hooks'
+
+const InputNumero = ({ name, id, value, setValue }) => (
+
+ setValue(e.target.value)}
+ />
+ setValue(value => value + 1)}>
+ add
+
+ setValue(value => value - 1)}>
+ remove
+
+
+)
+
+const App = () => {
+ const [user, setUser] = useState(null)
+
+ useEffect(() => {
+ fetch('/api/user')
+ .then(res => {
+ if (res.ok) {
+ return res.json()
+ }
+ })
+ .then(user => setUser(user))
+ }, [])
+
+ const [numeroGiocatori, setNumeroGiocatori] = useState(10)
+
+ const [numLupi, setNumLupi] = useState(2)
+ const [numFattucchiere, setNumFattucchiere] = useState(1)
+ const [numGuardie, setNumGuardie] = useState(1)
+ const [numCacciatori, setNumCacciatori] = useState(1)
+ const [numMedium, setNumMedium] = useState(1)
+ const [numVeggenti, setNumVeggenti] = useState(1)
+
+ const [tooManyRuoli, setTooManyRuoli] = useState(false)
+
+ useEffect(() => {
+ const totalNumGiocatoriConRuolo =
+ numLupi + numFattucchiere + numGuardie + numCacciatori + numMedium + numVeggenti
+
+ console.log(totalNumGiocatoriConRuolo)
+
+ setTooManyRuoli(numeroGiocatori < totalNumGiocatoriConRuolo)
+ }, [numLupi, numFattucchiere, numGuardie, numCacciatori, numMedium, numVeggenti])
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+render( , document.querySelector('main'))
diff --git a/frontend/src/home/index.js b/_frontend/src/home/index.js
similarity index 100%
rename from frontend/src/home/index.js
rename to _frontend/src/home/index.js
diff --git a/frontend/src/main.scss b/_frontend/src/main.scss
similarity index 89%
rename from frontend/src/main.scss
rename to _frontend/src/main.scss
index ee1fd47..f6a9ad0 100644
--- a/frontend/src/main.scss
+++ b/_frontend/src/main.scss
@@ -126,6 +126,14 @@ form {
grid-template-columns: auto 1fr;
gap: 1rem 0.5rem;
+ .compact {
+ grid-column: span 2;
+
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 0.5rem;
+ }
+
max-width: 35rem;
width: 100%;
@@ -137,7 +145,9 @@ form {
}
label {
+ align-self: center;
justify-self: end;
+
font-weight: var(--ft-sans-wt-bold);
}
@@ -145,13 +155,32 @@ form {
justify-self: end;
}
- & > :last-child {
- margin-top: 1rem;
+ .info {
+ grid-column: span 2;
+ display: flex;
+
+ align-items: start;
+
+ gap: 0.25rem;
+
+ .description {
+ width: 100%;
+ }
}
@media screen and (max-width: 512px) {
grid-template-columns: 1fr;
- gap: 0.25rem;
+
+ .compact {
+ grid-column: span 1;
+ grid-template-columns: 1fr;
+
+ gap: 0.25rem;
+ }
+
+ .info {
+ grid-column: span 1;
+ }
.fill-row {
grid-column: span 1;
@@ -161,8 +190,6 @@ form {
label {
justify-self: start;
align-self: end;
-
- padding-top: 0.5rem;
}
button[type='submit'] {
diff --git a/frontend/vite.config.js b/_frontend/vite.config.js
similarity index 100%
rename from frontend/vite.config.js
rename to _frontend/vite.config.js
diff --git a/auth/auth.go b/auth/auth.go
new file mode 100644
index 0000000..03aa0ce
--- /dev/null
+++ b/auth/auth.go
@@ -0,0 +1,17 @@
+package auth
+
+import (
+ "github.com/aziis98/lupus-lite/database"
+ "github.com/aziis98/lupus-lite/model"
+)
+
+type AuthService interface {
+ Register(username, password string) error
+ Login(username, password string) (string, error)
+ UserForSession(token string) (string, error)
+ GetUser(username string) (model.User, error)
+}
+
+func NewInMemoryAuthService(db database.Database) AuthService {
+ return &memAuth{db, map[string]string{}}
+}
diff --git a/auth/inmemory.go b/auth/inmemory.go
new file mode 100644
index 0000000..7f733d6
--- /dev/null
+++ b/auth/inmemory.go
@@ -0,0 +1,57 @@
+package auth
+
+import (
+ "fmt"
+
+ "github.com/aziis98/lupus-lite/database"
+ "github.com/aziis98/lupus-lite/model"
+ "github.com/aziis98/lupus-lite/util"
+ "golang.org/x/crypto/bcrypt"
+)
+
+type memAuth struct {
+ db database.Database
+
+ sessions map[string]string
+}
+
+func (auth *memAuth) Register(username, password string) error {
+ passwordBCrypt, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return err
+ }
+
+ return auth.db.CreateUser(model.User{
+ Username: username,
+ PasswordBCrypt: passwordBCrypt,
+ })
+}
+
+func (auth *memAuth) Login(username, password string) (string, error) {
+ user, err := auth.db.GetUser(username)
+ if err != nil {
+ return "", err
+ }
+
+ if err := bcrypt.CompareHashAndPassword(user.PasswordBCrypt, []byte(password)); err != nil {
+ return "", err
+ }
+
+ token := util.GenerateRandomString(16)
+ auth.sessions[token] = username
+
+ return token, nil
+}
+
+func (auth *memAuth) UserForSession(token string) (string, error) {
+ username, ok := auth.sessions[token]
+ if !ok {
+ return "", fmt.Errorf(`invalid session token`)
+ }
+
+ return username, nil
+}
+
+func (auth *memAuth) GetUser(username string) (model.User, error) {
+ return auth.db.GetUser(username)
+}
diff --git a/database.go b/database.go
index 0e0aff6..06ab7d0 100644
--- a/database.go
+++ b/database.go
@@ -1,136 +1 @@
package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
-
- "golang.org/x/crypto/bcrypt"
-)
-
-// PublicUser is the "public" version of the "User" struct, this excludes private information
-type PublicUser struct {
- Username string `json:"username"`
-}
-
-// User represents a user in the database
-type User struct {
- Username string `json:"username"`
- PasswordBCrypt []byte `json:"passwordBCrypt"`
-}
-
-func (u User) PublicUser() PublicUser {
- return PublicUser{
- Username: u.Username,
- }
-}
-
-type Database interface {
- GetUsers() ([]User, error)
- GetUser(username string) (User, error)
- CreateUser(user User) error
-}
-
-type memDB struct {
- Users map[string]User `json:"users"`
-}
-
-func NewInMemoryDB() (Database, error) {
- var db memDB
-
- exampleFile, err := os.Open("example-db.local.json")
- if err != nil {
- return nil, err
- }
-
- if err := json.NewDecoder(exampleFile).Decode(&db); err != nil {
- return nil, err
- }
-
- return &db, nil
-}
-
-func (db *memDB) GetUsers() ([]User, error) {
- users := make([]User, 0, len(db.Users))
- for _, u := range db.Users {
- users = append(users, u)
- }
-
- return users, nil
-}
-
-func (db *memDB) GetUser(username string) (User, error) {
- user, ok := db.Users[username]
- if !ok {
- return User{}, fmt.Errorf(`no user with username %q`, username)
- }
-
- return user, nil
-}
-
-func (db *memDB) CreateUser(user User) error {
- if _, ok := db.Users[user.Username]; ok {
- return fmt.Errorf(`user with username %q already exists`, user.Username)
- }
-
- db.Users[user.Username] = user
- return nil
-}
-
-type Auth interface {
- Register(username, password string) error
- Login(username, password string) (string, error)
- UserForSession(token string) (string, error)
- GetUser(username string) (User, error)
-}
-
-type memAuth struct {
- db Database
-
- sessions map[string]string
-}
-
-func NewInMemoryAuth(db Database) Auth {
- return &memAuth{db, map[string]string{}}
-}
-
-func (auth *memAuth) Register(username, password string) error {
- passwordBCrypt, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- if err != nil {
- return err
- }
-
- return auth.db.CreateUser(User{
- Username: username,
- PasswordBCrypt: passwordBCrypt,
- })
-}
-
-func (auth *memAuth) Login(username, password string) (string, error) {
- user, err := auth.db.GetUser(username)
- if err != nil {
- return "", err
- }
-
- if err := bcrypt.CompareHashAndPassword(user.PasswordBCrypt, []byte(password)); err != nil {
- return "", err
- }
-
- token := GenerateRandomString(16)
- auth.sessions[token] = username
-
- return token, nil
-}
-
-func (auth *memAuth) UserForSession(token string) (string, error) {
- username, ok := auth.sessions[token]
- if !ok {
- return "", fmt.Errorf(`invalid session token`)
- }
-
- return username, nil
-}
-
-func (auth *memAuth) GetUser(username string) (User, error) {
- return auth.db.GetUser(username)
-}
diff --git a/database/database.go b/database/database.go
new file mode 100644
index 0000000..b375617
--- /dev/null
+++ b/database/database.go
@@ -0,0 +1,89 @@
+package database
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/aziis98/lupus-lite/model"
+ "github.com/aziis98/lupus-lite/util"
+)
+
+type Database interface {
+ CreateUser(user model.User) error
+ GetUsers() ([]model.User, error)
+ GetUser(username string) (model.User, error)
+
+ CreatePartita(partitaConfig model.PartitaConfig) (model.Partita, error)
+ GetPartita(uid string) (model.Partita, error)
+}
+
+type memDB struct {
+ Users map[string]model.User `json:"users"`
+ Partite map[string]model.Partita `json:"partite"`
+}
+
+func NewInMemoryDB() (Database, error) {
+ var db memDB
+
+ exampleFile, err := os.Open("example-db.local.json")
+ if err != nil {
+ return nil, err
+ }
+
+ if err := json.NewDecoder(exampleFile).Decode(&db); err != nil {
+ return nil, err
+ }
+
+ return &db, nil
+}
+
+func (db *memDB) GetUsers() ([]model.User, error) {
+ users := make([]model.User, 0, len(db.Users))
+ for _, u := range db.Users {
+ users = append(users, u)
+ }
+
+ return users, nil
+}
+
+func (db *memDB) GetUser(username string) (model.User, error) {
+ user, ok := db.Users[username]
+ if !ok {
+ return model.User{}, fmt.Errorf(`no user with username %q`, username)
+ }
+
+ return user, nil
+}
+
+func (db *memDB) CreateUser(user model.User) error {
+ if _, ok := db.Users[user.Username]; ok {
+ return fmt.Errorf(`user with username %q already exists`, user.Username)
+ }
+
+ db.Users[user.Username] = user
+ return nil
+}
+
+func (db *memDB) CreatePartita(partitaConfig model.PartitaConfig) (model.Partita, error) {
+ uid := util.GenerateRandomString(12)
+ partita := model.Partita{
+ Uid: uid,
+ Config: partitaConfig,
+ Players: []string{},
+ Iniziata: false,
+ }
+
+ db.Partite[uid] = partita
+
+ return partita, nil
+}
+
+func (db *memDB) GetPartita(uid string) (model.Partita, error) {
+ partita, ok := db.Partite[uid]
+ if !ok {
+ return model.Partita{}, fmt.Errorf(`nessuna partita con uid %q`, uid)
+ }
+
+ return partita, nil
+}
diff --git a/frontend/src/crea-partita/main.jsx b/frontend/src/crea-partita/main.jsx
deleted file mode 100644
index 273f204..0000000
--- a/frontend/src/crea-partita/main.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { render } from 'preact'
-import { useState, useEffect } from 'preact/hooks'
-
-const InputNumero = ({ name, id, value, setValue }) => (
-
- setValue(e.target.value)}
- />
- setValue(value => value + 1)}>
- add
-
- setValue(value => value - 1)}>
- remove
-
-
-)
-
-const App = () => {
- const [user, setUser] = useState(null)
- useEffect(() => {
- fetch('/api/user')
- .then(res => {
- if (res.ok) {
- return res.json()
- }
- })
- .then(user => setUser(user))
- })
-
- const [numeroGiocatori, setNumeroGiocatori] = useState(10)
- const [numLupi, setNumLupi] = useState(2)
- const [numFattucchiere, setNumFattucchiere] = useState(1)
-
- return (
- <>
-
-
-
- >
- )
-}
-
-render( , document.querySelector('main'))
diff --git a/lupus/lupus.go b/lupus/lupus.go
new file mode 100644
index 0000000..6034008
--- /dev/null
+++ b/lupus/lupus.go
@@ -0,0 +1,103 @@
+package lupus
+
+// StatoPartita rappresenta lo stato della partita
+type StatoPartita struct {
+ Uid string `json:"uid"` // Uid corrispondente come in "Partita"
+
+ Players []Player `json:"players"` // Players è una mappa da username a giocatore
+ Time uint `json:"time"` // Time indica la fase corrente del gioco (la parità indica notte/giorno e si inizia da "Notte 0")
+ Actions []Action `json:"actions"` // PhaseActions indica quali azioni sono state fatte in una certa fase
+}
+
+type Player struct {
+ Username string `json:"username"`
+ Ruolo Ruolo `json:"ruolo"`
+ Vivo bool `json:"vivo"`
+}
+
+type Ruolo struct {
+ Uid string `json:"uid"`
+ Nome string `json:"nome"`
+ Fazione string `json:"fazione"`
+ Aura string `json:"aura"`
+}
+
+type Action struct {
+ Uid string `json:"uid"`
+ Time uint `json:"time"` // Time indica in quale fase è stata compiuta l'azione
+ Player string `json:"player"`
+ TargetPlayer string `json:"targetPlayer"`
+}
+
+var (
+ AuraBianca = "bianca"
+ AuraNera = "nera"
+)
+
+var (
+ FazioneBuoni = "buoni"
+ FazioneCattivi = "cattivi"
+)
+
+var (
+ Contadino = Ruolo{
+ Uid: "contadino",
+ Nome: "Contadino",
+ Fazione: FazioneBuoni,
+ Aura: AuraBianca,
+ }
+ Lupo = Ruolo{
+ Uid: "lupo",
+ Nome: "Lupo",
+ Fazione: FazioneCattivi,
+ Aura: AuraNera,
+ }
+ Fattucchiera = Ruolo{
+ Uid: "fattucchiera",
+ Nome: "Fattucchiera",
+ Fazione: FazioneCattivi,
+ Aura: AuraNera,
+ }
+ Indemoniato = Ruolo{
+ Uid: "indemoniato",
+ Nome: "Indemoniato",
+ Fazione: FazioneCattivi,
+ Aura: AuraBianca,
+ }
+ Guardia = Ruolo{
+ Uid: "guardia",
+ Nome: "Guardia",
+ Fazione: FazioneBuoni,
+ Aura: AuraBianca,
+ }
+ Cacciatore = Ruolo{
+ Uid: "cacciatore",
+ Nome: "Cacciatore",
+ Fazione: FazioneBuoni,
+ Aura: AuraBianca,
+ }
+ Medium = Ruolo{
+ Uid: "medium",
+ Nome: "Medium",
+ Fazione: FazioneBuoni,
+ Aura: AuraBianca,
+ }
+ Veggente = Ruolo{
+ Uid: "veggente",
+ Nome: "Veggente",
+ Fazione: FazioneBuoni,
+ Aura: AuraBianca,
+ }
+)
+
+// Ruoli è una lista di ruoli comuni, l'ordine è puramente casuale
+var Ruoli = []Ruolo{
+ Contadino,
+ Lupo,
+ Fattucchiera,
+ Indemoniato,
+ Guardia,
+ Cacciatore,
+ Medium,
+ Veggente,
+}
diff --git a/main.go b/main.go
index 4652bd6..92511e6 100644
--- a/main.go
+++ b/main.go
@@ -6,6 +6,7 @@ import (
"os/exec"
"strings"
+ "github.com/aziis98/lupus-lite/routes"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
@@ -19,15 +20,15 @@ func main() {
app.Use(recover.New())
// Static files
- app.Static("/", "./frontend/dist")
+ app.Static("/", "./_frontend/dist")
// Api routes
- app.Route("/api", mountApiRoutes)
+ app.Route("/api", routes.Api)
if strings.HasPrefix(mode, "dev") {
log.Printf(`Running dev server for frontend: "npm run dev"`)
- err := (exec.Command("sh", "-c", "cd frontend/ && npm run dev").Start())
+ err := (exec.Command("sh", "-c", "cd _frontend/ && npm run dev").Start())
if err != nil {
log.Fatal(err)
}
diff --git a/model/model.go b/model/model.go
new file mode 100644
index 0000000..ff86512
--- /dev/null
+++ b/model/model.go
@@ -0,0 +1,33 @@
+package model
+
+// PublicUser is the "public" version of the "User" struct, this excludes private information
+type PublicUser struct {
+ Username string `json:"username"`
+}
+
+// User represents a user in the database
+type User struct {
+ Username string `json:"username"`
+ PasswordBCrypt []byte `json:"passwordBCrypt"`
+}
+
+func (u User) PublicUser() PublicUser {
+ return PublicUser{
+ Username: u.Username,
+ }
+}
+
+type PartitaConfig struct {
+ // NumeroGiocatori indica il numero totale di giocatori (per capire quanto tutti sono entrati nella partita)
+ NumeroGiocatori int
+
+ // NumeroPerRuolo indica quanti giocatori di un certo ruolo ci dovrebbero essere
+ NumeroPerRuolo map[string]int
+}
+
+type Partita struct {
+ Uid string // Uid è il codice identificativo della partita.
+ Config PartitaConfig // Config è la configurazione iniziale della partita.
+ Players []string // Players è la lista di giocatori che sono entrati in questa partita.
+ Iniziata bool // Iniziata indica se la partita è iniziata, quando lo è c'è una tabella corrispondente StatoPartita con le informazioni sui vari giocatori e sullo stato attuale del gioco.
+}
diff --git a/routes.go b/routes.go
index 667123b..06ab7d0 100644
--- a/routes.go
+++ b/routes.go
@@ -1,126 +1 @@
package main
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "time"
-
- "github.com/gofiber/fiber/v2"
-)
-
-const UserKey = "github.com/aziis98/lupus-lite/user"
-
-func requestUser(c *fiber.Ctx) *User {
- return c.Locals(UserKey).(*User)
-}
-
-func RequireLoggedMiddleware(auth Auth) fiber.Handler {
- return func(c *fiber.Ctx) error {
- token := c.Cookies("sid")
- if token == "" {
- return fmt.Errorf(`request has no session token`)
- }
-
- username, err := auth.UserForSession(token)
- if err != nil {
- return err
- }
-
- user, err := auth.GetUser(username)
- if err != nil {
- return err
- }
-
- c.Locals(UserKey, &user)
-
- return c.Next()
- }
-}
-
-func mountApiRoutes(api fiber.Router) {
- db, err := NewInMemoryDB()
- if err != nil {
- log.Fatal(err)
- }
-
- auth := NewInMemoryAuth(db)
- requireLogged := RequireLoggedMiddleware(auth)
-
- api.Get("/status", func(c *fiber.Ctx) error {
- s, err := json.MarshalIndent(db, "", " ")
- if err != nil {
- return err
- }
-
- log.Println(string(s))
-
- return c.SendString("ok")
- })
-
- api.Post("/login", func(c *fiber.Ctx) error {
- var loginForm struct {
- Username string `form:"username"`
- Password string `form:"password"`
- }
-
- if err := c.BodyParser(&loginForm); err != nil {
- return err
- }
-
- token, err := auth.Login(loginForm.Username, loginForm.Password)
- if err != nil {
- return err
- }
-
- c.Cookie(&fiber.Cookie{
- Name: "sid",
- Value: token,
- Path: "/",
- Expires: time.Now().Add(3 * 24 * time.Hour),
- })
-
- return c.Redirect("/")
- })
-
- api.Post("/logout", func(c *fiber.Ctx) error {
- c.Cookie(&fiber.Cookie{
- Name: "sid",
- Value: "",
- Path: "/",
- Expires: time.Now(),
- })
-
- return c.SendString("ok")
- })
-
- api.Post("/register", func(c *fiber.Ctx) error {
- var loginForm struct {
- Username string `form:"username"`
- Password string `form:"password"`
- Password2 string `form:"password2"`
- }
-
- if err := c.BodyParser(&loginForm); err != nil {
- return err
- }
-
- if err := validateUsername(loginForm.Username); err != nil {
- return err
- }
- if err := validatePasswords(loginForm.Password, loginForm.Password2); err != nil {
- return err
- }
-
- if err := auth.Register(loginForm.Username, loginForm.Password); err != nil {
- return err
- }
-
- return c.Redirect("/login")
- })
-
- api.Get("/user", requireLogged, func(c *fiber.Ctx) error {
- return c.JSON(requestUser(c).PublicUser())
- })
-
-}
diff --git a/routes/api.go b/routes/api.go
new file mode 100644
index 0000000..2a1df8f
--- /dev/null
+++ b/routes/api.go
@@ -0,0 +1,100 @@
+package routes
+
+import (
+ "encoding/json"
+ "log"
+ "time"
+
+ "github.com/aziis98/lupus-lite/auth"
+ "github.com/aziis98/lupus-lite/database"
+ "github.com/aziis98/lupus-lite/util"
+ "github.com/gofiber/fiber/v2"
+)
+
+func Api(api fiber.Router) {
+ db, err := database.NewInMemoryDB()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ auth := auth.NewInMemoryAuthService(db)
+ requireLogged := RequireLoggedMiddleware(auth)
+
+ api.Get("/status", func(c *fiber.Ctx) error {
+ s, err := json.MarshalIndent(db, "", " ")
+ if err != nil {
+ return err
+ }
+
+ log.Println(string(s))
+
+ return c.SendString("ok")
+ })
+
+ api.Post("/login", func(c *fiber.Ctx) error {
+ var loginForm struct {
+ Username string `form:"username"`
+ Password string `form:"password"`
+ }
+
+ if err := c.BodyParser(&loginForm); err != nil {
+ return err
+ }
+
+ token, err := auth.Login(loginForm.Username, loginForm.Password)
+ if err != nil {
+ return err
+ }
+
+ c.Cookie(&fiber.Cookie{
+ Name: "sid",
+ Value: token,
+ Path: "/",
+ Expires: time.Now().Add(3 * 24 * time.Hour),
+ })
+
+ return c.Redirect("/")
+ })
+
+ api.Post("/logout", func(c *fiber.Ctx) error {
+ c.Cookie(&fiber.Cookie{
+ Name: "sid",
+ Value: "",
+ Path: "/",
+ Expires: time.Now(),
+ })
+
+ return c.SendString("ok")
+ })
+
+ api.Post("/register", func(c *fiber.Ctx) error {
+ var loginForm struct {
+ Username string `form:"username"`
+ Password string `form:"password"`
+ Password2 string `form:"password2"`
+ }
+
+ if err := c.BodyParser(&loginForm); err != nil {
+ return err
+ }
+
+ if err := util.ValidateUsername(loginForm.Username); err != nil {
+ return err
+ }
+
+ if err := util.ValidatePasswords(loginForm.Password, loginForm.Password2); err != nil {
+ return err
+ }
+
+ if err := auth.Register(loginForm.Username, loginForm.Password); err != nil {
+ return err
+ }
+
+ return c.Redirect("/login")
+ })
+
+ api.Get("/user", requireLogged, func(c *fiber.Ctx) error {
+ return c.JSON(requestUser(c).PublicUser())
+ })
+
+}
diff --git a/routes/auth.go b/routes/auth.go
new file mode 100644
index 0000000..015ca4c
--- /dev/null
+++ b/routes/auth.go
@@ -0,0 +1,38 @@
+package routes
+
+import (
+ "fmt"
+
+ "github.com/aziis98/lupus-lite/auth"
+ "github.com/aziis98/lupus-lite/model"
+ "github.com/gofiber/fiber/v2"
+)
+
+const UserKey = "github.com/aziis98/lupus-lite/user"
+
+func requestUser(c *fiber.Ctx) *model.User {
+ return c.Locals(UserKey).(*model.User)
+}
+
+func RequireLoggedMiddleware(auth auth.AuthService) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ token := c.Cookies("sid")
+ if token == "" {
+ return fmt.Errorf(`request has no session token`)
+ }
+
+ username, err := auth.UserForSession(token)
+ if err != nil {
+ return err
+ }
+
+ user, err := auth.GetUser(username)
+ if err != nil {
+ return err
+ }
+
+ c.Locals(UserKey, &user)
+
+ return c.Next()
+ }
+}
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..07a8108
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,33 @@
+package util
+
+import (
+ "math/rand"
+ "time"
+ "unicode"
+)
+
+const alphabet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+func init() {
+ rand.Seed(time.Now().UnixNano())
+}
+
+func GenerateRandomString(n int) string {
+ b := make([]byte, n)
+
+ for i := range b {
+ b[i] = alphabet[rand.Intn(len(alphabet))]
+ }
+
+ return string(b)
+}
+
+func IsWhitespaceFree(s string) bool {
+ for _, r := range s {
+ if unicode.IsSpace(r) {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/validate.go b/util/validate.go
similarity index 75%
rename from validate.go
rename to util/validate.go
index b0efed2..8c592e4 100644
--- a/validate.go
+++ b/util/validate.go
@@ -1,8 +1,10 @@
-package main
+package util
-import "fmt"
+import (
+ "fmt"
+)
-func validateUsername(username string) error {
+func ValidateUsername(username string) error {
if !IsWhitespaceFree(username) {
return fmt.Errorf(`username cannot contain spaces`)
}
@@ -15,7 +17,7 @@ func validateUsername(username string) error {
return nil
}
-func validatePasswords(password, password2 string) error {
+func ValidatePasswords(password, password2 string) error {
if password != password2 {
return fmt.Errorf(`the two password fields don't match`)
}
diff --git a/utils.go b/utils.go
index 46d5cff..06ab7d0 100644
--- a/utils.go
+++ b/utils.go
@@ -1,33 +1 @@
package main
-
-import (
- "math/rand"
- "time"
- "unicode"
-)
-
-const alphabet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-
-func init() {
- rand.Seed(time.Now().UnixNano())
-}
-
-func GenerateRandomString(n int) string {
- b := make([]byte, n)
-
- for i := range b {
- b[i] = alphabet[rand.Intn(len(alphabet))]
- }
-
- return string(b)
-}
-
-func IsWhitespaceFree(s string) bool {
- for _, r := range s {
- if unicode.IsSpace(r) {
- return false
- }
- }
-
- return true
-}