#109 move last url to new layout
This commit is contained in:
@@ -3,7 +3,7 @@ tmp_dir = "tmp"
|
|||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = []
|
args_bin = []
|
||||||
bin = "/bin/bash -c 'FRONTEND_URL=xxx ./tmp/main'"
|
bin = "./tmp/main"
|
||||||
cmd = "templ generate && go build -o ./tmp/main ."
|
cmd = "templ generate && go build -o ./tmp/main ."
|
||||||
delay = 1000
|
delay = 1000
|
||||||
exclude_dir = ["static", "migrations", "node_modules", "tmp"]
|
exclude_dir = ["static", "migrations", "node_modules", "tmp"]
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -4,7 +4,9 @@ go 1.22.5
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
firebase.google.com/go v3.13.0+incompatible
|
firebase.google.com/go v3.13.0+incompatible
|
||||||
|
github.com/a-h/templ v0.2.747
|
||||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/prometheus/client_golang v1.20.1
|
github.com/prometheus/client_golang v1.20.1
|
||||||
google.golang.org/api v0.194.0
|
google.golang.org/api v0.194.0
|
||||||
@@ -19,7 +21,6 @@ require (
|
|||||||
cloud.google.com/go/iam v1.1.12 // indirect
|
cloud.google.com/go/iam v1.1.12 // indirect
|
||||||
cloud.google.com/go/longrunning v0.5.11 // indirect
|
cloud.google.com/go/longrunning v0.5.11 // indirect
|
||||||
cloud.google.com/go/storage v1.43.0 // indirect
|
cloud.google.com/go/storage v1.43.0 // indirect
|
||||||
github.com/a-h/templ v0.2.747 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -107,6 +107,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
|||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ import (
|
|||||||
func getHandler(db *sql.DB) http.Handler {
|
func getHandler(db *sql.DB) http.Handler {
|
||||||
var router = http.NewServeMux()
|
var router = http.NewServeMux()
|
||||||
|
|
||||||
router.HandleFunc("/", service.HandleStaticUi)
|
router.HandleFunc("/", service.HandleIndexAnd404)
|
||||||
|
|
||||||
// Serve static files (CSS, JS and images)
|
// Serve static files (CSS, JS and images)
|
||||||
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||||
|
|
||||||
|
router.HandleFunc("/app", service.App)
|
||||||
router.HandleFunc("POST /api/workout", service.NewWorkout(db))
|
router.HandleFunc("POST /api/workout", service.NewWorkout(db))
|
||||||
router.HandleFunc("GET /api/workout", service.GetWorkouts(db))
|
router.HandleFunc("GET /api/workout", service.GetWorkouts(db))
|
||||||
router.HandleFunc("DELETE /api/workout", service.DeleteWorkout(db))
|
router.HandleFunc("DELETE /api/workout", service.DeleteWorkout(db))
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
@@ -14,6 +15,11 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
log.Println("Starting server...")
|
log.Println("Starting server...")
|
||||||
|
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "./data.db")
|
db, err := sql.Open("sqlite3", "./data.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not open Database data.db: ", err)
|
log.Fatal("Could not open Database data.db: ", err)
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func EnableCors(next http.Handler) http.Handler {
|
func EnableCors(next http.Handler) http.Handler {
|
||||||
var frontent_url = os.Getenv("FRONTEND_URL")
|
var base_url = os.Getenv("BASE_URL")
|
||||||
if frontent_url == "" {
|
if base_url == "" {
|
||||||
log.Fatal("FRONTEND_URL is not set")
|
log.Fatal("BASE_URL is not set")
|
||||||
}
|
}
|
||||||
log.Println("FRONTEND_URL is", frontent_url)
|
log.Println("BASE_URL is", base_url)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", frontent_url)
|
w.Header().Set("Access-Control-Allow-Origin", base_url)
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
|
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleStaticUi(w http.ResponseWriter, r *http.Request) {
|
func HandleIndexAnd404(w http.ResponseWriter, r *http.Request) {
|
||||||
var comp templ.Component = nil
|
var comp templ.Component = nil
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
comp = templates.Layout(templates.NotFound())
|
comp = templates.Layout(templates.NotFound())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"me-fit/templates"
|
||||||
"me-fit/utils"
|
"me-fit/utils"
|
||||||
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
@@ -22,6 +23,13 @@ var (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func App(w http.ResponseWriter, r *http.Request) {
|
||||||
|
comp := templates.App()
|
||||||
|
layout := templates.Layout(comp)
|
||||||
|
layout.Render(r.Context(), w)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func NewWorkout(db *sql.DB) http.HandlerFunc {
|
func NewWorkout(db *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
metrics.WithLabelValues("new").Inc()
|
metrics.WithLabelValues("new").Inc()
|
||||||
|
|||||||
65
templates/app.templ
Normal file
65
templates/app.templ
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
templ App() {
|
||||||
|
<main class="mx-2">
|
||||||
|
<form
|
||||||
|
class="max-w-xl mx-auto flex flex-col gap-4 justify-center mt-10"
|
||||||
|
>
|
||||||
|
<h2 class="text-4xl mb-8">Track your workout</h2>
|
||||||
|
<input
|
||||||
|
id="date"
|
||||||
|
type="date"
|
||||||
|
class="input input-bordered"
|
||||||
|
value=""
|
||||||
|
name="date"
|
||||||
|
/>
|
||||||
|
<select class="select select-bordered w-full" name="type">
|
||||||
|
<option>Push Ups</option>
|
||||||
|
<option>Pull Ups</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="input input-bordered"
|
||||||
|
placeholder="Sets"
|
||||||
|
name="sets"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="input input-bordered"
|
||||||
|
placeholder="Reps"
|
||||||
|
name="reps"
|
||||||
|
/>
|
||||||
|
<button class="btn btn-primary self-end">Save</button>
|
||||||
|
</form>
|
||||||
|
<!-- <div class="overflow-x-auto mx-auto max-w-screen-lg"> -->
|
||||||
|
<!-- <h2 class="text-4xl mt-14 mb-8">Workout history</h2> -->
|
||||||
|
<!-- <table class="table table-auto max-w-full"> -->
|
||||||
|
<!-- <thead> -->
|
||||||
|
<!-- <tr> -->
|
||||||
|
<!-- <th>Date</th> -->
|
||||||
|
<!-- <th>Type</th> -->
|
||||||
|
<!-- <th>Sets</th> -->
|
||||||
|
<!-- <th>Reps</th> -->
|
||||||
|
<!-- <th></th> -->
|
||||||
|
<!-- </tr> -->
|
||||||
|
<!-- </thead> -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- <tbody> -->
|
||||||
|
<!-- <tr> -->
|
||||||
|
<!-- <th>{workout.date}</th> -->
|
||||||
|
<!-- <th>{workout.type}</th> -->
|
||||||
|
<!-- <th>{workout.sets}</th> -->
|
||||||
|
<!-- <th>{workout.reps}</th> -->
|
||||||
|
<!-- <th> -->
|
||||||
|
<!-- <div class="tooltip" data-tip="Delete Entry"> -->
|
||||||
|
<!-- <button on:click={() => deleteWorkout(workout.id)}> -->
|
||||||
|
<!-- <MdiDelete class="text-gray-400 text-lg"></MdiDelete> -->
|
||||||
|
<!-- </button> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
<!-- </th> -->
|
||||||
|
<!-- </tr> -->
|
||||||
|
<!-- </tbody> -->
|
||||||
|
<!-- </table> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
</main>
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
templ header() {
|
templ header() {
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { PUBLIC_BASE_API_URL } from '$env/static/public';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import type { Auth } from 'firebase/auth';
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { addToast } from '$lib/toast';
|
|
||||||
import MdiDelete from '~icons/mdi/delete';
|
|
||||||
|
|
||||||
var auth: Auth | null = null;
|
|
||||||
|
|
||||||
let workouts: any[];
|
|
||||||
$: workouts = [];
|
|
||||||
|
|
||||||
async function handleSubmit(_submit: SubmitEvent) {
|
|
||||||
const form = _submit.target as HTMLFormElement;
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(PUBLIC_BASE_API_URL + '/workout', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + (await auth?.currentUser?.getIdToken())
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
fetchWorkouts();
|
|
||||||
resetForm();
|
|
||||||
} else {
|
|
||||||
addToast('Failed to save workout: ' + (await response.text()), 'error');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
addToast('Failed to save workout: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetForm() {
|
|
||||||
const form = document.querySelector('form') as HTMLFormElement;
|
|
||||||
form.reset();
|
|
||||||
|
|
||||||
const date = new Date();
|
|
||||||
const dateInput = document.getElementById('date') as HTMLInputElement;
|
|
||||||
dateInput.value = date.toISOString().split('T')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchWorkouts() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(PUBLIC_BASE_API_URL + '/workout', {
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + (await auth?.currentUser?.getIdToken())
|
|
||||||
},
|
|
||||||
method: 'GET'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
workouts = await response.json();
|
|
||||||
workouts = workouts.map((workout: any) => {
|
|
||||||
workout.date = new Date(workout.date).toLocaleDateString();
|
|
||||||
return workout;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addToast('Failed to fetch workouts: ' + (await response.text()), 'error');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
addToast('Failed to fetch workouts: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteWorkout(id: string) {
|
|
||||||
console.log('Deleting workout with id: ', id);
|
|
||||||
try {
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('id', id);
|
|
||||||
const response = await fetch(PUBLIC_BASE_API_URL + '/workout', {
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + (await auth?.currentUser?.getIdToken())
|
|
||||||
},
|
|
||||||
body: data,
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
workouts = workouts.filter((workout) => workout.id !== id);
|
|
||||||
} else {
|
|
||||||
addToast('Failed to delete workout: ' + (await response.text()), 'error');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
addToast('Failed to delete workout: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const authImp = import('$lib/firebase');
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
|
|
||||||
auth = (await authImp).auth;
|
|
||||||
await auth.authStateReady();
|
|
||||||
if (!auth?.currentUser) {
|
|
||||||
goto('/signin');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetchWorkouts();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main class="mx-2">
|
|
||||||
<form
|
|
||||||
class="max-w-xl mx-auto flex flex-col gap-4 justify-center mt-10"
|
|
||||||
on:submit|preventDefault={handleSubmit}
|
|
||||||
>
|
|
||||||
<h2 class="text-4xl mb-8">Track your workout</h2>
|
|
||||||
<input id="date" type="date" class="input input-bordered" value={new Date()} name="date" />
|
|
||||||
|
|
||||||
<select class="select select-bordered w-full" name="type">
|
|
||||||
<option>Push Ups</option>
|
|
||||||
<option>Pull Ups</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input type="number" class="input input-bordered" placeholder="Sets" name="sets" />
|
|
||||||
<input type="number" class="input input-bordered" placeholder="Reps" name="reps" />
|
|
||||||
|
|
||||||
<button class="btn btn-primary self-end">Save</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto mx-auto max-w-screen-lg">
|
|
||||||
<h2 class="text-4xl mt-14 mb-8">Workout history</h2>
|
|
||||||
<table class="table table-auto max-w-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Sets</th>
|
|
||||||
<th>Reps</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{#each workouts as workout}
|
|
||||||
<tr>
|
|
||||||
<th>{workout.date}</th>
|
|
||||||
<th>{workout.type}</th>
|
|
||||||
<th>{workout.sets}</th>
|
|
||||||
<th>{workout.reps}</th>
|
|
||||||
<th>
|
|
||||||
<div class="tooltip" data-tip="Delete Entry">
|
|
||||||
<button on:click={() => deleteWorkout(workout.id)}>
|
|
||||||
<MdiDelete class="text-gray-400 text-lg"></MdiDelete>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
Reference in New Issue
Block a user