Database integration WIP
This commit is contained in:
parent
f3e5cd62b1
commit
2bcda34f6a
13 changed files with 225 additions and 37 deletions
48
server/Cargo.lock
generated
48
server/Cargo.lock
generated
|
@ -286,6 +286,19 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
"password-hash",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atoi"
|
name = "atoi"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -343,6 +356,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -1294,6 +1316,17 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
@ -1571,6 +1604,7 @@ name = "server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"argon2",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1746,6 +1780,8 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlformat",
|
"sqlformat",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -1784,6 +1820,7 @@ dependencies = [
|
||||||
"sqlx-sqlite",
|
"sqlx-sqlite",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2037,6 +2074,17 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.9"
|
version = "0.7.9"
|
||||||
|
|
|
@ -7,11 +7,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.4.0"
|
actix-web = "4.4.0"
|
||||||
|
argon2 = { version = "0.5.2", features = ["zeroize"] }
|
||||||
clap = { version = "4.4.5", features = ["derive"] }
|
clap = { version = "4.4.5", features = ["derive"] }
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
sled = { version = "0.34.7" }
|
sled = { version = "0.34.7" }
|
||||||
sqlx = "0.7.2"
|
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio"] }
|
||||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
|
|
5
server/build.rs
Normal file
5
server/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// generated by `sqlx migrate build-script`
|
||||||
|
fn main() {
|
||||||
|
// trigger recompilation when a new migration is added
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
9
server/migrations/0001_users_table.sql
Normal file
9
server/migrations/0001_users_table.sql
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
create index users_username_index on users (username);
|
10
server/migrations/0002_posts_table.sql
Normal file
10
server/migrations/0002_posts_table.sql
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id SERIAL NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||||
|
);
|
||||||
|
create index IF NOT EXISTS posts_user_id_index on posts (user_id);
|
||||||
|
create index IF NOT EXISTS posts_id_index on posts (id);
|
|
@ -1,21 +1,31 @@
|
||||||
use actix_web::web::Data;
|
#![allow(dead_code, unused_imports)]
|
||||||
|
use actix_web::web::{Data, Query};
|
||||||
use actix_web::{web::scope, App, HttpServer};
|
use actix_web::{web::scope, App, HttpServer};
|
||||||
|
use log::info;
|
||||||
// use uuid::Uuid;
|
// use uuid::Uuid;
|
||||||
|
|
||||||
mod routes;
|
mod routes;
|
||||||
|
mod state;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
use log::info;
|
use routes::{get_posts, new_post, register, test};
|
||||||
use routes::{get_posts, new_post, test};
|
use sqlx::ConnectOptions;
|
||||||
use types::AppState;
|
use state::AppState;
|
||||||
|
|
||||||
|
use sqlx::{migrate::MigrateDatabase, query, sqlite};
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
name: String,
|
||||||
|
pass: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||||
info!("Starting server...");
|
|
||||||
|
|
||||||
let data = AppState::new();
|
let data = AppState::new().await;
|
||||||
|
|
||||||
|
info!("Spinning up server on http://localhost:8080");
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new().service(
|
App::new().service(
|
||||||
scope("api")
|
scope("api")
|
||||||
|
@ -23,6 +33,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(new_post)
|
.service(new_post)
|
||||||
.service(routes::vote)
|
.service(routes::vote)
|
||||||
.service(test)
|
.service(test)
|
||||||
|
.service(register)
|
||||||
.app_data(Data::new(data.clone())),
|
.app_data(Data::new(data.clone())),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::types::{AppState, NewPost, Post};
|
use crate::types::{NewPost, Post};
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
use actix_web::web::{Data, Path};
|
use actix_web::web::{Data, Path};
|
||||||
use actix_web::{get, post, web::Json, HttpResponse, Responder};
|
use actix_web::{get, post, web::Json, HttpResponse, Responder, Result};
|
||||||
|
use argon2::password_hash::SaltString;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -24,6 +27,13 @@ pub async fn new_post(new_post: Json<NewPost>, data: Data<AppState>) -> impl Res
|
||||||
let post = Post::from(new_post.into_inner());
|
let post = Post::from(new_post.into_inner());
|
||||||
info!("Created post {:?}", post.uuid);
|
info!("Created post {:?}", post.uuid);
|
||||||
|
|
||||||
|
// let q = "INSERT INTO posts (uuid, content, upvotes, downvotes) VALUES (?, ?, ?, ?)";
|
||||||
|
// let query = sqlx::query(q)
|
||||||
|
// .bind(post.uuid)
|
||||||
|
// .bind(post.content)
|
||||||
|
// .bind(post.votes.up)
|
||||||
|
// .bind(post.votes.down);
|
||||||
|
|
||||||
match data.posts.lock() {
|
match data.posts.lock() {
|
||||||
Ok(mut posts) => {
|
Ok(mut posts) => {
|
||||||
posts.insert(post.uuid, post);
|
posts.insert(post.uuid, post);
|
||||||
|
@ -86,3 +96,44 @@ pub async fn vote(params: Path<(Uuid, VoteDirection)>, data: Data<AppState>) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RegisterData {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
captcha: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
use argon2::password_hash::rand_core::OsRng;
|
||||||
|
use argon2::password_hash::*;
|
||||||
|
use argon2::Algorithm;
|
||||||
|
use argon2::Argon2;
|
||||||
|
use argon2::PasswordHasher;
|
||||||
|
use argon2::PasswordVerifier;
|
||||||
|
use argon2::Version;
|
||||||
|
|
||||||
|
#[post("/register")]
|
||||||
|
pub async fn register(data: Json<RegisterData>, state: Data<AppState>) -> Result<impl Responder> {
|
||||||
|
let q = "SELECT password FROM users WHERE username = ?";
|
||||||
|
let query = sqlx::query(q).bind(&data.username);
|
||||||
|
let result = query.fetch_one(&state.pool).await.ok();
|
||||||
|
if result.is_some() {
|
||||||
|
info!("User \"{}\" already exists", data.username);
|
||||||
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = data.password.clone();
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let phc_hash = Argon2::default().hash_password(password.as_bytes(), &salt);
|
||||||
|
if let Ok(phc_hash) = phc_hash {
|
||||||
|
info!("User: {} registered", data.username);
|
||||||
|
let phc_hash = phc_hash.to_string();
|
||||||
|
let q = "INSERT INTO users (username, password) VALUES (?, ?)";
|
||||||
|
let query = sqlx::query(q).bind(&data.username).bind(&phc_hash);
|
||||||
|
query.execute(&state.pool).await.unwrap();
|
||||||
|
} else {
|
||||||
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json("User registered"))
|
||||||
|
}
|
||||||
|
|
31
server/src/state.rs
Normal file
31
server/src/state.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::types::Post;
|
||||||
|
use sqlx::Sqlite;
|
||||||
|
use sqlx::{self, sqlite};
|
||||||
|
use sqlx::{AnyPool, Pool};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub posts: Arc<Mutex<BTreeMap<Uuid, Post>>>,
|
||||||
|
pub pool: Pool<Sqlite>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub async fn new() -> Self {
|
||||||
|
let pool = sqlite::SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect(":memory:")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
posts: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
|
pool: pool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,16 +4,17 @@ use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
// The post as it is received from the client
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct NewPost {
|
pub struct NewPost {
|
||||||
title: String,
|
|
||||||
content: String,
|
content: String,
|
||||||
|
token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The post as it is stored in the database
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub title: String,
|
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub votes: VoteCount,
|
pub votes: VoteCount,
|
||||||
}
|
}
|
||||||
|
@ -22,26 +23,12 @@ impl From<NewPost> for Post {
|
||||||
fn from(post: NewPost) -> Self {
|
fn from(post: NewPost) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid: Uuid::new_v4(),
|
uuid: Uuid::new_v4(),
|
||||||
title: post.title,
|
|
||||||
content: post.content,
|
content: post.content,
|
||||||
votes: VoteCount::new(),
|
votes: VoteCount::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AppState {
|
|
||||||
pub posts: Arc<Mutex<BTreeMap<Uuid, Post>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
posts: Arc::new(Mutex::new(BTreeMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Part of the post struct
|
// Part of the post struct
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct VoteCount {
|
pub struct VoteCount {
|
||||||
|
|
|
@ -10,6 +10,20 @@ import { useContext } from "react";
|
||||||
import { LoginContext } from "./Context";
|
import { LoginContext } from "./Context";
|
||||||
// import { TestContext } from "./Context";
|
// import { TestContext } from "./Context";
|
||||||
|
|
||||||
|
interface RegisterData {
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
captcha: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRegister(data: RegisterData): void {
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
fetch("/api/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function LoginDialog(): JSX.Element {
|
function LoginDialog(): JSX.Element {
|
||||||
// const [open, setOpen] = useState(openState);
|
// const [open, setOpen] = useState(openState);
|
||||||
|
@ -27,6 +41,10 @@ function LoginDialog(): JSX.Element {
|
||||||
console.log(username, password);
|
console.log(username, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRegister = (): void => {
|
||||||
|
sendRegister({ username: username, password: password, captcha: "test" });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={loginCTX.loginModalOpen} onClose={handleClose} sx={{ p: 2, top: "-40%" }}>
|
<Dialog open={loginCTX.loginModalOpen} onClose={handleClose} sx={{ p: 2, top: "-40%" }}>
|
||||||
<DialogTitle>Login</DialogTitle>
|
<DialogTitle>Login</DialogTitle>
|
||||||
|
@ -57,6 +75,9 @@ function LoginDialog(): JSX.Element {
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleLogin}>Login</Button>
|
<Button onClick={handleLogin}>Login</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleRegister}>Register</Button>
|
||||||
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ export interface Post {
|
||||||
votes: { up: number, down: number },
|
votes: { up: number, down: number },
|
||||||
}
|
}
|
||||||
|
|
||||||
const URL = "http://localhost:8080/api/";
|
// const URL = "http://localhost:8080/api/";
|
||||||
|
|
||||||
function sendVote(post: Post, direction: string): void {
|
function sendVote(post: Post, direction: string): void {
|
||||||
fetch(URL + 'vote/' + post.uuid + '/' + direction, { method: 'POST' });
|
fetch('/api/vote/' + post.uuid + '/' + direction, { method: 'POST' });
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VoteDirection { UP = 1, DOWN = -1, NONE = 0 }
|
enum VoteDirection { UP = 1, DOWN = -1, NONE = 0 }
|
||||||
|
|
|
@ -8,7 +8,7 @@ function Primary(): JSX.Element {
|
||||||
const [posts, setPosts] = useState<Post[]>([]);
|
const [posts, setPosts] = useState<Post[]>([]);
|
||||||
|
|
||||||
const refreshPosts = (): void => {
|
const refreshPosts = (): void => {
|
||||||
fetch("http://localhost:8080/api/").then((response): void => {
|
fetch("/api/").then((response): void => {
|
||||||
response.json().then((data): void => {
|
response.json().then((data): void => {
|
||||||
setPosts(data);
|
setPosts(data);
|
||||||
})
|
})
|
||||||
|
@ -41,19 +41,22 @@ function PostContainer({ posts }: { posts: Post[] }): JSX.Element {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NewPost {
|
||||||
|
content: string,
|
||||||
|
token: string,
|
||||||
|
}
|
||||||
|
|
||||||
function PostInput({ newPostCallback }: { newPostCallback: () => void }): JSX.Element {
|
function PostInput({ newPostCallback }: { newPostCallback: () => void }): JSX.Element {
|
||||||
const [cinput, setCinput] = useState("");
|
const [currentInput, setCurrentInput] = useState("");
|
||||||
const [title, setTitle] = useState("");
|
|
||||||
|
|
||||||
const handleSubmit = (): void => {
|
const handleSubmit = (): void => {
|
||||||
if (cinput && title) submitPostToServer();
|
if (currentInput) submitPostToServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitPostToServer = async (): Promise<void> => {
|
const submitPostToServer = async (): Promise<void> => {
|
||||||
const newPost = { title: title, content: cinput };
|
const newPost: NewPost = { content: currentInput, token: "" }
|
||||||
|
|
||||||
const response = await fetch("http://localhost:8080/api/", {
|
const response = await fetch("/api/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -63,16 +66,15 @@ function PostInput({ newPostCallback }: { newPostCallback: () => void }): JSX.El
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
newPostCallback();
|
newPostCallback();
|
||||||
setCinput("");
|
setCurrentInput("");
|
||||||
setTitle("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={2} display={"flex"} flexDirection={"column"} width={1}>
|
<Box py={2} display={"flex"} flexDirection={"column"} width={1}>
|
||||||
<Paper sx={{ p: 2, display: "flex", flexDirection: "column", alignContent: "center", alignItems: "center" }} elevation={4}>
|
<Paper sx={{ p: 2, display: "flex", flexDirection: "column", alignContent: "center", alignItems: "center" }} elevation={4}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<TextField sx={{pb:1}} size="small" id="outlined-basic" label="Title" variant="outlined" value={title} fullWidth onChange={(a): void => setTitle(a.target.value)} />
|
{/* <TextField sx={{pb:1}} size="small" id="outlined-basic" label="Title" variant="outlined" value={title} fullWidth onChange={(a): void => setTitle(a.target.value)} /> */}
|
||||||
<TextField multiline id="outlined-basic" label="Share your thoughts" variant="outlined" minRows={4} value={cinput} fullWidth onChange={(a): void => setCinput(a.target.value)} />
|
<TextField multiline id="outlined-basic" label="Share your thoughts" variant="outlined" minRows={4} value={currentInput} fullWidth onChange={(a): void => setCurrentInput(a.target.value)} />
|
||||||
<Container disableGutters sx={{ mt:1, display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "center", width: "100%" }}>
|
<Container disableGutters sx={{ mt:1, display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "center", width: "100%" }}>
|
||||||
<Button sx={{mr:1}} type="reset" startIcon={<CancelIcon/>} variant="outlined" onClick={handleSubmit}>Cancel</Button>
|
<Button sx={{mr:1}} type="reset" startIcon={<CancelIcon/>} variant="outlined" onClick={handleSubmit}>Cancel</Button>
|
||||||
<Button type="submit" startIcon={<SendIcon />} variant="contained" onClick={handleSubmit}>Send</Button>
|
<Button type="submit" startIcon={<SendIcon />} variant="contained" onClick={handleSubmit}>Send</Button>
|
||||||
|
|
|
@ -5,4 +5,16 @@ import { qrcode } from 'vite-plugin-qrcode'
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), qrcode()],
|
plugins: [react(), qrcode()],
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080/api',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
rewrite: (path): string => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue