first commit
This commit is contained in:
1
back/.env
Normal file
1
back/.env
Normal file
@@ -0,0 +1 @@
|
||||
DATABASE_URL="mysql://root:root@127.0.0.1:3306/liscord_db"
|
||||
1
back/.gitignore
vendored
Normal file
1
back/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1062
back/Cargo.lock
generated
Normal file
1062
back/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
back/Cargo.toml
Normal file
12
back/Cargo.toml
Normal 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
9
back/diesel.toml
Normal 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"
|
||||
13
back/docker/docker-compose.yaml
Normal file
13
back/docker/docker-compose.yaml
Normal 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
0
back/migrations/.keep
Normal file
6
back/migrations/2025-08-17-115759_create_base/down.sql
Normal file
6
back/migrations/2025-08-17-115759_create_base/down.sql
Normal 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;
|
||||
73
back/migrations/2025-08-17-115759_create_base/up.sql
Normal file
73
back/migrations/2025-08-17-115759_create_base/up.sql
Normal 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)
|
||||
);
|
||||
24
back/src/bin/change_user_email.rs
Normal file
24
back/src/bin/change_user_email.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
40
back/src/bin/create_user.rs
Normal file
40
back/src/bin/create_user.rs
Normal 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";
|
||||
15
back/src/bin/delete_user.rs
Normal file
15
back/src/bin/delete_user.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
19
back/src/bin/show_users.rs
Normal file
19
back/src/bin/show_users.rs
Normal 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
70
back/src/lib.rs
Normal 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
32
back/src/main.rs
Normal 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
50
back/src/models.rs
Normal 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
100
back/src/schema.rs
Normal 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,
|
||||
);
|
||||
Reference in New Issue
Block a user