Fix wishlist for new people table
This commit is contained in:
parent
682ea672c4
commit
8032019807
|
|
@ -1,10 +1,7 @@
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{self, Body}, extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form
|
extract::{Path, Query, State}, response::{Html, IntoResponse, Redirect, Response}, Extension, Form
|
||||||
};
|
};
|
||||||
use chrono::{FixedOffset, Utc};
|
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, PgPool, Row};
|
use sqlx::{FromRow, PgPool, Row};
|
||||||
|
|
@ -12,7 +9,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
user::{get_user_roles_display, UserData},
|
user::{get_user_roles_display, AccountData},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HtmlTemplate<T>(T);
|
struct HtmlTemplate<T>(T);
|
||||||
|
|
@ -44,13 +41,13 @@ pub struct Calendar {
|
||||||
#[template(path = "calendar.html")]
|
#[template(path = "calendar.html")]
|
||||||
struct CalendarTemplate {
|
struct CalendarTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
calendars: Vec<Calendar>,
|
calendars: Vec<Calendar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn calendar(
|
pub async fn calendar(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
@ -101,7 +98,7 @@ pub async fn get_events(
|
||||||
Path(calendar): Path<String>,
|
Path(calendar): Path<String>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Query(params): Query<EventParams>,
|
Query(params): Query<EventParams>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
//println!("Calendar: {}", calendar);
|
//println!("Calendar: {}", calendar);
|
||||||
//println!("Paramters: {:?}", params);
|
//println!("Paramters: {:?}", params);
|
||||||
|
|
@ -182,7 +179,7 @@ pub struct EventCreate {
|
||||||
|
|
||||||
pub async fn create_event(
|
pub async fn create_event(
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
Form(event): Form<EventCreate>,
|
Form(event): Form<EventCreate>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/calendar", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/calendar", user_data.clone(), db_pool.clone()).await {
|
||||||
|
|
@ -222,13 +219,13 @@ pub async fn create_event(
|
||||||
#[template(path = "newevent.html")]
|
#[template(path = "newevent.html")]
|
||||||
struct EventTemplate {
|
struct EventTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
calendars: Vec<Calendar>,
|
calendars: Vec<Calendar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_event(
|
pub async fn new_event(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ use sqlx::PgPool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::user::get_useremails_by_role;
|
use crate::user::{get_useremails_by_role, AccountData};
|
||||||
use crate::email::send_emails;
|
use crate::email::send_emails;
|
||||||
|
|
||||||
use super::{AppError, UserData};
|
use super::{AppError};
|
||||||
|
|
||||||
fn get_client(hostname: String) -> Result<BasicClient, AppError> {
|
fn get_client(hostname: String) -> Result<BasicClient, AppError> {
|
||||||
let google_client_id = ClientId::new(var("GOOGLE_CLIENT_ID")?);
|
let google_client_id = ClientId::new(var("GOOGLE_CLIENT_ID")?);
|
||||||
|
|
@ -58,7 +58,7 @@ fn get_client(hostname: String) -> Result<BasicClient, AppError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
Query(mut params): Query<HashMap<String, String>>,
|
Query(mut params): Query<HashMap<String, String>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Host(hostname): Host,
|
Host(hostname): Host,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use error_handling::AppError;
|
||||||
use google_oauth::{google_auth_return, login, logout};
|
use google_oauth::{google_auth_return, login, logout};
|
||||||
use middlewares::inject_user_data;
|
use middlewares::inject_user_data;
|
||||||
use routes::{about, contact, dashboard, index, profile, user_profile, user_profile_account, useradmin};
|
use routes::{about, contact, dashboard, index, profile, user_profile, user_profile_account, useradmin};
|
||||||
use user::{add_user_role, delete_user_role, UserData};
|
use user::{add_user_role, delete_user_role, AccountData};
|
||||||
use wishlist::{
|
use wishlist::{
|
||||||
user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item,
|
user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item,
|
||||||
user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item,
|
user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item,
|
||||||
|
|
@ -69,7 +69,7 @@ async fn main() {
|
||||||
println!("SOURCE_DB_URL not set");
|
println!("SOURCE_DB_URL not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_data: Option<UserData> = None;
|
let user_data: Option<AccountData> = None;
|
||||||
|
|
||||||
// build our application with some routes
|
// build our application with some routes
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::{AppError, UserData};
|
use super::{AppError, AccountData};
|
||||||
use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse};
|
use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse};
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
@ -39,15 +39,15 @@ pub async fn inject_user_data(
|
||||||
let id = query.0;
|
let id = query.0;
|
||||||
let expires_at = query.1;
|
let expires_at = query.1;
|
||||||
if expires_at > Utc::now().naive_local() {
|
if expires_at > Utc::now().naive_local() {
|
||||||
let row: UserData = sqlx::query_as(
|
let row: AccountData = sqlx::query_as(
|
||||||
r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name FROM users WHERE id = $1"#,
|
r#"SELECT id, created_at, created_by, updated_at, updated_by, email, name, family_name, given_name, person_id FROM users WHERE id = $1"#,
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
request.extensions_mut().insert(Some(UserData {
|
request.extensions_mut().insert(Some(AccountData {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
created_at: row.created_at,
|
created_at: row.created_at,
|
||||||
created_by: row.created_by,
|
created_by: row.created_by,
|
||||||
|
|
@ -57,6 +57,7 @@ pub async fn inject_user_data(
|
||||||
name: row.name,
|
name: row.name,
|
||||||
family_name: row.family_name,
|
family_name: row.family_name,
|
||||||
given_name: row.given_name,
|
given_name: row.given_name,
|
||||||
|
person_id: row.person_id,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +70,7 @@ pub async fn inject_user_data(
|
||||||
Ok(next.run(request).await)
|
Ok(next.run(request).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_authorized(path: &str, user_data: Option<UserData>, db_pool: PgPool) -> bool {
|
pub async fn is_authorized(path: &str, user_data: Option<AccountData>, db_pool: PgPool) -> bool {
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
let query: Result<(uuid::Uuid,), _> = match path {
|
let query: Result<(uuid::Uuid,), _> = match path {
|
||||||
"/profile" => {
|
"/profile" => {
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,14 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
user::{get_other_roles_display, get_user_roles_display, AccountData},
|
user::{get_other_roles_display, get_user_roles_display, AccountData, PersonData},
|
||||||
UserData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "profile.html")]
|
#[template(path = "profile.html")]
|
||||||
struct ProfileTemplate {
|
struct ProfileTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,9 +25,9 @@ struct ProfileTemplate {
|
||||||
#[template(path = "user.html")]
|
#[template(path = "user.html")]
|
||||||
struct UserProfileTemplate {
|
struct UserProfileTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
profile: UserData,
|
profile: PersonData,
|
||||||
profile_accounts: Vec<AccountData>,
|
profile_accounts: Vec<AccountData>,
|
||||||
account: AccountData,
|
account: AccountData,
|
||||||
profile_roles: Vec<crate::user::UserRolesDisplay>,
|
profile_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
|
|
@ -57,21 +56,21 @@ where
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate {
|
struct IndexTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "dashboard.html")]
|
#[template(path = "dashboard.html")]
|
||||||
struct DashboardTemplate {
|
struct DashboardTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
fire_rating: String,
|
fire_rating: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -89,7 +88,7 @@ pub async fn index(
|
||||||
} else {
|
} else {
|
||||||
let template = IndexTemplate {
|
let template = IndexTemplate {
|
||||||
logged_in,
|
logged_in,
|
||||||
user: UserData::default(),
|
user: AccountData::default(),
|
||||||
};
|
};
|
||||||
HtmlTemplate(template).into_response()
|
HtmlTemplate(template).into_response()
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +96,7 @@ pub async fn index(
|
||||||
|
|
||||||
pub async fn dashboard(
|
pub async fn dashboard(
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -131,7 +130,7 @@ pub async fn dashboard(
|
||||||
/// Handles the profile page.
|
/// Handles the profile page.
|
||||||
pub async fn profile(
|
pub async fn profile(
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -163,7 +162,7 @@ pub async fn profile(
|
||||||
pub async fn user_profile(
|
pub async fn user_profile(
|
||||||
Path(user_id): Path<uuid::Uuid>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -174,7 +173,7 @@ pub async fn user_profile(
|
||||||
//let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
//let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||||
|
|
||||||
// Extract the user data.
|
// Extract the user data.
|
||||||
let profile: UserData = sqlx::query_as( "SELECT * FROM people WHERE id = $1")
|
let profile: PersonData = sqlx::query_as( "SELECT * FROM people WHERE id = $1")
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -201,7 +200,7 @@ pub async fn user_profile(
|
||||||
pub async fn user_profile_account(
|
pub async fn user_profile_account(
|
||||||
Path((user_id, account_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
Path((user_id, account_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -277,13 +276,13 @@ pub async fn user_profile_account(
|
||||||
#[template(path = "useradmin.html")]
|
#[template(path = "useradmin.html")]
|
||||||
struct UserAdminTemplate {
|
struct UserAdminTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
users: Vec<UserData>,
|
users: Vec<PersonData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn useradmin(
|
pub async fn useradmin(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
@ -294,7 +293,7 @@ pub async fn useradmin(
|
||||||
let user = user_data.as_ref().unwrap().clone();
|
let user = user_data.as_ref().unwrap().clone();
|
||||||
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||||
|
|
||||||
let users = sqlx::query_as::<_, UserData>("SELECT * FROM people order by name")
|
let users = sqlx::query_as::<_, PersonData>("SELECT * FROM people order by name")
|
||||||
.fetch_all(&db_pool)
|
.fetch_all(&db_pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -322,15 +321,15 @@ pub async fn useradmin(
|
||||||
#[template(path = "about.html")]
|
#[template(path = "about.html")]
|
||||||
struct AboutTemplate {
|
struct AboutTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn about(Extension(user_data): Extension<Option<UserData>>) -> impl IntoResponse {
|
pub async fn about(Extension(user_data): Extension<Option<AccountData>>) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
||||||
// Set empty user
|
// Set empty user
|
||||||
let mut user = UserData::default();
|
let mut user = AccountData::default();
|
||||||
|
|
||||||
if logged_in {
|
if logged_in {
|
||||||
// Extract the user data.
|
// Extract the user data.
|
||||||
|
|
@ -345,15 +344,15 @@ pub async fn about(Extension(user_data): Extension<Option<UserData>>) -> impl In
|
||||||
#[template(path = "contactus.html")]
|
#[template(path = "contactus.html")]
|
||||||
struct ContactTemplate {
|
struct ContactTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn contact(Extension(user_data): Extension<Option<UserData>>) -> impl IntoResponse {
|
pub async fn contact(Extension(user_data): Extension<Option<AccountData>>) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
||||||
// Set empty user
|
// Set empty user
|
||||||
let mut user = UserData::default();
|
let mut user = AccountData::default();
|
||||||
|
|
||||||
if logged_in {
|
if logged_in {
|
||||||
// Extract the user data.
|
// Extract the user data.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use sqlx::{FromRow, PgPool};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
user::{get_user_roles_display, UserData},
|
user::{get_user_roles_display, AccountData, PersonData},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Select participants from user list
|
/// Select participants from user list
|
||||||
|
|
@ -87,13 +87,13 @@ struct GiftExchange {
|
||||||
#[template(path = "giftexchanges.html")]
|
#[template(path = "giftexchanges.html")]
|
||||||
struct GiftExchangesTemplate {
|
struct GiftExchangesTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
giftexchanges: Vec<GiftExchange>,
|
giftexchanges: Vec<GiftExchange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn giftexchanges(
|
pub async fn giftexchanges(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
@ -144,16 +144,16 @@ struct GiftExchangeParticipant {
|
||||||
#[template(path = "giftexchange.html")]
|
#[template(path = "giftexchange.html")]
|
||||||
struct GiftExchangeTemplate {
|
struct GiftExchangeTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
giftexchange: GiftExchange,
|
giftexchange: GiftExchange,
|
||||||
participants: Vec<UserData>,
|
participants: Vec<PersonData>,
|
||||||
non_participants: Vec<UserData>,
|
non_participants: Vec<PersonData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn giftexchange(
|
pub async fn giftexchange(
|
||||||
Path(exchange_id): Path<i64>,
|
Path(exchange_id): Path<i64>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
@ -180,7 +180,7 @@ pub async fn giftexchange(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get participants
|
// Get participants
|
||||||
let participants = sqlx::query_as::<_, UserData>(
|
let participants = sqlx::query_as::<_, PersonData>(
|
||||||
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
"select * from users where users.id in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
||||||
)
|
)
|
||||||
.bind(exchange_id)
|
.bind(exchange_id)
|
||||||
|
|
@ -189,7 +189,7 @@ pub async fn giftexchange(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Get non participants
|
// Get non participants
|
||||||
let non_participants = sqlx::query_as::<_, UserData>(
|
let non_participants = sqlx::query_as::<_, PersonData>(
|
||||||
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
"select * from users where users.id not in (select participant_id from gift_exchange_participants where exchange_id = $1)",
|
||||||
)
|
)
|
||||||
.bind(exchange_id)
|
.bind(exchange_id)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use uuid::Uuid;
|
||||||
use crate::middlewares::is_authorized;
|
use crate::middlewares::is_authorized;
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize, FromRow)]
|
||||||
pub struct UserData {
|
pub struct PersonData {
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
pub created_at: chrono::NaiveDateTime,
|
pub created_at: chrono::NaiveDateTime,
|
||||||
pub created_by: uuid::Uuid,
|
pub created_by: uuid::Uuid,
|
||||||
|
|
@ -137,7 +137,7 @@ pub async fn get_other_roles_display(
|
||||||
pub async fn add_user_role(
|
pub async fn add_user_role(
|
||||||
Path((account_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
Path((account_id, role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/roles", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/roles", user_data.clone(), db_pool.clone()).await {
|
||||||
sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)")
|
sqlx::query("INSERT INTO user_roles (created_by, updated_by, user_id, role_id) VALUES ($1, $2, $3, $4)")
|
||||||
|
|
@ -166,7 +166,7 @@ pub async fn add_user_role(
|
||||||
pub async fn delete_user_role(
|
pub async fn delete_user_role(
|
||||||
Path((account_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
Path((account_id, user_role_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/roles", user_data, db_pool.clone()).await {
|
if is_authorized("/roles", user_data, db_pool.clone()).await {
|
||||||
sqlx::query("DELETE FROM user_roles WHERE id = $1")
|
sqlx::query("DELETE FROM user_roles WHERE id = $1")
|
||||||
|
|
@ -235,7 +235,7 @@ pub async fn get_useremails_by_role(role_name: String, db_pool: &PgPool) -> Stri
|
||||||
pub async fn user_account(
|
pub async fn user_account(
|
||||||
Path(account_id): Path<uuid::Uuid>,
|
Path(account_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use uuid::Uuid;
|
||||||
use crate::{
|
use crate::{
|
||||||
middlewares::is_authorized,
|
middlewares::is_authorized,
|
||||||
user::{
|
user::{
|
||||||
get_user_roles_display, get_user_wishlist_item_by_id, get_user_wishlist_items, UserData,
|
get_user_roles_display, get_user_wishlist_item_by_id, get_user_wishlist_items, PersonData, AccountData,
|
||||||
UserWishlistItem,
|
UserWishlistItem,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -40,13 +40,13 @@ where
|
||||||
#[template(path = "userwishlists.html")]
|
#[template(path = "userwishlists.html")]
|
||||||
struct WishListsTemplate {
|
struct WishListsTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
users: Vec<UserData>,
|
users: Vec<PersonData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wishlists(
|
pub async fn wishlists(
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
|
|
@ -57,7 +57,7 @@ pub async fn wishlists(
|
||||||
let user = user_data.as_ref().unwrap().clone();
|
let user = user_data.as_ref().unwrap().clone();
|
||||||
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||||
|
|
||||||
let users = sqlx::query_as::<_, UserData>(r#"SELECT p.* FROM people p
|
let users = sqlx::query_as::<_, PersonData>(r#"SELECT p.* FROM people p
|
||||||
left join user_roles ur on ur.user_id = p.id
|
left join user_roles ur on ur.user_id = p.id
|
||||||
left join roles r on r.id = ur.role_id
|
left join roles r on r.id = ur.role_id
|
||||||
where r.name = 'normal' order by p.name;"#)
|
where r.name = 'normal' order by p.name;"#)
|
||||||
|
|
@ -88,17 +88,17 @@ pub async fn wishlists(
|
||||||
#[template(path = "userwishlist.html")]
|
#[template(path = "userwishlist.html")]
|
||||||
struct UserWishListTemplate {
|
struct UserWishListTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
my_wishlist: bool,
|
my_wishlist: bool,
|
||||||
person: UserData,
|
person: PersonData,
|
||||||
person_wishlist_items: Vec<crate::user::UserWishlistItem>,
|
person_wishlist_items: Vec<crate::user::UserWishlistItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist(
|
pub async fn user_wishlist(
|
||||||
Path(user_id): Path<uuid::Uuid>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -109,7 +109,7 @@ pub async fn user_wishlist(
|
||||||
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||||
|
|
||||||
// Extract the user data.
|
// Extract the user data.
|
||||||
let person = sqlx::query_as("SELECT * FROM people WHERE id = $1")
|
let person = sqlx::query_as("SELECT * FROM people WHERE id = $1 or id = (select person_id from users where id = $1)")
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -123,7 +123,7 @@ pub async fn user_wishlist(
|
||||||
let person_wishlist_items = get_user_wishlist_items(user_id, &db_pool.clone()).await;
|
let person_wishlist_items = get_user_wishlist_items(user_id, &db_pool.clone()).await;
|
||||||
|
|
||||||
// Is viewed and viewing user the same (my wishlist)?
|
// Is viewed and viewing user the same (my wishlist)?
|
||||||
let my_wishlist = user_id == userid;
|
let my_wishlist = user_id == user.person_id;
|
||||||
|
|
||||||
// Create the wishlist template.
|
// Create the wishlist template.
|
||||||
let template = UserWishListTemplate {
|
let template = UserWishListTemplate {
|
||||||
|
|
@ -147,16 +147,16 @@ pub async fn user_wishlist(
|
||||||
#[template(path = "userwishlistadd.html")]
|
#[template(path = "userwishlistadd.html")]
|
||||||
struct UserWishListAddTemplate {
|
struct UserWishListAddTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
person: UserData,
|
person: PersonData,
|
||||||
person_wishlist_items: Vec<crate::user::UserWishlistItem>,
|
person_wishlist_items: Vec<crate::user::UserWishlistItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_wishlist_add(
|
pub async fn user_wishlist_add(
|
||||||
Path(user_id): Path<uuid::Uuid>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -167,7 +167,7 @@ pub async fn user_wishlist_add(
|
||||||
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||||
|
|
||||||
// Extract the user data.
|
// Extract the user data.
|
||||||
let person = sqlx::query_as("SELECT * FROM users WHERE id = $1")
|
let person = sqlx::query_as("SELECT * FROM people WHERE id = $1")
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_one(&db_pool)
|
.fetch_one(&db_pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -206,7 +206,7 @@ pub struct ItemForm {
|
||||||
pub async fn user_wishlist_add_item(
|
pub async fn user_wishlist_add_item(
|
||||||
Path(user_id): Path<uuid::Uuid>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
Form(item_form): Form<ItemForm>,
|
Form(item_form): Form<ItemForm>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
|
|
@ -221,7 +221,9 @@ pub async fn user_wishlist_add_item(
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let redirect_string = format!("/userwishlist/{user_id}");
|
// Redirect to wishlist
|
||||||
|
let person_id = user_data.as_ref().unwrap().person_id;
|
||||||
|
let redirect_string = format!("/userwishlist/{person_id}");
|
||||||
Redirect::to(&redirect_string).into_response()
|
Redirect::to(&redirect_string).into_response()
|
||||||
} else {
|
} else {
|
||||||
Redirect::to("/").into_response()
|
Redirect::to("/").into_response()
|
||||||
|
|
@ -232,7 +234,7 @@ pub async fn user_wishlist_add_item(
|
||||||
#[template(path = "userwishlistedit.html")]
|
#[template(path = "userwishlistedit.html")]
|
||||||
struct UserWishListEditTemplate {
|
struct UserWishListEditTemplate {
|
||||||
logged_in: bool,
|
logged_in: bool,
|
||||||
user: UserData,
|
user: AccountData,
|
||||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||||
user_wishlist_item: crate::user::UserWishlistItem,
|
user_wishlist_item: crate::user::UserWishlistItem,
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +242,7 @@ struct UserWishListEditTemplate {
|
||||||
pub async fn user_wishlist_edit_item(
|
pub async fn user_wishlist_edit_item(
|
||||||
Path(item_id): Path<uuid::Uuid>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// Is the user logged in?
|
// Is the user logged in?
|
||||||
let logged_in = user_data.is_some();
|
let logged_in = user_data.is_some();
|
||||||
|
|
@ -277,7 +279,7 @@ pub async fn user_wishlist_edit_item(
|
||||||
pub async fn user_wishlist_save_item(
|
pub async fn user_wishlist_save_item(
|
||||||
Path(item_id): Path<uuid::Uuid>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
Form(item_form): Form<ItemForm>,
|
Form(item_form): Form<ItemForm>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
|
|
@ -294,8 +296,8 @@ pub async fn user_wishlist_save_item(
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user_id = user_data.as_ref().unwrap().id;
|
let person_id = user_data.as_ref().unwrap().person_id;
|
||||||
let redirect_string = format!("/userwishlist/{user_id}");
|
let redirect_string = format!("/userwishlist/{person_id}");
|
||||||
Redirect::to(&redirect_string).into_response()
|
Redirect::to(&redirect_string).into_response()
|
||||||
} else {
|
} else {
|
||||||
Redirect::to("/").into_response()
|
Redirect::to("/").into_response()
|
||||||
|
|
@ -305,7 +307,7 @@ pub async fn user_wishlist_save_item(
|
||||||
pub async fn user_wishlist_bought_item(
|
pub async fn user_wishlist_bought_item(
|
||||||
Path(item_id): Path<uuid::Uuid>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
// Update item to purchased
|
// Update item to purchased
|
||||||
|
|
@ -335,7 +337,7 @@ pub async fn user_wishlist_bought_item(
|
||||||
pub async fn user_wishlist_received_item(
|
pub async fn user_wishlist_received_item(
|
||||||
Path(user_id): Path<uuid::Uuid>,
|
Path(user_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
// Update item received time
|
// Update item received time
|
||||||
|
|
@ -349,8 +351,8 @@ pub async fn user_wishlist_received_item(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Redirect to user wishlist
|
// Redirect to user wishlist
|
||||||
let userid = user_data.as_ref().unwrap().id;
|
let person_id = user_data.as_ref().unwrap().person_id;
|
||||||
let redirect_string = format!("/userwishlist/{userid}");
|
let redirect_string = format!("/userwishlist/{person_id}");
|
||||||
Redirect::to(&redirect_string).into_response()
|
Redirect::to(&redirect_string).into_response()
|
||||||
} else {
|
} else {
|
||||||
Redirect::to("/").into_response()
|
Redirect::to("/").into_response()
|
||||||
|
|
@ -360,7 +362,7 @@ pub async fn user_wishlist_received_item(
|
||||||
pub async fn user_wishlist_delete_item(
|
pub async fn user_wishlist_delete_item(
|
||||||
Path(item_id): Path<uuid::Uuid>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
sqlx::query("delete from wishlist_items where id = $1")
|
sqlx::query("delete from wishlist_items where id = $1")
|
||||||
|
|
@ -370,8 +372,8 @@ pub async fn user_wishlist_delete_item(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Redirect to user wishlist
|
// Redirect to user wishlist
|
||||||
let userid = user_data.as_ref().unwrap().id;
|
let person_id = user_data.as_ref().unwrap().person_id;
|
||||||
let redirect_string = format!("/userwishlist/{userid}");
|
let redirect_string = format!("/userwishlist/{person_id}");
|
||||||
Redirect::to(&redirect_string).into_response()
|
Redirect::to(&redirect_string).into_response()
|
||||||
} else {
|
} else {
|
||||||
Redirect::to("/").into_response()
|
Redirect::to("/").into_response()
|
||||||
|
|
@ -381,7 +383,7 @@ pub async fn user_wishlist_delete_item(
|
||||||
pub async fn user_wishlist_returned_item(
|
pub async fn user_wishlist_returned_item(
|
||||||
Path(item_id): Path<uuid::Uuid>,
|
Path(item_id): Path<uuid::Uuid>,
|
||||||
State(db_pool): State<PgPool>,
|
State(db_pool): State<PgPool>,
|
||||||
Extension(user_data): Extension<Option<UserData>>,
|
Extension(user_data): Extension<Option<AccountData>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||||
sqlx::query("update wishlist_items set purchased_by = null where id = $1")
|
sqlx::query("update wishlist_items set purchased_by = null where id = $1")
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<br />
|
<br />
|
||||||
<h2>List</h2>
|
<h2>List</h2>
|
||||||
{% if my_wishlist %}
|
{% if my_wishlist %}
|
||||||
<a href="/userwishlist/add/{{ user.id }}">Add</a>
|
<a href="/userwishlist/add/{{ person.id }}">Add</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="table-responsive overflow-auto">
|
<div class="table-responsive overflow-auto">
|
||||||
<table data-toggle="table" class="table table-striped table-bordered">
|
<table data-toggle="table" class="table table-striped table-bordered">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue