Inital commit
This commit is contained in:
commit
88aacacb19
13 changed files with 3908 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
.idea
|
||||||
|
server-config.json
|
||||||
|
client-config.json
|
3617
Cargo.lock
generated
Normal file
3617
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "fedichat"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
figment = { version = "0.10", features = ["json", "env"] }
|
||||||
|
futures-util = "0.3.30"
|
||||||
|
serde = { version = "1.0", features = ["derive", "alloc", "std"] }
|
||||||
|
serde_json = { version = "1.0" , features = ["default"]}
|
||||||
|
tokio = { version = "1.37.0", features = ["full", "rt-multi-thread"]}
|
||||||
|
tokio-tungstenite = { version = "0.21.0", features = ["default", "rustls"]}
|
||||||
|
iced = { version = "0.12", features = ["default"]}
|
||||||
|
iced_futures = { version = "0.12", features = ["tokio"] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "client"
|
||||||
|
path = "src/client/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "server"
|
||||||
|
path = "src/server/main.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "common"
|
||||||
|
path = "src/common/lib.rs"
|
0
client-config.json.example
Normal file
0
client-config.json.example
Normal file
4
server-config.json.example
Normal file
4
server-config.json.example
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 8000
|
||||||
|
}
|
85
src/client/main.rs
Normal file
85
src/client/main.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
mod websocket;
|
||||||
|
|
||||||
|
use iced::widget::{column, Column, button, text_input, text};
|
||||||
|
use iced::{Application, Command, Element, Pixels, settings::Settings};
|
||||||
|
use iced_futures::Subscription;
|
||||||
|
use websocket::websocket;
|
||||||
|
use crate::websocket::AppWebsocketConfig;
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
|
let settings = Settings {
|
||||||
|
id: None,
|
||||||
|
window: Default::default(),
|
||||||
|
flags: (),
|
||||||
|
fonts: vec![],
|
||||||
|
default_font: Default::default(),
|
||||||
|
default_text_size: Pixels(16.0),
|
||||||
|
antialiasing: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
ChatApp::run(settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ChatApp {
|
||||||
|
host: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ChatAppEvent {
|
||||||
|
ConnectButtonPress,
|
||||||
|
HostTextInputChanged(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatApp {
|
||||||
|
fn view_auth(&self) -> Element<<ChatApp as Application>::Message> {
|
||||||
|
column![
|
||||||
|
text_input("Host", &self.host)
|
||||||
|
.on_input(ChatAppEvent::HostTextInputChanged)
|
||||||
|
.on_submit(ChatAppEvent::ConnectButtonPress),
|
||||||
|
button("Connect")
|
||||||
|
.on_press(ChatAppEvent::ConnectButtonPress)
|
||||||
|
].into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for ChatApp {
|
||||||
|
type Executor = iced_futures::backend::native::tokio::Executor;
|
||||||
|
type Message = ChatAppEvent;
|
||||||
|
type Theme = iced::theme::Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
|
(Self {
|
||||||
|
host: String::from("127.0.0.1:3000"),
|
||||||
|
}, Command::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Chat app")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: ChatAppEvent) -> Command<Self::Message> {
|
||||||
|
println!("update");
|
||||||
|
match message {
|
||||||
|
ChatAppEvent::ConnectButtonPress => {
|
||||||
|
println!("ConnectButtonPress")
|
||||||
|
},
|
||||||
|
ChatAppEvent::HostTextInputChanged(host_value) => {
|
||||||
|
self.host = host_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Self::Message> {
|
||||||
|
println!("view");
|
||||||
|
self.view_auth()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
println!("subscription");
|
||||||
|
websocket(AppWebsocketConfig::new(self.host.clone(), None, None))
|
||||||
|
}
|
||||||
|
}
|
40
src/client/websocket.rs
Normal file
40
src/client/websocket.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use iced::subscription::{Subscription, channel};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum AppWebsocketState {
|
||||||
|
Disconnected,
|
||||||
|
Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct AppWebsocketConfig {
|
||||||
|
host: String,
|
||||||
|
user: Option<String>,
|
||||||
|
pass: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWebsocketConfig {
|
||||||
|
pub fn new(host: String, user: Option<String>, pass: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
host,
|
||||||
|
user,
|
||||||
|
pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn websocket(config: AppWebsocketConfig) -> Subscription<super::ChatAppEvent> {
|
||||||
|
struct Websocket;
|
||||||
|
|
||||||
|
channel(
|
||||||
|
std::any::TypeId::of::<Websocket>(),
|
||||||
|
100,
|
||||||
|
|mut output| async move {
|
||||||
|
let mut state = AppWebsocketState::Disconnected;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
2
src/common/lib.rs
Normal file
2
src/common/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod packets;
|
||||||
|
pub mod user;
|
10
src/common/packets.rs
Normal file
10
src/common/packets.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Message {
|
||||||
|
Text { chat: (), content: String }
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub struct GroupPermissions {}
|
||||||
|
// pub struct UserPermissions {}
|
||||||
|
// pub struct RoomPermissions {}
|
4
src/common/user.rs
Normal file
4
src/common/user.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
struct User {
|
||||||
|
id: u32,
|
||||||
|
username: String
|
||||||
|
}
|
7
src/server/config.rs
Normal file
7
src/server/config.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16
|
||||||
|
}
|
13
src/server/db.rs
Normal file
13
src/server/db.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
trait DatabaseProvider {
|
||||||
|
fn message_create();
|
||||||
|
fn message_edit();
|
||||||
|
fn message_delete();
|
||||||
|
|
||||||
|
fn chat_create();
|
||||||
|
fn chat_edit();
|
||||||
|
fn chat_delete();
|
||||||
|
|
||||||
|
fn user_create();
|
||||||
|
fn user_edit();
|
||||||
|
fn user_delete();
|
||||||
|
}
|
93
src/server/main.rs
Normal file
93
src/server/main.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
mod config;
|
||||||
|
mod db;
|
||||||
|
|
||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
use tokio::{net::{TcpListener, TcpStream}, sync::Mutex};
|
||||||
|
use crate::config::Config;
|
||||||
|
use figment::{Figment, providers::{Format, Env, Json}};
|
||||||
|
use futures_util::{StreamExt, stream::SplitSink, TryStreamExt, };
|
||||||
|
use tokio_tungstenite::{WebSocketStream, tungstenite::Message as TungsteniteMessage};
|
||||||
|
use common::packets::Message;
|
||||||
|
|
||||||
|
type PeerList = Arc<Mutex<Vec<Peer>>>;
|
||||||
|
|
||||||
|
pub struct Peer {
|
||||||
|
pub addr: SocketAddr,
|
||||||
|
pub websocket_tx: SplitSink<WebSocketStream<TcpStream>, TungsteniteMessage>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peer {
|
||||||
|
fn new(addr: SocketAddr, websocket_tx: SplitSink<WebSocketStream<TcpStream>, TungsteniteMessage>) -> Self {
|
||||||
|
Self {
|
||||||
|
addr,
|
||||||
|
websocket_tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let config: Config = Figment::new()
|
||||||
|
.merge(Json::file("server-config.json"))
|
||||||
|
.merge(Env::prefixed("APP_"))
|
||||||
|
.extract()?;
|
||||||
|
|
||||||
|
let peer_list = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let tcp_listener = TcpListener::bind(format!("{}:{}", config.host, config.port)).await?;
|
||||||
|
println!("Listening on {}:{}", config.host, config.port);
|
||||||
|
|
||||||
|
while let Ok((stream, addr)) = tcp_listener.accept().await {
|
||||||
|
println!("Connection from {}", &addr);
|
||||||
|
tokio::spawn(handle_connection(stream, addr, peer_list.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection(stream: TcpStream, addr: SocketAddr, peer_list: PeerList) {
|
||||||
|
let websocket_stream = tokio_tungstenite::accept_async(stream)
|
||||||
|
.await
|
||||||
|
.expect("Could not initialize websocket stream");
|
||||||
|
|
||||||
|
let (mut websocket_tx, mut websocket_rx) = websocket_stream.split();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut locked_peer_list = peer_list.lock().await;
|
||||||
|
locked_peer_list.push(Peer::new(addr, websocket_tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(socket_result) = websocket_rx.next().await {
|
||||||
|
match socket_result {
|
||||||
|
Ok(websocket_message) => {
|
||||||
|
match websocket_message {
|
||||||
|
TungsteniteMessage::Text(text_message) => {
|
||||||
|
match serde_json::from_str(&text_message) {
|
||||||
|
Ok(message) => {
|
||||||
|
handle_message(message, addr, peer_list.clone())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("ParseError: {err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Websocket: Unsupported message type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("SocketError: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message(message: Message, sender: SocketAddr, peer_list: PeerList) {
|
||||||
|
match message {
|
||||||
|
Message::Text { chat, content} => {
|
||||||
|
println!("{chat}: {content}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue