diff --git a/Cargo.lock b/Cargo.lock
index 51114f1..d01f843 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 74e0f4d..4b242c0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
+
diff --git a/client/index.html b/client/index.html
index ac30053..8e76679 100644
--- a/client/index.html
+++ b/client/index.html
@@ -1,17 +1,16 @@
-
-
-
-
- Vite App
-
-
-
-
Hello, Bootstrap and Vite!
- Primary button
-
-
-
-
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
+
diff --git a/client/src/App.vue b/client/src/App.vue
index 633a5df..0c7c639 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -1,47 +1,35 @@
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Loading...
+
+
-
-
diff --git a/client/src/components/Clicker.vue b/client/src/components/Clicker.vue
new file mode 100644
index 0000000..7c474d1
--- /dev/null
+++ b/client/src/components/Clicker.vue
@@ -0,0 +1,65 @@
+
+
+
+
+ You have accessed this content {{ count }} times this session.
+ Refresh Content
+
+
+
+
diff --git a/client/src/components/HelloWorld.vue b/client/src/components/HelloWorld.vue
deleted file mode 100644
index 5fb372c..0000000
--- a/client/src/components/HelloWorld.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
{{ msg }}
-
- You’ve successfully created a project with
- Vite +
- Vue 3 .
-
-
-
-
-
diff --git a/client/src/components/Login.vue b/client/src/components/Login.vue
new file mode 100644
index 0000000..3ce2f40
--- /dev/null
+++ b/client/src/components/Login.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Signup.vue b/client/src/components/Signup.vue
new file mode 100644
index 0000000..64ef0b6
--- /dev/null
+++ b/client/src/components/Signup.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/TheWelcome.vue b/client/src/components/TheWelcome.vue
deleted file mode 100644
index dab9536..0000000
--- a/client/src/components/TheWelcome.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
-
-
-
- Documentation
-
- Vue’s
- official documentation
- provides you with all information you need to get started.
-
-
-
-
-
-
- Tooling
-
- This project is served and bundled with
- Vite . The
- recommended IDE setup is
- VSCode +
- Volar . If
- you need to test your components and web pages, check out
- Cypress and
- Cypress Component Testing .
-
-
-
- More instructions are available in README.md.
-
-
-
-
-
-
- Ecosystem
-
- Get official tools and libraries for your project:
- Pinia ,
- Vue Router ,
- Vue Test Utils , and
- Vue Dev Tools . If
- you need more resources, we suggest paying
- Awesome Vue
- a visit.
-
-
-
-
-
-
- Community
-
- Got stuck? Ask your question on
- Vue Land , our official
- Discord server, or
- StackOverflow . You should also subscribe to
- our mailing list and follow
- the official
- @vuejs
- twitter account for latest news in the Vue world.
-
-
-
-
-
-
- Support Vue
-
- As an independent project, Vue relies on community backing for its sustainability. You can help
- us by
- becoming a sponsor .
-
-
diff --git a/client/src/components/WelcomeItem.vue b/client/src/components/WelcomeItem.vue
deleted file mode 100644
index 6d7086a..0000000
--- a/client/src/components/WelcomeItem.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
diff --git a/src/main.rs b/src/main.rs
index e6c07c5..7aab609 100644
--- a/src/main.rs
+++ b/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),
+ #[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,
+}
+#[derive(ApiResponse)]
+enum NewUserResponse {
+ #[oai(status = 200)]
+ Ok(Json),
+ #[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),
+ #[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,
+ 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, 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::("user") {
+ UserResponse::Ok(Json(User { username }))
+ } else {
+ UserResponse::AuthError
+ }
+ }
+ #[oai(path = "/auth", method = "post", tag = "ApiTags::User")]
+ async fn auth_user(&self, user: Json, 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 {
+ async fn get_num(&self, session: &Session) -> NumResponse {
+ //if session.get("user")
+ if let None = session.get::("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::::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::(32).0)),
- )
- .with(session)
- .data(dbpool);
+ //.at("/api/", get(index))
+ //.at(
+ // "/api/ws/:name",
+ // get(ws.data(tokio::sync::broadcast::channel::(32).0)),
+ //)
+ //.data(dbpool)
+ .nest("/api", api)
+ .with(session);
Server::new(TcpListener::bind("127.0.0.1:3000"))
.run(app)