This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
@@ -32,10 +33,10 @@ type Auth interface {
|
||||
}
|
||||
|
||||
type AuthSqlite struct {
|
||||
db *sql.DB
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewAuthSqlite(db *sql.DB) *AuthSqlite {
|
||||
func NewAuthSqlite(db *sqlx.DB) *AuthSqlite {
|
||||
return &AuthSqlite{db: db}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"spend-sparrow/log"
|
||||
"spend-sparrow/types"
|
||||
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,8 +17,8 @@ var (
|
||||
ErrAlreadyExists = errors.New("row already exists")
|
||||
)
|
||||
|
||||
func RunMigrations(db *sql.DB, pathPrefix string) error {
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
func RunMigrations(db *sqlx.DB, pathPrefix string) error {
|
||||
driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
log.Error("Could not create Migration instance: %v", err)
|
||||
return types.ErrInternal
|
||||
|
||||
126
handler/account.go
Normal file
126
handler/account.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"spend-sparrow/handler/middleware"
|
||||
"spend-sparrow/service"
|
||||
"spend-sparrow/template/account"
|
||||
"spend-sparrow/utils"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Account interface {
|
||||
Handle(router *http.ServeMux)
|
||||
}
|
||||
|
||||
type AccountImpl struct {
|
||||
service service.Account
|
||||
auth service.Auth
|
||||
render *Render
|
||||
}
|
||||
|
||||
func NewAccount(service service.Account, auth service.Auth, render *Render) Account {
|
||||
return AccountImpl{
|
||||
service: service,
|
||||
auth: auth,
|
||||
render: render,
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AccountImpl) Handle(router *http.ServeMux) {
|
||||
router.Handle("/account", handler.handleAccountPage())
|
||||
// router.Handle("POST /account", handler.handleAddAccount())
|
||||
// router.Handle("GET /account", handler.handleGetAccount())
|
||||
// router.Handle("DELETE /account/{id}", handler.handleDeleteAccount())
|
||||
}
|
||||
|
||||
func (handler AccountImpl) handleAccountPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUser(r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
return
|
||||
}
|
||||
|
||||
comp := account.AccountListComp(nil)
|
||||
handler.render.RenderLayout(r, w, comp, user)
|
||||
}
|
||||
}
|
||||
|
||||
// func (handler AccountImpl) handleAddAccount() http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// user := middleware.GetUser(r)
|
||||
// if user == nil {
|
||||
// utils.DoRedirect(w, r, "/auth/signin")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var dateStr = r.FormValue("date")
|
||||
// var typeStr = r.FormValue("type")
|
||||
// var setsStr = r.FormValue("sets")
|
||||
// var repsStr = r.FormValue("reps")
|
||||
//
|
||||
// wo := service.NewAccountDto("", dateStr, typeStr, setsStr, repsStr)
|
||||
// wo, err := handler.service.AddAccount(user, wo)
|
||||
// if err != nil {
|
||||
// utils.TriggerToast(w, r, "error", "Invalid input values", http.StatusBadRequest)
|
||||
// http.Error(w, "Invalid input values", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
// wor := account.Account{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps}
|
||||
//
|
||||
// comp := account.AccountItemComp(wor, true)
|
||||
// handler.render.Render(r, w, comp)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (handler AccountImpl) handleGetAccount() http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// user := middleware.GetUser(r)
|
||||
// if user == nil {
|
||||
// utils.DoRedirect(w, r, "/auth/signin")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// workouts, err := handler.service.GetAccounts(user)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// wos := make([]*types.Account, 0)
|
||||
// for _, wo := range workouts {
|
||||
// wos = append(wos, *types.Account{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps})
|
||||
// }
|
||||
//
|
||||
// comp := account.AccountListComp(wos)
|
||||
// handler.render.Render(r, w, comp)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (handler AccountImpl) handleDeleteAccount() http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// user := middleware.GetUser(r)
|
||||
// if user == nil {
|
||||
// utils.DoRedirect(w, r, "/auth/signin")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// rowId := r.PathValue("id")
|
||||
// if rowId == "" {
|
||||
// utils.TriggerToast(w, r, "error", "Missing ID field", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// rowIdInt, err := strconv.Atoi(rowId)
|
||||
// if err != nil {
|
||||
// utils.TriggerToast(w, r, "error", "Invalid ID", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// err = handler.service.DeleteAccount(user, rowIdInt)
|
||||
// if err != nil {
|
||||
// utils.TriggerToast(w, r, "error", "Internal Server Error", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,129 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"spend-sparrow/handler/middleware"
|
||||
"spend-sparrow/service"
|
||||
"spend-sparrow/template/workout"
|
||||
"spend-sparrow/utils"
|
||||
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Workout interface {
|
||||
Handle(router *http.ServeMux)
|
||||
}
|
||||
|
||||
type WorkoutImpl struct {
|
||||
service service.Workout
|
||||
auth service.Auth
|
||||
render *Render
|
||||
}
|
||||
|
||||
func NewWorkout(service service.Workout, auth service.Auth, render *Render) Workout {
|
||||
return WorkoutImpl{
|
||||
service: service,
|
||||
auth: auth,
|
||||
render: render,
|
||||
}
|
||||
}
|
||||
|
||||
func (handler WorkoutImpl) Handle(router *http.ServeMux) {
|
||||
router.Handle("/workout", handler.handleWorkoutPage())
|
||||
router.Handle("POST /api/workout", handler.handleAddWorkout())
|
||||
router.Handle("GET /api/workout", handler.handleGetWorkout())
|
||||
router.Handle("DELETE /api/workout/{id}", handler.handleDeleteWorkout())
|
||||
}
|
||||
|
||||
func (handler WorkoutImpl) handleWorkoutPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUser(r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
return
|
||||
}
|
||||
|
||||
currentDate := time.Now().Format("2006-01-02")
|
||||
comp := workout.WorkoutComp(currentDate)
|
||||
handler.render.RenderLayout(r, w, comp, user)
|
||||
}
|
||||
}
|
||||
|
||||
func (handler WorkoutImpl) handleAddWorkout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUser(r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
return
|
||||
}
|
||||
|
||||
var dateStr = r.FormValue("date")
|
||||
var typeStr = r.FormValue("type")
|
||||
var setsStr = r.FormValue("sets")
|
||||
var repsStr = r.FormValue("reps")
|
||||
|
||||
wo := service.NewWorkoutDto("", dateStr, typeStr, setsStr, repsStr)
|
||||
wo, err := handler.service.AddWorkout(user, wo)
|
||||
if err != nil {
|
||||
utils.TriggerToast(w, r, "error", "Invalid input values", http.StatusBadRequest)
|
||||
http.Error(w, "Invalid input values", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
wor := workout.Workout{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps}
|
||||
|
||||
comp := workout.WorkoutItemComp(wor, true)
|
||||
handler.render.Render(r, w, comp)
|
||||
}
|
||||
}
|
||||
|
||||
func (handler WorkoutImpl) handleGetWorkout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUser(r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
return
|
||||
}
|
||||
|
||||
workouts, err := handler.service.GetWorkouts(user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wos := make([]workout.Workout, 0)
|
||||
for _, wo := range workouts {
|
||||
wos = append(wos, workout.Workout{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps})
|
||||
}
|
||||
|
||||
comp := workout.WorkoutListComp(wos)
|
||||
handler.render.Render(r, w, comp)
|
||||
}
|
||||
}
|
||||
|
||||
func (handler WorkoutImpl) handleDeleteWorkout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := middleware.GetUser(r)
|
||||
if user == nil {
|
||||
utils.DoRedirect(w, r, "/auth/signin")
|
||||
return
|
||||
}
|
||||
|
||||
rowId := r.PathValue("id")
|
||||
if rowId == "" {
|
||||
utils.TriggerToast(w, r, "error", "Missing ID field", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rowIdInt, err := strconv.Atoi(rowId)
|
||||
if err != nil {
|
||||
utils.TriggerToast(w, r, "error", "Invalid ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.service.DeleteWorkout(user, rowIdInt)
|
||||
if err != nil {
|
||||
utils.TriggerToast(w, r, "error", "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
16
main.go
16
main.go
@@ -9,7 +9,6 @@ import (
|
||||
"spend-sparrow/types"
|
||||
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/joho/godotenv"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
@@ -28,7 +28,7 @@ func main() {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", "./data.db")
|
||||
db, err := sqlx.Open("sqlite3", "./data.db")
|
||||
if err != nil {
|
||||
log.Fatal("Could not open Database data.db: %v", err)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func main() {
|
||||
run(context.Background(), db, os.Getenv)
|
||||
}
|
||||
|
||||
func run(ctx context.Context, database *sql.DB, env func(string) string) {
|
||||
func run(ctx context.Context, database *sqlx.DB, env func(string) string) {
|
||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
@@ -100,26 +100,26 @@ func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func createHandler(d *sql.DB, serverSettings *types.Settings) http.Handler {
|
||||
func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler {
|
||||
var router = http.NewServeMux()
|
||||
|
||||
authDb := db.NewAuthSqlite(d)
|
||||
workoutDb := db.NewWorkoutDbSqlite(d)
|
||||
accountDb := db.NewAccountSqlite(d)
|
||||
|
||||
randomService := service.NewRandomImpl()
|
||||
clockService := service.NewClockImpl()
|
||||
mailService := service.NewMailImpl(serverSettings)
|
||||
|
||||
authService := service.NewAuthImpl(authDb, randomService, clockService, mailService, serverSettings)
|
||||
workoutService := service.NewWorkoutImpl(workoutDb, randomService, clockService, mailService, serverSettings)
|
||||
accountService := service.NewAccountImpl(accountDb, randomService, clockService, serverSettings)
|
||||
|
||||
render := handler.NewRender()
|
||||
indexHandler := handler.NewIndex(authService, render)
|
||||
authHandler := handler.NewAuth(authService, render)
|
||||
workoutHandler := handler.NewWorkout(workoutService, authService, render)
|
||||
accountHandler := handler.NewAccount(accountService, authService, render)
|
||||
|
||||
indexHandler.Handle(router)
|
||||
workoutHandler.Handle(router)
|
||||
accountHandler.Handle(router)
|
||||
authHandler.Handle(router)
|
||||
|
||||
// Serve static files (CSS, JS and images)
|
||||
|
||||
@@ -12,6 +12,6 @@ CREATE TABLE account (
|
||||
created_at DATETIME NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
updated_at DATETIME,
|
||||
updated_by TEXT,
|
||||
updated_by TEXT
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ type AccountImpl struct {
|
||||
settings *types.Settings
|
||||
}
|
||||
|
||||
func NewAccountImpl(db db.Account, clock Clock, random Random, settings *types.Settings) Account {
|
||||
func NewAccountImpl(db db.Account, random Random, clock Clock, settings *types.Settings) Account {
|
||||
return AccountImpl{
|
||||
db: db,
|
||||
clock: clock,
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><path fill="currentColor" d="M165.906 18.688C15.593 59.28-42.187 198.55 92.72 245.375h-1.095c.635.086 1.274.186 1.906.28c8.985 3.077 18.83 5.733 29.532 7.94C173.36 273.35 209.74 321.22 212.69 368c-33.514 23.096-59.47 62.844-59.47 62.844l26.28 38.686L138.28 493h81.97c-40.425-40.435-11.76-85.906 36.125-85.906c48.54 0 73.945 48.112 36.156 85.906h81.126l-40.375-23.47l26.283-38.686s-26.376-40.4-60.282-63.406c3.204-46.602 39.5-94.167 89.595-113.844c10.706-2.207 20.546-4.86 29.53-7.938c.633-.095 1.273-.195 1.908-.28h-1.125c134.927-46.82 77.163-186.094-73.157-226.69c-40.722 39.37 6.54 101.683 43.626 56.877c36.9 69.08 8.603 127.587-72.28 83.406c-11.88 24.492-34.213 41.374-60.688 41.374c-26.703 0-49.168-17.167-60.97-42c-81.774 45.38-110.512-13.372-73.437-82.78c37.09 44.805 84.35-17.508 43.626-56.876zm90.79 35.92c-27.388 0-51.33 27.556-51.33 63.61c0 36.056 23.942 62.995 51.33 62.995s51.327-26.94 51.327-62.994c0-36.058-23.94-63.61-51.328-63.61z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 128 128"><path d="M93.46 39.45c6.71-1.49 15.45-8.15 16.78-11.43.78-1.92-3.11-4.92-4.15-6.13-2.38-2.76-1.42-4.12-.5-7.41 1.05-3.74-1.44-7.87-4.97-9.49s-7.75-1.11-11.3.47-6.58 4.12-9.55 6.62c-2.17-1.37-5.63-7.42-11.23-3.49-3.87 2.71-4.22 8.61-3.72 13.32 1.17 10.87 3.85 16.51 8.9 18.03 6.38 1.92 13.44.91 19.74-.49" style="fill:#ffca28"/><path d="M104.36 8.18c-.85 14.65-15.14 24.37-21.92 28.65l4.4 3.78s2.79.06 6.61-1.16c6.55-2.08 16.12-7.96 16.78-11.43.97-5.05-4.21-3.95-5.38-7.94-.61-2.11 2.97-6.1-.49-11.9M79.78 12.09s-2.55-2.61-4.44-3.8c-.94 1.77-1.61 3.69-1.94 5.67-.59 3.48 0 8.42 1.39 12.1.22.57 1.04.48 1.13-.12 1.2-7.91 3.86-13.85 3.86-13.85" style="fill:#e2a610"/><path d="M61.96 38.16S30.77 41.53 16.7 68.61s-2.11 43.5 10.55 49.48 44.56 8.09 65.31 3.17 25.94-15.12 24.97-24.97c-1.41-14.38-14.77-23.22-14.77-23.22s.53-17.76-13.25-29.29c-12.23-10.24-27.55-5.62-27.55-5.62" style="fill:#ffca28"/><path d="M74.76 83.73c-6.69-8.44-14.59-9.57-17.12-12.6-1.38-1.65-2.19-3.32-1.88-5.39.33-2.2 2.88-3.72 4.86-4.09 2.31-.44 7.82-.21 12.45 4.2 1.1 1.04.7 2.66.67 4.11-.08 3.11 4.37 6.13 7.97 3.53 3.61-2.61.84-8.42-1.49-11.24-1.76-2.13-8.14-6.82-16.07-7.56-2.23-.21-11.2-1.54-16.38 8.31-1.49 2.83-2.04 9.67 5.76 15.45 1.63 1.21 10.09 5.51 12.44 8.3 4.07 4.83 1.28 9.08-1.9 9.64-8.67 1.52-13.58-3.17-14.49-5.74-.65-1.83.03-3.81-.81-5.53-.86-1.77-2.62-2.47-4.48-1.88-6.1 1.94-4.16 8.61-1.46 12.28 2.89 3.93 6.44 6.3 10.43 7.6 14.89 4.85 22.05-2.81 23.3-8.42.92-4.11.82-7.67-1.8-10.97" style="fill:#6b4b46"/><path d="M71.16 48.99c-12.67 27.06-14.85 61.23-14.85 61.23" style="fill:none;stroke:#6b4b46;stroke-width:5;stroke-miterlimit:10"/><path d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46-.73 2.44-10.08-7.06-23.98-6.49-4.86.2-3.45-2.78-1.2-4.5 2.97-2.27 7.96-3.91 15.48-1.47" style="fill:#6d4c41"/><path d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46-.73 2.44-10.08-7.06-23.98-6.49-4.86.2-3.45-2.78-1.2-4.5 2.97-2.27 7.96-3.91 15.48-1.47" style="fill:#6b4b46"/><path d="M96.49 58.86c1.06-.73 4.62.53 5.62 7.5.49 3.41.64 6.71.64 6.71s-4.2-3.77-5.59-6.42c-1.75-3.35-2.43-6.59-.67-7.79" style="fill:#e2a610"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.1 KiB |
1
template/account/default.go
Normal file
1
template/account/default.go
Normal file
@@ -0,0 +1 @@
|
||||
package account
|
||||
7
template/account/workout.templ
Normal file
7
template/account/workout.templ
Normal file
@@ -0,0 +1,7 @@
|
||||
package account
|
||||
|
||||
import "spend-sparrow/types"
|
||||
|
||||
templ AccountListComp(accounts []*types.Account) {
|
||||
<main class="mx-2"></main>
|
||||
}
|
||||
@@ -3,14 +3,7 @@ package template
|
||||
templ Index() {
|
||||
<div class="h-full">
|
||||
<div class="text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">Next Level Workout Tracker</h1>
|
||||
<p class="py-6">
|
||||
Ever wanted to track your workouts and see your progress over time? spend-sparrow is the perfect
|
||||
solution for you.
|
||||
</p>
|
||||
<a href="/workout" class="">Get Started</a>
|
||||
</div>
|
||||
<h1 class="text-5xl font-bold">Spend Sparrow - Your personal finance</h1>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ templ Layout(slot templ.Component, user templ.Component) {
|
||||
<div class="h-screen flex flex-col">
|
||||
<div class="flex justify-end items-center gap-2 py-1 px-2 h-12 md:gap-10 md:px-10 md:py-2 shadow-sm">
|
||||
<a href="/" class="flex-1 flex gap-2">
|
||||
<img src="/static/favicon.svg" alt="spend-sparrow logo"/>
|
||||
<span>spend-sparrow</span>
|
||||
<img class="w-6" src="/static/favicon.svg" alt="Spend Sparrow logo"/>
|
||||
<span class="text-xl font-bold">SpendSparrow</span>
|
||||
</a>
|
||||
@user
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user