figuring out svelte woo

webclient
Adam Veldhousen 3 years ago
parent 1f84edf535
commit 7d32805098
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -97,12 +97,30 @@ func (s Stats) putLog(res http.ResponseWriter, req *http.Request) {
return
}
if in.Value == 0 {
fail.Payload = "Must specify non zero value"
fail.Status = http.StatusExpectationFailed
fail.Write(res)
return
}
if in.Type == "" {
fail.Payload = "Must specify 'Weight', 'Calories', or 'Height' for type"
fail.Status = http.StatusExpectationFailed
fail.Write(res)
return
}
if in.RecordedTS.Equal(time.Time{}) {
in.RecordedTS = time.Now().UTC()
}
if err := s.Conn.AddLog(userId, in.StatLog); err != nil {
log.Println(err)
fail.Write(res)
return
}
APIResp{Success: true}.Write(res)
APIResp{Success: true, Payload: in.StatLog}.Write(res)
}

@ -139,10 +139,12 @@ func (u UserHandler) login(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Authorization", fmt.Sprintf("Bearer %s", token))
http.SetCookie(res, &http.Cookie{
Name: "jwt",
Value: token,
Path: "/v1/api/",
Expires: time.Now().Add(duration),
Name: "jwt",
Value: token,
Domain: "localhost",
Path: "/",
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(duration),
})
APIResp{

@ -3,6 +3,7 @@ package services
import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"github.com/go-chi/jwtauth/v5"
@ -36,9 +37,13 @@ func NewJWTVerifier() func(http.Handler) http.Handler {
func GenerateJWT(u User) string {
_, tokenString, _ := tokenEncoder.Encode(map[string]interface{}{
"id": u.ID,
"x-Hasura-Role": "user",
"x-Hasura-User-Id": u.ID,
"id": u.ID,
"https://hasura.io/jwt/claims": map[string]interface{}{
"x-hasura-allowed-roles": []string{"user"},
"X-hasura-role": "user",
"x-hasura-default-role": "user",
"x-hasura-user-id": fmt.Sprintf("%d", u.ID),
},
})
return tokenString
}

@ -1,8 +1,8 @@
BIN := .bin
start: $(BIN)/api
start: clean $(BIN)/api
BODYTRACK_API_HOST="localhost" \
BODYTRACK_API_JWT_SECRET="ducks123" \
BODYTRACK_API_JWT_SECRET="D*zvWkiDHi#j&Rx7c#NaPbd*pk%ayjsX" \
BODYTRACK_API_PG_HOST="localhost" \
BODYTRACK_API_PG_USERNAME="api" \
BODYTRACK_API_PG_PASSWORD="api-user" \

@ -19,6 +19,7 @@ services:
- HASURA_GRAPHQL_ENABLE_CONSOLE=true
- HASURA_GRAPHQL_ADMIN_SECRET=bodytrack-graphql
- HASURA_GRAPHQL_UNAUTHORIZED_ROLE=anonymous
- "HASURA_GRAPHQL_JWT_SECRET={\"header\": { \"type\": \"cookie\", \"name\":\"jwt\"}, \"type\": \"HS256\", \"key\":\"D*zvWkiDHi#j&Rx7c#NaPbd*pk%ayjsX\"}"
ports:
- 8080:8080
depends_on:

@ -0,0 +1,21 @@
export const doFetch = async function({ url, method = "GET", body = null, headers = {} }) {
try {
const response = await fetch(url, {
method,
body: !!body ? JSON.stringify(body) : undefined,
mode: 'cors',
credentials: 'include',
headers: {
...headers,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
})
return await response.json();
} catch (error) {
return { success: false, payload: error }
}
}

@ -29,6 +29,7 @@
"@urql/svelte": "^1.2.1",
"d3": "^6.7.0",
"graphql": "^15.5.0",
"js-cookie": "^2.2.1",
"svelte-routing": "^1.6.0"
}
}

@ -1,6 +1,7 @@
<script>
import { initClient } from '@urql/svelte';
import { Router, Route } from 'svelte-routing';
import user from './stores/user.js';
import Nav from './components/Nav.svelte';
import Authenticate from './pages/Authenticate.svelte';
import Exercises from './pages/Exercises.svelte';
@ -10,6 +11,7 @@
url: 'http://localhost:8080/v1/graphql',
});
user.load();
export let url = "";
</script>

@ -2,7 +2,6 @@
export let exercise;
let instructions = exercise.instructions.replaceAll('\\n', '<br>');
console.log(instructions);
</script>
<article id="{exercise.name}" class="bg-gray-100 rounded-l p-8 animate-">
<header class="text-lg my-1">

@ -1,6 +1,13 @@
<script>
import Link from './Link.svelte';
import Button from './Button.svelte';
import user from '../stores/user.js';
import { onDestroy } from 'svelte';
let isLoggedIn = false;
const unsubscribe = user.subscribe(u => { isLoggedIn = u.loggedIn });
onDestroy(unsubscribe);
</script>
<style>
nav {
@ -22,7 +29,11 @@
<li><Link href="/">Exercise Index</Link></li>
</ul>
<section>
<Button><Link href="login">Log in/Sign up</Link></Button>
{#if isLoggedIn}
<Button on:button-click={user.logout}><Link href="/">Log out</Link></Button>
{:else}
<Button><Link href="login">Log in/Sign up</Link></Button>
{/if}
</section>
</section>
</nav>

@ -1,64 +1,71 @@
<script>
import { fade } from 'svelte/transition';
import Button from '../components/Button.svelte';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import Button from '../components/Button.svelte';
import user from '../stores/user.js';
export let showLogin = true;
let email= "";
let password= "";
let confirmPassword= "";
let label = showLogin ? "Login" : "Sign up"
let actionLabel = showLogin ? "Login" : "Sign up"
let inverseLabel = !showLogin ? "Login" : "Sign up"
let error = null;
function authenticate(){
const toggle = () => showLogin = !showLogin;
async function authenticate(){
error = null;
fetch("http://localhost:3000/v1/api/user/auth", {
method: "POST",
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password }),
}).then(response => response.json())
.then(({success, payload}) => {
if (!success){
error = payload;
}
})
.catch(e => error = e.message);
await user.login({ email, password });
console.log(user);
location.replace("/stats");
}
</script>
<style>
section {
width: 33%;
min-width: 500px;
}
input {
@apply border-solid border-2 border-gray-700 ;
@apply border-solid border-2 border-gray-700 flex-grow;
}
form label {
@apply flex flex-col py-2;
}
section div {
@apply flex justify-between py-2 w-4/5;
form label span {
@apply w-full;
}
</style>
<div class="w-full h-full flex justify-center flex-col content-center items-center" >
<h2>{label} </h2>
<section transition:fade class="flex flex-col p-5">
<div >
Email: <input id="email" bind:value={email}>
</div>
<div >
Password: <input id="password" bind:value={password} type="password" >
</div>
<h2>{actionLabel}</h2>
<form on:submit|preventDefault={authenticate} transition:fade class="flex flex-col p-5">
<label>
<span>Email:</span>
<input id="email" bind:value={email}>
</label>
<label>
<span>Password:</span>
<input id="password" bind:value={password} type="password">
</label>
{#if !showLogin}
<div>
Confirm Password: <input id="confirm-password" bind:value={confirmPassword} type="password" >
</div>
<label>
<span>Confirm Password:</span>
<input id="confirm-password" bind:value={confirmPassword} type="password">
</label>
{/if}
<section class="flex justify-end">
<span class="pr-2"><Button>{actionLabel}</Button></span>
<span ><Button on:button-click={toggle}>{inverseLabel}</Button></span>
</section>
{#if error}
<section class="text-red-700">
{error}
</section>
{/if}
<Button on:button-click={authenticate}>{label}</Button>
</section>
{#if error}
<section class="text-red-700">
{error}
</section>
{/if}
</form>
</div>

@ -1,47 +1,39 @@
<script>
import { query, operationStore } from '@urql/svelte';
import { onMount } from 'svelte'
import { onMount } from 'svelte';
import stats from '../stores/stats.js';
import * as d3 from 'd3';
import { query } from '@urql/svelte';
const statsQ = stats.getStat({ start: '2020-01-01T00:00:00', types: ['Weight'] });
let statsQuery = operationStore(`
query GetStats($types: [logtype!] = ["Weight"], $start: timestamp! = "2020-01-01T00:00:00") {
stats_log(where: {
logtype: {_in: $types },
recordedts: {_gte: $start}
},
order_by: { recordedts: desc }) {
value
logtype
recordedts
id
}
}`,
{
"start": "2020-01-01T00:00:00",
"types": ["Weight", "Calories"]
},
{ requestPolicy: "cache-and-network" });
let statsData = query(statsQuery);
let data = [30, 86, 168, 281, 303, 365];
let el;
onMount(() => {
query(statsQ);
let mounted = false;
function render(data, isLoading){
if (isLoading || !mounted)
return;
d3.select(el)
.selectAll("div")
.data(data)
.data(data.stats_log)
.enter()
.append("div")
.style("width", function(d) {
return d + "px";
return d.value + "px";
})
.text(function(d) {
return d;
return d.recordedts;
});
});
}
$: render($statsQ.data, $statsQ.fetching);
onMount(async () => {
mounted = true;
})
</script>
<style>
.chart :global(div) {

@ -0,0 +1,49 @@
import { query, operationStore, subscription } from '@urql/svelte';
import { writable } from 'svelte/store';
import { doFetch } from '../../helpers';
const defaultState = {loggedIn:false, loading:false, profile: null, error: null}
const { subscribe, set, update } = writable(defaultState);
async function logStat({ type = "Weight", recordedTs, value }) {
return await doFetch({
url: 'http://localhost/v1/api/stats',
method: 'PUT',
body: {
type,
recordedTs,
value
}
});
}
const getStat = ({ start = '2020-01-01T00:00:00',types = ['Weight', 'Calories', 'Height'] }) =>
operationStore(`
query GetStats($types: [logtype!] = ["Weight"], $start: timestamp! = "2020-01-01T00:00:00") {
stats_log(where: {
logtype: {_in: $types },
recordedts: {_gte: $start}
},
order_by: { recordedts: desc }) {
value
logtype
recordedts
id
}
}`,
{ start, types },
{
requestPolicy: "cache-and-network",
fetchOptions: {
headers: {
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
}
}
});
export default { subscribe, logStat, getStat };

@ -0,0 +1,63 @@
import Cookies from 'js-cookie';
import { writable } from 'svelte/store';
import { doFetch } from '../../helpers';
const defaultState = { loggedIn: false, loading: false, profile: null, error: null, token: null };
const { subscribe, set, update } = writable(defaultState);
const logout = () => {
localStorage.removeItem('jwt');
Cookies.remove('jwt', { path: '/', domain: 'localhost' });
set(defaultState);
location.pathname !== "/" && location.replace("/");
}
const load = async () => {
try {
update(p => ({ ...p, loading: true}));
const {success, payload} = await doFetch({ url:"http://localhost:3000/v1/api/user" });
if (!success) {
set({ ...defaultState, error: payload });
logout();
return
}
update(() => ({ ...defaultState, loggedIn:true, profile: payload, token: localStorage.getItem('jwt') }));
} catch (e) {
set({ ...defaultState, error: e.message });
logout();
}
}
const login = async ({ email, password }) => {
try {
update(p => ({ ...p, loading: true}));
const { success, payload } = await doFetch({
url: "http://localhost:3000/v1/api/user/auth",
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: { email, password }
});
if (!success) {
set({ ...defaultState, error: payload });
logout();
return
}
set({ ...defaultState, loggedIn:true, profile: payload.profile, token: payload.token });
localStorage.setItem('jwt', payload.token)
} catch (e) {
set({ ...defaultState, error: e.message });
console.trace(e);
}
}
export default { subscribe, login, logout, load };

@ -3414,6 +3414,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"

Loading…
Cancel
Save