mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
server: ensure first/last name nullable, make avatar long blob in DB
Fixes #474, #486.
This commit is contained in:
parent
46b8f2a8a5
commit
a07f7ac389
@ -11,7 +11,7 @@ use tracing::{info, instrument, warn};
|
|||||||
|
|
||||||
use super::sql_tables::LAST_SCHEMA_VERSION;
|
use super::sql_tables::LAST_SCHEMA_VERSION;
|
||||||
|
|
||||||
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
pub enum Users {
|
pub enum Users {
|
||||||
Table,
|
Table,
|
||||||
UserId,
|
UserId,
|
||||||
@ -27,7 +27,7 @@ pub enum Users {
|
|||||||
Uuid,
|
Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
pub enum Groups {
|
pub enum Groups {
|
||||||
Table,
|
Table,
|
||||||
GroupId,
|
GroupId,
|
||||||
@ -36,7 +36,7 @@ pub enum Groups {
|
|||||||
Uuid,
|
Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Iden)]
|
#[derive(Iden, Clone, Copy)]
|
||||||
pub enum Memberships {
|
pub enum Memberships {
|
||||||
Table,
|
Table,
|
||||||
UserId,
|
UserId,
|
||||||
@ -334,6 +334,132 @@ pub async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_o
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn replace_column<I: Iden + Copy + 'static, const N: usize>(
|
||||||
|
pool: &DbConnection,
|
||||||
|
table_name: I,
|
||||||
|
column_name: I,
|
||||||
|
mut new_column: ColumnDef,
|
||||||
|
update_values: [Statement; N],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Update the definition of a column (in a compatible way). Due to Sqlite, this is more complicated:
|
||||||
|
// - rename the column to a temporary name
|
||||||
|
// - create the column with the new definition
|
||||||
|
// - copy the data from the temp column to the new one
|
||||||
|
// - update the new one if there are changes needed
|
||||||
|
// - drop the old one
|
||||||
|
let builder = pool.get_database_backend();
|
||||||
|
pool.transaction::<_, (), sea_orm::DbErr>(move |transaction| {
|
||||||
|
Box::pin(async move {
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum TempTable {
|
||||||
|
TempName,
|
||||||
|
}
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Table::alter()
|
||||||
|
.table(table_name)
|
||||||
|
.rename_column(column_name, TempTable::TempName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(Table::alter().table(table_name).add_column(&mut new_column)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Query::update()
|
||||||
|
.table(table_name)
|
||||||
|
.value(column_name, Expr::col((table_name, TempTable::TempName))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
for statement in update_values {
|
||||||
|
transaction.execute(statement).await?;
|
||||||
|
}
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Table::alter()
|
||||||
|
.table(table_name)
|
||||||
|
.drop_column(TempTable::TempName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn migrate_to_v2(pool: &DbConnection) -> anyhow::Result<()> {
|
||||||
|
let builder = pool.get_database_backend();
|
||||||
|
// Allow nulls in DisplayName, and change empty string to null.
|
||||||
|
replace_column(
|
||||||
|
pool,
|
||||||
|
Users::Table,
|
||||||
|
Users::DisplayName,
|
||||||
|
ColumnDef::new(Users::DisplayName)
|
||||||
|
.string_len(255)
|
||||||
|
.to_owned(),
|
||||||
|
[builder.build(
|
||||||
|
Query::update()
|
||||||
|
.table(Users::Table)
|
||||||
|
.value(Users::DisplayName, Option::<String>::None)
|
||||||
|
.cond_where(Expr::col(Users::DisplayName).eq("")),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn migrate_to_v3(pool: &DbConnection) -> anyhow::Result<()> {
|
||||||
|
let builder = pool.get_database_backend();
|
||||||
|
// Allow nulls in First and LastName. Users who created their DB in 0.4.1 have the not null constraint.
|
||||||
|
replace_column(
|
||||||
|
pool,
|
||||||
|
Users::Table,
|
||||||
|
Users::FirstName,
|
||||||
|
ColumnDef::new(Users::FirstName).string_len(255).to_owned(),
|
||||||
|
[builder.build(
|
||||||
|
Query::update()
|
||||||
|
.table(Users::Table)
|
||||||
|
.value(Users::FirstName, Option::<String>::None)
|
||||||
|
.cond_where(Expr::col(Users::FirstName).eq("")),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
replace_column(
|
||||||
|
pool,
|
||||||
|
Users::Table,
|
||||||
|
Users::LastName,
|
||||||
|
ColumnDef::new(Users::LastName).string_len(255).to_owned(),
|
||||||
|
[builder.build(
|
||||||
|
Query::update()
|
||||||
|
.table(Users::Table)
|
||||||
|
.value(Users::LastName, Option::<String>::None)
|
||||||
|
.cond_where(Expr::col(Users::LastName).eq("")),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// Change Avatar from binary to blob(long), because for MySQL this is 64kb.
|
||||||
|
replace_column(
|
||||||
|
pool,
|
||||||
|
Users::Table,
|
||||||
|
Users::Avatar,
|
||||||
|
ColumnDef::new(Users::Avatar)
|
||||||
|
.blob(sea_query::BlobSize::Long)
|
||||||
|
.to_owned(),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn migrate_from_version(
|
pub async fn migrate_from_version(
|
||||||
pool: &DbConnection,
|
pool: &DbConnection,
|
||||||
version: SchemaVersion,
|
version: SchemaVersion,
|
||||||
@ -346,68 +472,13 @@ pub async fn migrate_from_version(
|
|||||||
std::cmp::Ordering::Equal => return Ok(()),
|
std::cmp::Ordering::Equal => return Ok(()),
|
||||||
std::cmp::Ordering::Greater => anyhow::bail!("DB version downgrading is not supported"),
|
std::cmp::Ordering::Greater => anyhow::bail!("DB version downgrading is not supported"),
|
||||||
}
|
}
|
||||||
let builder = pool.get_database_backend();
|
|
||||||
if version < SchemaVersion(2) {
|
if version < SchemaVersion(2) {
|
||||||
// Drop the not_null constraint on display_name. Due to Sqlite, this is more complicated:
|
migrate_to_v2(pool).await?;
|
||||||
// - rename the display_name column to a temporary name
|
|
||||||
// - create the display_name column without the constraint
|
|
||||||
// - copy the data from the temp column to the new one
|
|
||||||
// - update the new one to replace empty strings with null
|
|
||||||
// - drop the old one
|
|
||||||
pool.transaction::<_, (), sea_orm::DbErr>(|transaction| {
|
|
||||||
Box::pin(async move {
|
|
||||||
#[derive(Iden)]
|
|
||||||
enum TempUsers {
|
|
||||||
TempDisplayName,
|
|
||||||
}
|
|
||||||
transaction
|
|
||||||
.execute(
|
|
||||||
builder.build(
|
|
||||||
Table::alter()
|
|
||||||
.table(Users::Table)
|
|
||||||
.rename_column(Users::DisplayName, TempUsers::TempDisplayName),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
transaction
|
|
||||||
.execute(
|
|
||||||
builder.build(
|
|
||||||
Table::alter()
|
|
||||||
.table(Users::Table)
|
|
||||||
.add_column(ColumnDef::new(Users::DisplayName).string_len(255)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
transaction
|
|
||||||
.execute(builder.build(Query::update().table(Users::Table).value(
|
|
||||||
Users::DisplayName,
|
|
||||||
Expr::col((Users::Table, TempUsers::TempDisplayName)),
|
|
||||||
)))
|
|
||||||
.await?;
|
|
||||||
transaction
|
|
||||||
.execute(
|
|
||||||
builder.build(
|
|
||||||
Query::update()
|
|
||||||
.table(Users::Table)
|
|
||||||
.value(Users::DisplayName, Option::<String>::None)
|
|
||||||
.cond_where(Expr::col(Users::DisplayName).eq("")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
transaction
|
|
||||||
.execute(
|
|
||||||
builder.build(
|
|
||||||
Table::alter()
|
|
||||||
.table(Users::Table)
|
|
||||||
.drop_column(TempUsers::TempDisplayName),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
if version < SchemaVersion(3) {
|
||||||
|
migrate_to_v3(pool).await?;
|
||||||
|
}
|
||||||
|
let builder = pool.get_database_backend();
|
||||||
pool.execute(
|
pool.execute(
|
||||||
builder.build(
|
builder.build(
|
||||||
Query::update()
|
Query::update()
|
||||||
|
@ -21,7 +21,7 @@ impl From<SchemaVersion> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(2);
|
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(3);
|
||||||
|
|
||||||
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
||||||
let version = {
|
let version = {
|
||||||
@ -100,21 +100,21 @@ mod tests {
|
|||||||
let sql_pool = get_in_memory_db().await;
|
let sql_pool = get_in_memory_db().await;
|
||||||
sql_pool
|
sql_pool
|
||||||
.execute(raw_statement(
|
.execute(raw_statement(
|
||||||
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, creation_date TEXT);"#,
|
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, first_name TEXT NOT NULL, last_name TEXT, avatar BLOB, creation_date TEXT);"#,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
sql_pool
|
sql_pool
|
||||||
.execute(raw_statement(
|
.execute(raw_statement(
|
||||||
r#"INSERT INTO users (user_id, display_name, creation_date)
|
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||||
VALUES ("bôb", "", "1970-01-01 00:00:00")"#,
|
VALUES ("bôb", "", "", "1970-01-01 00:00:00")"#,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
sql_pool
|
sql_pool
|
||||||
.execute(raw_statement(
|
.execute(raw_statement(
|
||||||
r#"INSERT INTO users (user_id, display_name, creation_date)
|
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||||
VALUES ("john", "John Doe", "1971-01-01 00:00:00")"#,
|
VALUES ("john", "John Doe", "John", "1971-01-01 00:00:00")"#,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -142,11 +142,12 @@ mod tests {
|
|||||||
#[derive(FromQueryResult, PartialEq, Eq, Debug)]
|
#[derive(FromQueryResult, PartialEq, Eq, Debug)]
|
||||||
struct SimpleUser {
|
struct SimpleUser {
|
||||||
display_name: Option<String>,
|
display_name: Option<String>,
|
||||||
|
first_name: Option<String>,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SimpleUser::find_by_statement(raw_statement(
|
SimpleUser::find_by_statement(raw_statement(
|
||||||
r#"SELECT display_name, uuid FROM users ORDER BY display_name"#
|
r#"SELECT display_name, first_name, uuid FROM users ORDER BY display_name"#
|
||||||
))
|
))
|
||||||
.all(&sql_pool)
|
.all(&sql_pool)
|
||||||
.await
|
.await
|
||||||
@ -154,10 +155,12 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
SimpleUser {
|
SimpleUser {
|
||||||
display_name: None,
|
display_name: None,
|
||||||
|
first_name: None,
|
||||||
uuid: crate::uuid!("a02eaf13-48a7-30f6-a3d4-040ff7c52b04")
|
uuid: crate::uuid!("a02eaf13-48a7-30f6-a3d4-040ff7c52b04")
|
||||||
},
|
},
|
||||||
SimpleUser {
|
SimpleUser {
|
||||||
display_name: Some("John Doe".to_owned()),
|
display_name: Some("John Doe".to_owned()),
|
||||||
|
first_name: Some("John".to_owned()),
|
||||||
uuid: crate::uuid!("986765a5-3f03-389e-b47b-536b2d6e1bec")
|
uuid: crate::uuid!("986765a5-3f03-389e-b47b-536b2d6e1bec")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user