Added user auth and clicker
This commit is contained in:
parent
8619c00a20
commit
995648dac3
198
Cargo.lock
generated
198
Cargo.lock
generated
@ -262,6 +262,12 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.17.0"
|
||||
@ -349,6 +355,41 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.4.0"
|
||||
@ -375,6 +416,19 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -402,6 +456,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@ -443,6 +506,8 @@ dependencies = [
|
||||
"bcrypt",
|
||||
"futures-util",
|
||||
"poem",
|
||||
"poem-openapi",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing-subscriber",
|
||||
@ -804,6 +869,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
@ -978,6 +1049,25 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.8",
|
||||
"tokio",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -1183,21 +1273,26 @@ dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"mime",
|
||||
"multer",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"poem-derive",
|
||||
"priority-queue",
|
||||
"quick-xml",
|
||||
"rand",
|
||||
"regex",
|
||||
"rfc7239",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"serde_yaml",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
@ -1215,6 +1310,49 @@ dependencies = [
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poem-openapi"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62659dcc7ca09a525881300646f3b28e319889072e83cd16a2865ba024a185e"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"indexmap 2.0.2",
|
||||
"mime",
|
||||
"num-traits",
|
||||
"poem",
|
||||
"poem-openapi-derive",
|
||||
"quick-xml",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poem-openapi-derive"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e90bf699e87e95b8303f9b59684cf3b9a8fff840872b13cbe0680aa4330d5226"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"http",
|
||||
"indexmap 2.0.2",
|
||||
"mime",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.38",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.1"
|
||||
@ -1268,6 +1406,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
@ -1397,6 +1545,15 @@ version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.20"
|
||||
@ -1423,19 +1580,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.189"
|
||||
name = "semver"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.189"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1465,6 +1628,19 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
|
||||
dependencies = [
|
||||
"indexmap 2.0.2",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@ -1782,6 +1958,12 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
@ -2130,6 +2312,12 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
|
||||
@ -11,3 +11,9 @@ tracing-subscriber = { version ="0.3.9", features = ["env-filter"] }
|
||||
sqlx = { version = "0.7.2", features = ["runtime-tokio", "sqlite"] }
|
||||
bcrypt = "0.15.0"
|
||||
anyhow = "1.0.75"
|
||||
poem-openapi = { version = "3.0.5", features = ["websocket"] }
|
||||
serde = "1.0.190"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-4 px-3 mx-auto">
|
||||
<h1>Hello, Bootstrap and Vite!</h1>
|
||||
<button class="btn btn-primary">Primary button</button>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -1,47 +1,35 @@
|
||||
<script setup>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
import Signup from './components/Signup.vue'
|
||||
import Login from './components/Login.vue'
|
||||
import Clicker from './components/Clicker.vue'
|
||||
|
||||
const routes = {
|
||||
'/': Clicker,
|
||||
'/signup': Signup,
|
||||
'/login': Login,
|
||||
'/button': Clicker,
|
||||
}
|
||||
|
||||
const currentPath = ref(window.location.hash)
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
currentPath.value = window.location.hash
|
||||
})
|
||||
|
||||
const currentView = computed(() => {
|
||||
return routes[currentPath.value.slice(1) || '/'] || NotFound
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="You did it!" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<TheWelcome />
|
||||
</main>
|
||||
<Suspense>
|
||||
<component :is="currentView" />
|
||||
<template #fallback>
|
||||
Loading...
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
65
client/src/components/Clicker.vue
Normal file
65
client/src/components/Clicker.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
async function get_clicks() {
|
||||
const api_url = '/api/num'
|
||||
|
||||
let resp = await fetch(api_url)
|
||||
console.log(resp)
|
||||
if (resp.ok) {
|
||||
let ans = await resp.json()
|
||||
console.log(ans)
|
||||
return ans
|
||||
}
|
||||
else {
|
||||
window.location.hash = "/login"
|
||||
return 'ERROR'
|
||||
}
|
||||
}
|
||||
|
||||
let clicks = await get_clicks()
|
||||
const count = ref(clicks)
|
||||
//const count = ref(-1)
|
||||
|
||||
async function click(ev) {
|
||||
ev.preventDefault()
|
||||
|
||||
count.value = await get_clicks()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="form-signin w-100 m-auto">
|
||||
<p class="mt-5 mb-3 text-body">You have accessed this content {{ count }} times this session.</p>
|
||||
<button type="submit" @click="click" class="btn btn-primary w-100 py-2">Refresh Content</button>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* html,
|
||||
body {
|
||||
height: 100%;
|
||||
} */
|
||||
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,44 +0,0 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
msg: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
client/src/components/Login.vue
Normal file
95
client/src/components/Login.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const user = ref('')
|
||||
const pass = ref('')
|
||||
|
||||
async function login(ev) {
|
||||
ev.preventDefault()
|
||||
const api_url = '/api/auth'
|
||||
const payload = { username: user.value, password: pass.value }
|
||||
const fetch_options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
console.log(user.value)
|
||||
console.log(pass.value)
|
||||
console.log(api_url)
|
||||
console.log(payload)
|
||||
console.log(fetch_options)
|
||||
|
||||
let resp = await fetch(api_url, fetch_options)
|
||||
console.log(resp)
|
||||
if (resp.ok) {
|
||||
let ans = await resp.json()
|
||||
console.log(ans)
|
||||
|
||||
window.location.hash = "/"
|
||||
} else {
|
||||
user.value = ''
|
||||
pass.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="form-signin w-100 m-auto">
|
||||
<form onsubmit="event.preventDefault();">
|
||||
<!-- <img class="mb-4" src="../assets/brand/bootstrap-logo.svg" alt="" width="72" height="57"> -->
|
||||
<svg class="bi me-2" width="72" height="57" role="img" viewBox="0 0 16 16">
|
||||
<path d="M4.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z" />
|
||||
<path
|
||||
d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8.5v3a1.5 1.5 0 0 1 1.5 1.5h5.5a.5.5 0 0 1 0 1H10A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5H.5a.5.5 0 0 1 0-1H6A1.5 1.5 0 0 1 7.5 10V7H2a2 2 0 0 1-2-2V4zm1 0v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1zm6 7.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5z" />
|
||||
</svg>
|
||||
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="email" v-model="user" class="form-control" id="floatingInput" placeholder="username">
|
||||
<label for="floatingInput">Username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" v-model="pass" class="form-control" id="floatingPassword" placeholder="Password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<a href="#/signup">Sign up</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" @click="login" class="btn btn-primary w-100 py-2">Sign in</button>
|
||||
|
||||
<p class="mt-5 mb-3 text-body-secondary">© 2023</p>
|
||||
</form>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* html,
|
||||
body {
|
||||
height: 100%;
|
||||
} */
|
||||
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
105
client/src/components/Signup.vue
Normal file
105
client/src/components/Signup.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const user = ref('')
|
||||
const pass = ref('')
|
||||
const refe = ref('')
|
||||
|
||||
const err = ref('')
|
||||
|
||||
async function signup(ev) {
|
||||
ev.preventDefault()
|
||||
const api_url = '/api/user'
|
||||
const payload = { username: user.value, password: pass.value, referral: refe.value }
|
||||
const fetch_options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
console.log(user.value)
|
||||
console.log(pass.value)
|
||||
console.log(api_url)
|
||||
console.log(payload)
|
||||
console.log(fetch_options)
|
||||
|
||||
let resp = await fetch(api_url, fetch_options)
|
||||
console.log(resp)
|
||||
if (resp.ok) {
|
||||
let ans = await resp.json()
|
||||
console.log(ans)
|
||||
|
||||
window.location.hash = "/"
|
||||
}
|
||||
else {
|
||||
err.value = "Username taken or invalid referral code"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="form-signin w-100 m-auto my-auto">
|
||||
<form onsubmit="event.preventDefault();">
|
||||
<!-- <img class="mb-4" src="../assets/brand/bootstrap-logo.svg" alt="" width="72" height="57"> -->
|
||||
<svg class="bi me-2" width="72" height="57" role="img" viewBox="0 0 16 16">
|
||||
<path d="M4.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z" />
|
||||
<path
|
||||
d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8.5v3a1.5 1.5 0 0 1 1.5 1.5h5.5a.5.5 0 0 1 0 1H10A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5H.5a.5.5 0 0 1 0-1H6A1.5 1.5 0 0 1 7.5 10V7H2a2 2 0 0 1-2-2V4zm1 0v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1zm6 7.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5z" />
|
||||
</svg>
|
||||
<h1 class="h3 mb-3 fw-normal">Sign up</h1>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" v-model="user" class="form-control" id="floatingInput" placeholder="username">
|
||||
<label for="floatingInput">Username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" v-model="pass" class="form-control" id="floatingPassword" placeholder="Password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="text" v-model="refe" class="form-control" id="floatingInput" placeholder="">
|
||||
<label for="floatingInput">Referral Code</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<p> {{ err }} </p>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
Already have an account?
|
||||
<a href="#/login">Log in</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" @click="signup" class="btn btn-primary w-100 py-2">Sign up</button>
|
||||
<p class="mt-5 mb-3 text-body-secondary">© 2023</p>
|
||||
</form>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* html,
|
||||
body {
|
||||
height: 100%;
|
||||
} */
|
||||
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,88 +0,0 @@
|
||||
<script setup>
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
||||
@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
src/main.rs
206
src/main.rs
@ -1,17 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
//use anyhow::Result;
|
||||
//use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use poem::{
|
||||
get, handler,
|
||||
//get,
|
||||
handler, //Result,
|
||||
listener::TcpListener,
|
||||
session::{CookieConfig, MemoryStorage, ServerSession, Session},
|
||||
web::{
|
||||
websocket::{Message, WebSocket},
|
||||
Data, Html, Path,
|
||||
},
|
||||
EndpointExt, IntoResponse, Route, Server,
|
||||
EndpointExt,
|
||||
IntoResponse,
|
||||
Route,
|
||||
Server,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use poem_openapi::{
|
||||
payload::Json, types::Password, ApiResponse, Object, OpenApi, OpenApiService, Tags,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
#[handler]
|
||||
fn index() -> Html<&'static str> {
|
||||
@ -94,8 +102,168 @@ fn ws(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Object, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
struct User {
|
||||
#[oai(validator(max_length = 64))]
|
||||
username: String,
|
||||
}
|
||||
#[derive(ApiResponse)]
|
||||
enum UserResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<User>),
|
||||
#[oai(status = 403)]
|
||||
AuthError,
|
||||
}
|
||||
#[derive(Debug, Object, Clone, Eq, PartialEq)]
|
||||
struct NewUser {
|
||||
/// Username
|
||||
#[oai(validator(max_length = 64))]
|
||||
username: String,
|
||||
/// Password
|
||||
#[oai(validator(max_length = 50))]
|
||||
password: Password,
|
||||
/// Invite / Referral Code
|
||||
#[oai(validator(max_length = 8))]
|
||||
referral: Option<String>,
|
||||
}
|
||||
#[derive(ApiResponse)]
|
||||
enum NewUserResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<User>),
|
||||
#[oai(status = 403)]
|
||||
UsernameTaken,
|
||||
#[oai(status = 403)]
|
||||
InvalidReferral,
|
||||
#[oai(status = 403)]
|
||||
SignupClosed,
|
||||
#[oai(status = 403)]
|
||||
InvalidPassword,
|
||||
#[oai(status = 500)]
|
||||
InternalServerError,
|
||||
}
|
||||
#[derive(Debug, Object, Clone, Eq, PartialEq)]
|
||||
struct UserLogin {
|
||||
/// Username
|
||||
#[oai(validator(max_length = 64))]
|
||||
username: String,
|
||||
/// Password
|
||||
#[oai(validator(max_length = 50))]
|
||||
password: Password,
|
||||
}
|
||||
#[derive(ApiResponse)]
|
||||
enum NumResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<u32>),
|
||||
#[oai(status = 403)]
|
||||
AuthError,
|
||||
}
|
||||
#[derive(Tags)]
|
||||
enum ApiTags {
|
||||
/// Operations about user
|
||||
User,
|
||||
}
|
||||
async fn valid_code(code: &str) -> bool {
|
||||
"changeme" == code
|
||||
}
|
||||
struct Api {
|
||||
db: Pool<Sqlite>,
|
||||
signup_open: bool, //TODO Should be in the db so it can change without restart
|
||||
}
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/user", method = "post", tag = "ApiTags::User")]
|
||||
async fn create_user(&self, user_form: Json<NewUser>, session: &Session) -> NewUserResponse {
|
||||
let has_referral = match &user_form.referral {
|
||||
Some(code) if valid_code(code).await => true,
|
||||
Some(_) => return NewUserResponse::InvalidReferral,
|
||||
None => false,
|
||||
};
|
||||
if !self.signup_open && !has_referral {
|
||||
return NewUserResponse::SignupClosed;
|
||||
}
|
||||
|
||||
//TODO propper handling of query errors
|
||||
let username = user_form.username.clone();
|
||||
let x = sqlx::query!("SELECT * FROM users WHERE username = ?", username)
|
||||
.fetch_optional(&self.db)
|
||||
.await;
|
||||
if let Ok(Some(_current_user)) = x {
|
||||
return NewUserResponse::UsernameTaken;
|
||||
}
|
||||
|
||||
let hash = match bcrypt::hash(user_form.password.as_str(), bcrypt::DEFAULT_COST) {
|
||||
Ok(hash) => hash,
|
||||
_ => return NewUserResponse::InvalidPassword,
|
||||
};
|
||||
|
||||
let res = sqlx::query!(
|
||||
"INSERT INTO users(username, password, admin) VALUES(?,?,?)",
|
||||
username,
|
||||
hash,
|
||||
false
|
||||
)
|
||||
.execute(&self.db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Err(_e) => NewUserResponse::InternalServerError,
|
||||
Ok(_) => {
|
||||
let user = User { username };
|
||||
session.set("user", user.username.clone());
|
||||
NewUserResponse::Ok(Json(user))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[oai(path = "/user", method = "get", tag = "ApiTags::User")]
|
||||
async fn get_user(&self, session: &Session) -> UserResponse {
|
||||
if let Some(username) = session.get::<String>("user") {
|
||||
UserResponse::Ok(Json(User { username }))
|
||||
} else {
|
||||
UserResponse::AuthError
|
||||
}
|
||||
}
|
||||
#[oai(path = "/auth", method = "post", tag = "ApiTags::User")]
|
||||
async fn auth_user(&self, user: Json<UserLogin>, session: &Session) -> UserResponse {
|
||||
let password = user.password.as_str();
|
||||
let username = user.username.to_string();
|
||||
|
||||
let result = sqlx::query!("SELECT * FROM users WHERE username = ?", username)
|
||||
.fetch_one(&self.db)
|
||||
.await;
|
||||
match result {
|
||||
Ok(user) if bcrypt::verify(password, &user.password).unwrap_or(false) => {
|
||||
let current_user = User {
|
||||
username: username.clone(),
|
||||
};
|
||||
session.set("user", username);
|
||||
UserResponse::Ok(Json(current_user))
|
||||
}
|
||||
_ => UserResponse::AuthError,
|
||||
}
|
||||
}
|
||||
|
||||
#[oai(path = "/num", method = "get", tag = "ApiTags::User")]
|
||||
//async fn get_num(&self, session: &Session) -> Json<u32> {
|
||||
async fn get_num(&self, session: &Session) -> NumResponse {
|
||||
//if session.get("user")
|
||||
if let None = session.get::<String>("user") {
|
||||
return NumResponse::AuthError;
|
||||
}
|
||||
match session.get("num") {
|
||||
Some(i) => {
|
||||
session.set("num", i + 1);
|
||||
NumResponse::Ok(Json(i))
|
||||
}
|
||||
None => {
|
||||
session.set("num", 1_u32);
|
||||
NumResponse::Ok(Json(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
if std::env::var_os("RUST_LOG").is_none() {
|
||||
std::env::set_var("RUST_LOG", "poem=debug");
|
||||
}
|
||||
@ -103,16 +271,26 @@ async fn main() -> Result<()> {
|
||||
|
||||
let session = ServerSession::new(CookieConfig::default(), MemoryStorage::new());
|
||||
|
||||
let dbpool = SqlitePool::connect("sqlite:chat.db").await?;
|
||||
//let dbpool = SqlitePool::connect("sqlite:chat.db").await?;
|
||||
let dbpool = Pool::<Sqlite>::connect("sqlite:chat.db").await?;
|
||||
let api = OpenApiService::new(
|
||||
Api {
|
||||
db: dbpool,
|
||||
signup_open: false,
|
||||
},
|
||||
"Chat",
|
||||
"0.0",
|
||||
);
|
||||
|
||||
let app = Route::new()
|
||||
.at("/api/", get(index))
|
||||
.at(
|
||||
"/api/ws/:name",
|
||||
get(ws.data(tokio::sync::broadcast::channel::<String>(32).0)),
|
||||
)
|
||||
.with(session)
|
||||
.data(dbpool);
|
||||
//.at("/api/", get(index))
|
||||
//.at(
|
||||
// "/api/ws/:name",
|
||||
// get(ws.data(tokio::sync::broadcast::channel::<String>(32).0)),
|
||||
//)
|
||||
//.data(dbpool)
|
||||
.nest("/api", api)
|
||||
.with(session);
|
||||
|
||||
Server::new(TcpListener::bind("127.0.0.1:3000"))
|
||||
.run(app)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user