diff --git a/.gitignore b/.gitignore index be8754a..ea4d9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ foo.db.bak out foo.db *.kak* +.air.toml diff --git a/TODO.md b/TODO.md index 2277257..d6d72d7 100644 --- a/TODO.md +++ b/TODO.md @@ -30,7 +30,7 @@ - [x] Authentication System - [x] Persistent logins across server restarts - [x] User login - - [ ] User logout + - [x] User logout - [ ] Role-based access (admin, staff) - [x] Password hashing - [ ] CSRF Protection diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 3dc20b6..10dce25 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -43,6 +43,7 @@ func (a *Authenticator) Middleware(in http.Handler) http.Handler { handler := http.NewServeMux() handler.HandleFunc("GET /login", getLogin) handler.HandleFunc("POST /login", a.login) + handler.HandleFunc("GET /logout", a.logout) handler.Handle("/", a.ensureAuth(in)) return handler } diff --git a/internal/auth/session.go b/internal/auth/session.go index f4afa1c..6add9dc 100644 --- a/internal/auth/session.go +++ b/internal/auth/session.go @@ -50,3 +50,26 @@ func (a *Authenticator) login(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &cookie) http.Redirect(w, r, "/", http.StatusFound) } + +func (a *Authenticator) logout(w http.ResponseWriter, r *http.Request) { + var cookie http.Cookie + cookie.Name = "token" + cookie.Value = "nil" + cookie.HttpOnly = true + cookie.SameSite = http.SameSiteStrictMode + cookie.Path = "/" + http.SetCookie(w, &cookie) + http.Redirect(w, r, "/", http.StatusFound) + c, err := r.Cookie("token") + if err != nil { + return + } + _, ok := a.sessions[c.Value] + if !ok { + return + } + fmt.Println("removing session") + delete(a.sessions, c.Value) + err = a.s.RevokeSession(c.Value) + fmt.Println(err) +} diff --git a/internal/models/data.go b/internal/models/data.go index 9b400c7..48eafcb 100644 --- a/internal/models/data.go +++ b/internal/models/data.go @@ -31,3 +31,7 @@ func (o *Order) Total() int { } return out } + +func (c *Cake) Total() int { + return c.Amount * c.Price +} diff --git a/internal/server/get.go b/internal/server/get.go index c3300c0..8d8449a 100644 --- a/internal/server/get.go +++ b/internal/server/get.go @@ -10,13 +10,26 @@ import ( "github.com/Bronku/iroon/internal/models" ) +func monthInterval(y int, m time.Month) (firstDay, lastDay time.Time) { + firstDay = time.Date(y, m, 1, 0, 0, 0, 0, time.UTC) + lastDay = time.Date(y, m+1, 1, 0, 0, 0, -1, time.UTC) + return firstDay, lastDay +} + func (h *Server) orders(_ *http.Request) (any, int, error) { - //r.URL.Query().Get() - orders, err := h.s.GetFilteredOrder("", time.Now(), time.Now().Add(time.Hour*24)) + var ( + y int + m time.Month + ) + y, m, _ = time.Now().Date() + first, last := monthInterval(y, m) + fmt.Println(first, last) + orders, err := h.s.GetFilteredOrder("", first, last) data := struct { - Today string + First string + Last string Orders []models.Order - }{Today: time.Now().Format("2006-01-02"), Orders: orders} + }{first.Format("2006-01-02"), last.Format("2006-01-02"), orders} return data, http.StatusOK, err } diff --git a/internal/server/static/style.css b/internal/server/static/style.css new file mode 100644 index 0000000..0b684f3 --- /dev/null +++ b/internal/server/static/style.css @@ -0,0 +1,336 @@ +:root { + --primary-color: #ff6b6b; + --primary-light: #ffeded; + --primary-dark: #e64c4c; + --text-color: #333333; + --text-light: #666666; + --background-color: #f8f9fa; + --white: #ffffff; + --border-color: #e4e9ec; + --hover-color: #f5f5f5; + --shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + --bottom-shadow: 0 4px 2px -2px rgba(0, 0, 0, 0.1); + --font-main: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + --transition: 0.2s ease; + --radius: 6px; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-main); + color: var(--text-color); + background-color: var(--background-color); + display: flex; + min-height: 100vh; + width: 100%; + line-height: 1.6; +} + +/* Sidebar */ +aside { + min-width: 4.5rem; + max-width: 4.5rem; + background-color: var(--primary-color); + height: 100vh; + position: fixed; + left: 0; + top: 0; + border-right: 1px solid var(--border-color); + box-shadow: var(--shadow); + z-index: 10; +} + +nav { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 2rem; + gap: 1.5rem; + height: 100%; +} + +nav>a { + color: var(--white); + text-decoration: none; + font-size: 1.5rem; + width: 3rem; + height: 3rem; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + transition: background-color var(--transition); + position: relative; +} + +nav>a:hover { + background-color: var(--primary-dark); +} + +nav>a:last-child { + margin-top: auto; + margin-bottom: 2rem; +} + +nav>a>i { + text-decoration: none; + color: var(--white); +} + +/* Tooltip for sidebar icons */ +nav>a::after { + content: attr(data-title); + position: absolute; + left: 120%; + top: 50%; + transform: translateY(-50%); + background-color: var(--text-color); + color: var(--white); + padding: 0.3rem 0.8rem; + border-radius: var(--radius); + font-size: 0.8rem; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity var(--transition); + z-index: 1; +} + +nav>a:hover::after { + opacity: 1; +} + +/* Main content area */ +.app-body { + flex: 1; + margin-left: 4.5rem; + padding: 0; + display: flex; + flex-direction: column; + min-height: 100vh; + overflow-x: hidden; +} + +header { + background-color: var(--white); + border-bottom: 1px solid var(--border-color); + padding: 1rem 2rem; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + position: sticky; + top: 0; + z-index: 5; +} + +h1 { + font-size: 1.8rem; + font-weight: 600; + color: var(--primary-color); +} + +main { + flex: 1; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Search form */ +.search-container { + background-color: var(--white); + border-radius: var(--radius); + padding: 1.25rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: flex-end; + box-shadow: var(--shadow); +} + +.search-box { + flex: 1; + min-width: 250px; + position: relative; +} + +.search-box input { + width: 100%; + padding: 0.75rem 1rem 0.75rem 2.5rem; + border: 1px solid var(--border-color); + border-radius: var(--radius); + font-size: 1rem; + transition: border-color var(--transition); +} + +.search-box::before { + content: "馃攳"; + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-light); +} + +.search-container label:not(.search-box) { + display: flex; + flex-direction: column; + gap: 0.5rem; + font-size: 0.9rem; + color: var(--text-light); +} + +.search-container input[type="date"] { + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: var(--radius); + min-width: 160px; +} + +.search-container input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.1); +} + +/* Table styling */ +#results-table { + background-color: var(--white); + border-radius: var(--radius); + box-shadow: var(--shadow); + /* overflow: hidden; */ + margin-top: 1rem; +} + +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + font-size: 0.95rem; +} + +thead { + border-bottom: 1px solid var(--border-color); + position: relative; +} + +tbody th { + font-weight: 300; + color: var(--text-color); +} + +thead th { + color: var(--text-light); + font-weight: 100; + font-size: rem; + font-size: 0.8rem; + /* box-shadow: var(--bottom-shadow); */ +} + + +th { + text-align: left; + padding: 1rem; + /* color: var(--primary-dark); */ +} + +td { + padding: 1rem; +} + +th:first-child, +td:first-child { + padding-left: 1.5rem; +} + +th:last-child, +td:last-child { + padding-right: 1.5rem; +} + +tr.clickable { + cursor: pointer; + transition: background-color var(--transition); +} + +tr.clickable:hover { + background-color: var(--hover-color); +} + +tr.active { + background-color: var(--primary-light); +} + +/* Order details */ +.details-content { + background-color: var(--hover-color); + padding: 0 !important; +} + +.details-content table { + margin: 0; + box-shadow: none; +} + +.details-content th { + background-color: transparent; + font-weight: normal; +} + +/* Links */ +a { + text-decoration: none; + color: var(--text-light); +} + +tr a { + text-decoration: underline; + color: var(--primary-color); +} + +.order { + display: flex; + flex-direction: row; +} + +.order div { + display: flex; + flex-direction: column; +} + + + + +/* Responsive adjustments */ +@media (max-width: 768px) { + .search-container { + flex-direction: column; + align-items: stretch; + } + + .search-container>* { + width: 100%; + } + + aside { + min-width: 3.5rem; + max-width: 3.5rem; + } + + .app-body { + margin-left: 3.5rem; + } + + nav a { + width: 2.5rem; + height: 2.5rem; + font-size: 1.2rem; + } +} \ No newline at end of file diff --git a/internal/server/templates/layout/layout.gohtml b/internal/server/templates/layout/layout.gohtml index 2780ab6..d245c35 100644 --- a/internal/server/templates/layout/layout.gohtml +++ b/internal/server/templates/layout/layout.gohtml @@ -1,17 +1,26 @@ {{define "layout"}} - - - {{template "title" .}} - - - + + + + {{template "title" .}} + + + + + + + +

{{template "title" .}}

- {{template "main" .}} - +
+ + {{end}} diff --git a/internal/server/templates/layout/nav.gohtml b/internal/server/templates/layout/nav.gohtml index b6fd88d..e17f673 100644 --- a/internal/server/templates/layout/nav.gohtml +++ b/internal/server/templates/layout/nav.gohtml @@ -1,6 +1,7 @@ {{define "nav"}} -All orders -New order -All Cakes -New Cake + + + + + {{end}} diff --git a/internal/server/templates/order.gohtml b/internal/server/templates/order.gohtml index 23033c3..43738e9 100644 --- a/internal/server/templates/order.gohtml +++ b/internal/server/templates/order.gohtml @@ -1,71 +1,71 @@ {{define "title"}} - Order {{.Order.ID}} + {{if .Order.ID}} + Edytuj zam贸wienie #{{.Order.ID}} + {{else}} + Nowe zam贸wienie + {{end}} {{end}} {{define "main"}}
-
+ {{with .Order}} +

Info

- -
-

Basket

+

Dodane Ciasta

+ +
{{end}} {{with .Catalogue}}
-

Catalogue

+

Katalog

    {{range .}}
  • {{.Name}} {{.Price}} - +
  • {{end}}
{{end}} -
- +{{end}} {{define "orders-table"}} - +
+ - - - - - - + + + + + - + + {{range .}} - - - - - + + + + - + + + + + {{end}} + +
NameSurnamePhoneLocationDateNumer zam贸wieniaKlientLokalizacjaData zam贸wieniaPozosta艂o do zap艂aty StatusTotal
edit{{.Name}}{{.Surname}}{{.Phone}}
#{{.ID}}{{.Surname}} {{.Name}} {{.Phone}} {{.Location}} {{.Date.Format "2006-01-02"}}{{.Total}} PLN {{.Status}}{{.Total}}
+{{end}} +{{define "order_info"}} + + {{range .Cakes}} + + + + + {{end}}
{{.Name}}
ID: {{.ID}}
Cena:
{{.Price}} PLN
Ilo艣膰:
{{.Amount}}
Razem:
{{.Total}} PLN
- {{end}} +{{end}} diff --git a/internal/store/session.go b/internal/store/session.go index b34c551..76d5b8d 100644 --- a/internal/store/session.go +++ b/internal/store/session.go @@ -12,6 +12,12 @@ func (s *Store) AddSession(token, userName string, expiration time.Time) error { return err } +func (s *Store) RevokeSession(token string) error { + query := "delete from session where token = ?;" + _, err := s.db.Exec(query, token) + return err +} + func (s *Store) CleanSessions() error { now := time.Now().Format("2006-01-02 15:04") query := "delete from session where expiration < ?;" diff --git a/main.go b/main.go index 5dffb9b..1ffc634 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main -import "github.com/Bronku/iroon/cmd/iroon" +import ( + "github.com/Bronku/iroon/cmd/iroon" +) func main() { iroon.Run()