add router & gql-client for FE, adapt BE routing

This commit is contained in:
gilex-dev 2023-10-26 23:22:42 +02:00
parent 57886016ab
commit a790bc9dd9
17 changed files with 282 additions and 124 deletions

View File

@ -55,3 +55,10 @@ Commands were run in the order listed below on a debian based system.
cd frontend cd frontend
pnpm install pnpm install
``` ```
- Add Vue-router & Villus
```bash
cd frontend
pnpm install villus graphql
pnpm install graphql-tag
```

View File

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title> <title>YetAnotherTodoList</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -9,7 +9,11 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"vue": "^3.3.4" "graphql": "^16.8.1",
"graphql-tag": "^2.12.6",
"villus": "^3.2.0",
"vue": "^3.3.4",
"vue-router": "^4.2.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.2.3",

View File

@ -5,9 +5,21 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
graphql:
specifier: ^16.8.1
version: 16.8.1
graphql-tag:
specifier: ^2.12.6
version: 2.12.6(graphql@16.8.1)
villus:
specifier: ^3.2.0
version: 3.2.0(graphql@16.8.1)(vue@3.3.6)
vue: vue:
specifier: ^3.3.4 specifier: ^3.3.4
version: 3.3.6 version: 3.3.6
vue-router:
specifier: ^4.2.5
version: 4.2.5(vue@3.3.6)
devDependencies: devDependencies:
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
@ -288,6 +300,10 @@ packages:
'@vue/compiler-dom': 3.3.6 '@vue/compiler-dom': 3.3.6
'@vue/shared': 3.3.6 '@vue/shared': 3.3.6
/@vue/devtools-api@6.5.1:
resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
dev: false
/@vue/reactivity-transform@3.3.6: /@vue/reactivity-transform@3.3.6:
resolution: {integrity: sha512-RlJl4dHfeO7EuzU1iJOsrlqWyJfHTkJbvYz/IOJWqu8dlCNWtxWX377WI0VsbAgBizjwD+3ZjdnvSyyFW1YVng==} resolution: {integrity: sha512-RlJl4dHfeO7EuzU1iJOsrlqWyJfHTkJbvYz/IOJWqu8dlCNWtxWX377WI0VsbAgBizjwD+3ZjdnvSyyFW1YVng==}
dependencies: dependencies:
@ -371,6 +387,21 @@ packages:
dev: true dev: true
optional: true optional: true
/graphql-tag@2.12.6(graphql@16.8.1):
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
engines: {node: '>=10'}
peerDependencies:
graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
dependencies:
graphql: 16.8.1
tslib: 2.6.2
dev: false
/graphql@16.8.1:
resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
dev: false
/magic-string@0.30.5: /magic-string@0.30.5:
resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -409,6 +440,20 @@ packages:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: false
/villus@3.2.0(graphql@16.8.1)(vue@3.3.6):
resolution: {integrity: sha512-kb4Dv6Uvu7FSqfVnXTFb7I+VDv9fzFG2Emqu1eu9acY/Y3xM8xqy/I8w/HexNYDkofx5fGIgWk9nvfEWROtlGw==}
peerDependencies:
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
vue: ^3.0.0 || ^2.7.0
dependencies:
graphql: 16.8.1
vue: 3.3.6
dev: false
/vite@4.5.0: /vite@4.5.0:
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@ -444,6 +489,15 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vue-router@4.2.5(vue@3.3.6):
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
peerDependencies:
vue: ^3.2.0
dependencies:
'@vue/devtools-api': 6.5.1
vue: 3.3.6
dev: false
/vue@3.3.6: /vue@3.3.6:
resolution: {integrity: sha512-jJIDETeWJnoY+gfn4ZtMPMS5KtbP4ax+CT4dcQFhTnWEk8xMupFyQ0JxL28nvT/M4+p4a0ptxaV2WY0LiIxvRg==} resolution: {integrity: sha512-jJIDETeWJnoY+gfn4ZtMPMS5KtbP4ax+CT4dcQFhTnWEk8xMupFyQ0JxL28nvT/M4+p4a0ptxaV2WY0LiIxvRg==}
peerDependencies: peerDependencies:

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

1
frontend/src/.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'vue/dist/vue.esm-bundler.js';

View File

@ -1,30 +1,21 @@
<script setup> <script setup></script>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template> <template>
<div> <router-link to="/">Home</router-link> |
<a href="https://vitejs.dev" target="_blank"> <router-link to="/about">About</router-link>
<img src="/vite.svg" class="logo" alt="Vite logo" /> <router-view></router-view>
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template> </template>
<style scoped> <style scoped>
.logo { .logo {
height: 6em; height: 6em;
padding: 1.5em; padding: 1.5em;
will-change: filter; will-change: filter;
transition: filter 300ms; transition: filter 300ms;
} }
.logo:hover { .logo:hover {
filter: drop-shadow(0 0 2em #646cffaa); filter: drop-shadow(0 0 2em #646cffaa);
} }
.logo.vue:hover { .logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa); filter: drop-shadow(0 0 2em #42b883aa);
} }
</style> </style>

View File

@ -1,40 +1,43 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
defineProps({ defineProps({
msg: String, msg: String
}) })
const count = ref(0) const count = ref(0)
</script> </script>
<template> <template>
<h1>{{ msg }}</h1> <h1>{{ msg }}</h1>
<div class="card"> <div class="card">
<button type="button" @click="count++">count is {{ count }}</button> <button type="button" @click="count++">count is {{ count }}</button>
<p> <p>
Edit Edit
<code>components/HelloWorld.vue</code> to test HMR <code>components/HelloWorld.vue</code> to test HMR
</p> </p>
</div> </div>
<p> <p>
Check out Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a >create-vue</a
>, the official Vue + Vite starter >, the official Vue + Vite starter
</p> </p>
<p> <p>
Install Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a> <a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX in your IDE for a better DX
</p> </p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> <p class="read-the-docs"
>Go to <a href="https://vitejs.dev">vitejs.dev</a> or
<a href="https://vuejs.org/">vuejs.org</a> to learn more</p
>
</template> </template>
<style scoped> <style scoped>
.read-the-docs { .read-the-docs {
color: #888; color: #888;
} }
</style> </style>

View File

@ -0,0 +1,38 @@
<template>
<div class="gql_showcase">
<p>See GraphQl in action:</p>
<ul v-if="data">
<li v-for="user in data.users"
>{{ user.userName }}
<ul v-if="user.todos">
<li v-for="todo in user.todos">{{ todo.text }}</li>
</ul>
</li>
</ul>
</div>
</template>
<script setup>
import { useQuery } from 'villus'
import { gql } from 'graphql-tag'
const getUsers = gql`
query getUsers {
users {
userName
todos {
text
}
}
}
`
const { data } = useQuery({
query: getUsers
})
</script>
<style scoped>
.gql_showcase {
text-align: left;
}
</style>

View File

@ -1,5 +1,26 @@
import { createApp } from 'vue' import { createApp } from 'vue/dist/vue.esm-bundler.js'
import { createClient } from 'villus'
import { createRouter, createWebHistory } from 'vue-router'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import Home from './views/Home.vue'
import About from './views/About.vue'
createApp(App).mount('#app') const NotFound = { template: '<div>Sorry, page not found (404)</div>' }
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
const router = createRouter({
history: createWebHistory(),
routes
})
const villusClient = createClient({
url: '/api'
})
createApp(App).use(router).use(villusClient).mount('#app')

View File

@ -1,89 +1,95 @@
:root { :root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
color-scheme: light dark; color-scheme: light dark;
color: rgba(255, 255, 255, 0.87); color: rgba(255, 255, 255, 0.87);
background-color: #242424; background-color: #242424;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
a { a {
font-weight: 500; font-weight: 500;
color: #646cff; color: #646cff;
text-decoration: inherit; text-decoration: inherit;
} }
a:hover { a:hover {
color: #535bf2; color: #535bf2;
} }
a { a {
font-weight: 500; font-weight: 500;
color: #646cff; color: #646cff;
text-decoration: inherit; text-decoration: inherit;
} }
a:hover { a:hover {
color: #535bf2; color: #535bf2;
} }
body { body {
margin: 0; margin: 0;
display: flex; display: flex;
place-items: center; place-items: center;
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
} }
h1 { h1 {
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1; line-height: 1.1;
} }
button { button {
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0.6em 1.2em; padding: 0.6em 1.2em;
font-size: 1em; font-size: 1em;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
background-color: #1a1a1a; background-color: #1a1a1a;
cursor: pointer; cursor: pointer;
transition: border-color 0.25s; transition: border-color 0.25s;
} }
button:hover { button:hover {
border-color: #646cff; border-color: #646cff;
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 4px auto -webkit-focus-ring-color; outline: 4px auto -webkit-focus-ring-color;
} }
.card { .card {
padding: 2em; padding: 2em;
} }
#app { #app {
max-width: 1280px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 2rem; padding: 2rem;
text-align: center; text-align: center;
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
color: #213547; color: #213547;
background-color: #ffffff; background-color: #ffffff;
} }
a:hover {
color: #747bff; a:hover {
} color: #747bff;
button { }
background-color: #f9f9f9;
} button {
background-color: #f9f9f9;
}
} }

View File

@ -0,0 +1,7 @@
<script setup>
import HelloWorld from '../components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="Vite + Vue"></HelloWorld>
</template>

View File

@ -0,0 +1,8 @@
<script setup>
import Users from '../components/Users.vue'
</script>
<template>
<h1>Welcome, this is YetAnotherTodoList</h1>
<Users />
</template>

View File

@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package server package server
import ( import (
"net/http"
"github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/graphql/playground"
"somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/globals"
) )
func handleDevTools() { func handleDevTools(port int) {
http.Handle("/playground", playground.Handler("GraphQL playground", "/api")) mux.Handle("/playground", playground.Handler("GraphQL playground", "/api"))
globals.Logger.Printf("connect to http://localhost:%v/ for GraphQL playground", port)
} }

View File

@ -19,5 +19,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package server package server
func handleDevTools() { func handleDevTools(_ int) {
} }

View File

@ -34,7 +34,21 @@ func handleFrontend() {
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
frontendFS := http.FileServer(http.FS(stripped)) frontendFS := http.FileServer(http.FS(stripped))
http.Handle("/", frontendFS) mux.Handle("/assets/", frontendFS)
mux.HandleFunc("/", indexHandler)
// TODO: redirect from vue to 404 page (on go/proxy server)
}
// Following Nathan Tsao's implementation on https://betterprogramming.pub/how-to-serve-a-single-page-application-using-go-4b9a38d92987
func indexHandler(w http.ResponseWriter, r *http.Request) {
// TODO: add map with path:type
if r.URL.Path == "/vite.svg" {
rawFile, _ := frontend.ReadFile("dist/vite.svg")
w.Header().Add("Content-Type", "image/svg+xml")
w.Write(rawFile)
return
}
rawFile, _ := frontend.ReadFile("dist/index.html")
w.Write(rawFile)
} }

View File

@ -26,16 +26,20 @@ import (
"somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph" "somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph"
) )
func StartServer(port int) { var mux *http.ServeMux
handleDevTools() // controlled by 'dev' tag
handleFrontend() // controlled by 'headless' tag
http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { func StartServer(port int) {
mux = http.NewServeMux()
handleDevTools(port) // controlled by 'dev' tag
handleFrontend() // controlled by 'headless' tag
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s %s", globals.Version, globals.CommitHash) fmt.Fprintf(w, "%s %s", globals.Version, globals.CommitHash)
}) })
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}})) srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))
http.Handle("/api", srv) mux.Handle("/api", srv)
globals.Logger.Printf("connect to http://localhost:%v/ for GraphQL playground", port)
globals.Logger.Fatal(http.ListenAndServe(":"+strconv.FormatInt(int64(port), 10), nil)) globals.Logger.Fatal(http.ListenAndServe(":"+strconv.FormatInt(int64(port), 10), mux))
} }