Подключение к Postgres и тестовый эндпоинт авторизации

This commit is contained in:
2025-03-22 03:20:55 +04:00
parent 3cf42eea8a
commit 9f7460973e
24 changed files with 1091 additions and 47 deletions

2
src/database/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod schema;
pub mod models;

26
src/database/models.rs Normal file
View File

@@ -0,0 +1,26 @@
use diesel::prelude::*;
use serde::Serialize;
#[derive(diesel_derive_enum::DbEnum, Serialize, Debug)]
#[ExistingTypePath = "crate::database::schema::sql_types::UserRole"]
#[DbValueStyle = "UPPERCASE"]
#[serde(rename_all = "UPPERCASE")]
pub enum UserRole {
Student,
Teacher,
Admin,
}
#[derive(Queryable, Selectable, Serialize)]
#[diesel(table_name = crate::database::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
pub id: String,
pub username: String,
pub password: String,
pub vk_id: Option<i32>,
pub access_token: String,
pub group: String,
pub role: UserRole,
pub version: String,
}

38
src/database/schema.rs Normal file
View File

@@ -0,0 +1,38 @@
// @generated automatically by Diesel CLI.
pub mod sql_types {
#[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "user_role"))]
pub struct UserRole;
}
diesel::table! {
fcm (user_id) {
user_id -> Text,
token -> Text,
topics -> Nullable<Array<Nullable<Text>>>,
}
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::UserRole;
users (id) {
id -> Text,
username -> Text,
password -> Text,
vk_id -> Nullable<Int4>,
access_token -> Text,
group -> Text,
role -> UserRole,
version -> Text,
}
}
diesel::joinable!(fcm -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!(
fcm,
users,
);

View File

@@ -1,39 +1,57 @@
use crate::routes::auth::sign_in::sign_in;
use crate::xls_downloader::basic_impl::BasicXlsDownloader;
use crate::xls_downloader::interface::XLSDownloader;
use schedule_parser::parse_xls;
use std::{env, fs};
use actix_web::{web, App, HttpServer};
use chrono::{DateTime, Utc};
use diesel::{Connection, PgConnection};
use dotenvy::dotenv;
use schedule_parser::schema::ScheduleEntity;
use std::collections::HashMap;
use std::sync::Mutex;
use std::env;
mod database;
mod routes;
mod xls_downloader;
#[tokio::main]
async fn main() {
let args: Vec<String> = env::args().collect();
assert_ne!(args.len(), 1);
let mut downloader = BasicXlsDownloader::new();
downloader
.set_url(args[1].to_string())
.await
.expect("Failed to set url");
let fetch_res = downloader.fetch(false).await.expect("Failed to fetch xls");
let (teachers, groups) = parse_xls(fetch_res.data.as_ref().unwrap());
fs::write(
"./schedule.json",
serde_json::to_string_pretty(&groups)
.expect("Failed to serialize schedule")
.as_bytes(),
)
.expect("Failed to write schedule");
fs::write(
"./teachers.json",
serde_json::to_string_pretty(&teachers)
.expect("Failed to serialize teachers schedule")
.as_bytes(),
)
.expect("Failed to write teachers schedule");
pub struct AppState {
downloader: Mutex<BasicXlsDownloader>,
schedule: Mutex<
Option<(
String,
DateTime<Utc>,
(
HashMap<String, ScheduleEntity>,
HashMap<String, ScheduleEntity>,
),
)>,
>,
database: Mutex<PgConnection>,
}
#[actix_web::main]
async fn main() {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let data = web::Data::new(AppState {
downloader: Mutex::new(BasicXlsDownloader::new()),
schedule: Mutex::new(None),
database: Mutex::new(
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)),
),
});
HttpServer::new(move || {
let schedule_scope = web::scope("/auth").service(sign_in);
let api_scope = web::scope("/api/v1").service(schedule_scope);
App::new().app_data(data.clone()).service(api_scope)
})
.bind(("127.0.0.1", 8080))
.unwrap()
.run()
.await
.unwrap();
}

2
src/routes/auth/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod sign_in;
mod schema;

56
src/routes/auth/schema.rs Normal file
View File

@@ -0,0 +1,56 @@
use crate::database::models::User;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Deserialize)]
pub struct SignInDto {
pub username: String,
pub password: String,
}
pub struct SignInResult(Result<SignInOk, SignInErr>);
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignInOk {
id: String,
access_token: String,
group: String,
}
#[derive(Serialize)]
pub struct SignInErr {
code: SignInErrCode,
}
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SignInErrCode {
IncorrectCredentials,
InvalidVkAccessToken,
}
impl SignInResult {
pub fn ok(user: &User) -> Self {
Self(Ok(SignInOk {
id: user.id.clone(),
access_token: user.access_token.clone(),
group: user.group.clone(),
}))
}
pub fn err(code: SignInErrCode) -> SignInResult {
Self(Err(SignInErr { code }))
}
}
impl Serialize for SignInResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.0 {
Ok(ok) => serializer.serialize_some(&ok),
Err(err) => serializer.serialize_some(&err),
}
}
}

View File

@@ -0,0 +1,26 @@
use crate::database::models::User;
use crate::routes::auth::schema::SignInErrCode::IncorrectCredentials;
use crate::routes::auth::schema::{SignInDto, SignInResult};
use crate::AppState;
use actix_web::{post, web};
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
use std::ops::DerefMut;
use web::Json;
#[post("/sign-in")]
pub async fn sign_in(data: Json<SignInDto>, app_state: web::Data<AppState>) -> Json<SignInResult> {
use crate::database::schema::users::dsl::*;
match {
let mut lock = app_state.database.lock().unwrap();
let connection = lock.deref_mut();
users
.filter(username.eq(data.username.clone()))
.select(User::as_select())
.first(connection)
} {
Ok(user) => Json(SignInResult::ok(&user)),
Err(_) => Json(SignInResult::err(IncorrectCredentials)),
}
}

1
src/routes/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod auth;