first commit

This commit is contained in:
2025-08-25 22:41:07 +02:00
commit 0bef2b69c2
25 changed files with 6007 additions and 0 deletions

1
back/.env Normal file
View File

@@ -0,0 +1 @@
DATABASE_URL="mysql://root:root@127.0.0.1:3306/liscord_db"

1
back/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1062
back/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
back/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "back"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = "0.8.4"
diesel = { version = "2.2.0", features = ["mysql", "serde_json"] }
dotenvy = "0.15"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.143"
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }

9
back/diesel.toml Normal file
View File

@@ -0,0 +1,9 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory]
dir = "/home/clement/projects/liscord/back/migrations"

View File

@@ -0,0 +1,13 @@
services:
db:
image: mariadb:latest
volumes:
- /liscord:/var/lib/mysql/data
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: liscord_db
volumes:
db_data:

0
back/migrations/.keep Normal file
View File

View File

@@ -0,0 +1,6 @@
DROP TABLE IF EXISTS user_roles;
DROP TABLE IF EXISTS server_members;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS channels;
DROP TABLE IF EXISTS servers;
DROP TABLE IF EXISTS users;

View File

@@ -0,0 +1,73 @@
-- Your SQL goes here
-- Table des utilisateurs
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table des serveurs
CREATE TABLE servers (
server_id INT AUTO_INCREMENT PRIMARY KEY,
server_name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table des canaux
CREATE TABLE channels (
channel_id INT AUTO_INCREMENT PRIMARY KEY,
server_id INT NOT NULL,
channel_name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (server_id) REFERENCES servers(server_id)
);
-- Table des messages
CREATE TABLE messages (
message_id INT AUTO_INCREMENT PRIMARY KEY,
channel_id INT NOT NULL,
user_id INT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (channel_id) REFERENCES channels(channel_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- Table des membres de serveur
CREATE TABLE server_members (
server_member_id INT AUTO_INCREMENT PRIMARY KEY,
server_id INT NOT NULL,
user_id INT NOT NULL,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (server_id) REFERENCES servers(server_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- Table des membres de canal
CREATE TABLE channel_members (
channel_member_id INT AUTO_INCREMENT PRIMARY KEY,
channel_id INT NOT NULL,
user_id INT NOT NULL,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (channel_id) REFERENCES channels(channel_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- Table des rôles
CREATE TABLE roles (
role_id INT AUTO_INCREMENT PRIMARY KEY,
server_id INT NOT NULL,
role_name VARCHAR(50) NOT NULL,
FOREIGN KEY (server_id) REFERENCES servers(server_id)
);
-- Table des relations utilisateur-rôle
CREATE TABLE user_roles (
user_role_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id)
);

View File

@@ -0,0 +1,24 @@
use back::*;
use std::env::args;
fn main() {
let id = args()
.nth(1)
.expect("This action requires an id")
.parse::<i32>()
.expect("Invalid id");
let new_email = args().nth(2).expect("This action requires an email");
if !new_email.contains("@") {
panic!("Email invalid");
}
let connection = &mut establish_connection();
if let Ok(user) = change_user_email(id, new_email, connection) {
debug(format!("Email changed for user {}", user.username));
} else {
debug("Problem while trying to change email");
}
}

View File

@@ -0,0 +1,40 @@
use back::{models::NewUser, *};
use std::io::stdin;
fn main() {
let connection = &mut establish_connection();
let mut username = String::new();
let mut password = String::new();
let mut email = String::new();
println!("Username: ");
stdin().read_line(&mut username).unwrap();
let username = username.trim_end();
println!("Email: ",);
stdin().read_line(&mut email).unwrap();
let email = email.trim_end();
println!("Password: ",);
stdin().read_line(&mut password).unwrap();
let password = password.trim_end();
let new_user = NewUser {
username: username.to_owned(),
email: email.to_owned(),
password_hash: password.to_owned(),
};
if let Ok(user) = create_user(connection, &new_user) {
debug(format!("\nSaved user {username} with id {}", user.user_id));
} else {
debug("Saving failed");
}
}
// #[cfg(not(windows))]
// const EOF: &str = "CTRL+D";
// #[cfg(windows)]
// const EOF: &str = "CTRL+Z";

View File

@@ -0,0 +1,15 @@
use std::env::args;
use back::*;
fn main() {
let target_username = args().nth(1).expect("Expected a username");
let connection = &mut establish_connection();
if let Ok(username) = delete_user(target_username, connection) {
debug(format!("User {username} deleted"));
} else {
debug("Deletion failed");
}
}

View File

@@ -0,0 +1,19 @@
use back::establish_connection;
use back::models::*;
use diesel::prelude::*;
fn main() {
use back::schema::users::dsl::*;
let connection = &mut establish_connection();
let results = users
.limit(5)
.select(User::as_select())
.load(connection)
.expect("Error loading posts");
println!("Displaying {} posts", results.len());
for user in results {
println!("{}, {}", user.username, user.email);
}
}

70
back/src/lib.rs Normal file
View File

@@ -0,0 +1,70 @@
use diesel::prelude::*;
use dotenvy::dotenv;
use std::{env, fmt::Display};
use crate::models::{NewUser, User};
pub mod models;
pub mod schema;
// use crate::crud_macros::make_crud;
type Error = diesel::result::Error;
pub fn debug(s: impl Display) {
#[cfg(debug_assertions)]
{
println!("{s}");
}
}
pub fn establish_connection() -> MysqlConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
MysqlConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {database_url}"))
}
pub fn create_user(conn: &mut MysqlConnection, new_user: &NewUser) -> Result<User, Error> {
use crate::schema::users;
conn.transaction(|conn| {
diesel::insert_into(users::table)
.values(new_user)
.execute(conn)?;
users::table
.order(users::user_id.desc())
.select(User::as_select())
.first(conn)
})
}
pub fn delete_user(
target_username: String,
connection: &mut MysqlConnection,
) -> Result<String, Error> {
use crate::schema::users::dsl::*;
let nb_del = diesel::delete(users.filter(username.eq(&target_username))).execute(connection)?;
if nb_del > 0 {
Ok(target_username)
} else {
Err(diesel::result::Error::NotFound)
}
}
pub fn change_user_email(
id: i32,
new_email: String,
connection: &mut MysqlConnection,
) -> Result<User, Error> {
use crate::schema::users::dsl::*;
connection.transaction(|connection| {
let user = users.find(id).select(User::as_select()).first(connection)?;
diesel::update(users.find(id))
.set(email.eq(&new_email))
.execute(connection)?;
Ok(user)
})
}

32
back/src/main.rs Normal file
View File

@@ -0,0 +1,32 @@
use axum::{
Json, Router,
response::IntoResponse,
routing::{get, post},
};
use back::{create_user, establish_connection, models::NewUser};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(hello))
.route("/create_user", post(create_user_api));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn hello() -> &'static str {
"Hello !"
}
async fn create_user_api(Json(payload): Json<NewUser>) -> impl IntoResponse {
let mut conn = establish_connection();
match create_user(&mut conn, &payload) {
Ok(user) => (axum::http::StatusCode::CREATED, axum::Json(user)).into_response(),
Err(e) => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create user: {e}"),
)
.into_response(),
}
}

50
back/src/models.rs Normal file
View File

@@ -0,0 +1,50 @@
use crate::schema::*;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = users)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct User {
pub user_id: i32,
pub username: String,
pub password_hash: String,
pub email: String,
}
#[derive(Insertable, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct NewUser {
pub username: String,
pub password_hash: String,
pub email: String,
}
#[derive(Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = servers)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct Server {
pub server_id: i32,
pub server_name: String,
}
#[derive(Insertable, Serialize, Deserialize)]
#[diesel(table_name = servers)]
pub struct NewServer {
pub server_name: String,
}
#[derive(Queryable, Serialize, Deserialize)]
#[diesel(table_name = channels)]
pub struct Channel {
pub channel_id: i32,
pub server_id: i32,
pub channel_name: String,
}
#[derive(Insertable, Serialize, Deserialize)]
#[diesel(table_name = channels)]
pub struct NewChannel {
pub server_id: i32,
pub channel_name: String,
}

100
back/src/schema.rs Normal file
View File

@@ -0,0 +1,100 @@
// @generated automatically by Diesel CLI.
diesel::table! {
channel_members (channel_member_id) {
channel_member_id -> Integer,
channel_id -> Integer,
user_id -> Integer,
joined_at -> Nullable<Timestamp>,
}
}
diesel::table! {
channels (channel_id) {
channel_id -> Integer,
server_id -> Integer,
#[max_length = 100]
channel_name -> Varchar,
created_at -> Nullable<Timestamp>,
}
}
diesel::table! {
messages (message_id) {
message_id -> Integer,
channel_id -> Integer,
user_id -> Integer,
content -> Text,
created_at -> Nullable<Timestamp>,
}
}
diesel::table! {
roles (role_id) {
role_id -> Integer,
server_id -> Integer,
#[max_length = 50]
role_name -> Varchar,
}
}
diesel::table! {
server_members (server_member_id) {
server_member_id -> Integer,
server_id -> Integer,
user_id -> Integer,
joined_at -> Nullable<Timestamp>,
}
}
diesel::table! {
servers (server_id) {
server_id -> Integer,
#[max_length = 100]
server_name -> Varchar,
created_at -> Nullable<Timestamp>,
}
}
diesel::table! {
user_roles (user_role_id) {
user_role_id -> Integer,
user_id -> Integer,
role_id -> Integer,
}
}
diesel::table! {
users (user_id) {
user_id -> Integer,
#[max_length = 50]
username -> Varchar,
#[max_length = 255]
password_hash -> Varchar,
#[max_length = 100]
email -> Varchar,
created_at -> Nullable<Timestamp>,
}
}
diesel::joinable!(channel_members -> channels (channel_id));
diesel::joinable!(channel_members -> users (user_id));
diesel::joinable!(channels -> servers (server_id));
diesel::joinable!(messages -> channels (channel_id));
diesel::joinable!(messages -> users (user_id));
diesel::joinable!(roles -> servers (server_id));
diesel::joinable!(server_members -> servers (server_id));
diesel::joinable!(server_members -> users (user_id));
diesel::joinable!(user_roles -> roles (role_id));
diesel::joinable!(user_roles -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!(
channel_members,
channels,
messages,
roles,
server_members,
servers,
user_roles,
users,
);