1
0
mirror of https://github.com/nitnelave/lldap.git synced 2023-04-12 14:25:13 +00:00
lldap/server/src/domain/sql_migrations.rs
Valentin Tolmer 312230e45f db: Change the version number from u8 to i16
This is the smallest integer compatible with all of MySQL, Postgres and
SQlite.

This is a backwards-compatible change for SQlite since both are
represented as "integer", and all u8 values can be represented as i16.
2023-01-13 15:12:26 +01:00

342 lines
10 KiB
Rust

use crate::domain::{
sql_tables::{DbConnection, SchemaVersion},
types::{GroupId, UserId, Uuid},
};
use sea_orm::{ConnectionTrait, FromQueryResult, Statement};
use sea_query::{ColumnDef, Expr, ForeignKey, ForeignKeyAction, Iden, Query, Table, Value};
use serde::{Deserialize, Serialize};
use tracing::{instrument, warn};
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
pub enum Users {
Table,
UserId,
Email,
DisplayName,
FirstName,
LastName,
Avatar,
CreationDate,
PasswordHash,
TotpSecret,
MfaType,
Uuid,
}
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
pub enum Groups {
Table,
GroupId,
DisplayName,
CreationDate,
Uuid,
}
#[derive(Iden)]
pub enum Memberships {
Table,
UserId,
GroupId,
}
// Metadata about the SQL DB.
#[derive(Iden)]
pub enum Metadata {
Table,
// Which version of the schema we're at.
Version,
}
#[derive(FromQueryResult, PartialEq, Eq, Debug)]
pub struct JustSchemaVersion {
pub version: SchemaVersion,
}
#[instrument(skip_all, level = "debug", ret)]
pub async fn get_schema_version(pool: &DbConnection) -> Option<SchemaVersion> {
JustSchemaVersion::find_by_statement(
pool.get_database_backend().build(
Query::select()
.from(Metadata::Table)
.column(Metadata::Version),
),
)
.one(pool)
.await
.ok()
.flatten()
.map(|j| j.version)
}
pub async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_orm::DbErr> {
let builder = pool.get_database_backend();
// SQLite needs this pragma to be turned on. Other DB might not understand this, so ignore the
// error.
let _ = pool
.execute(Statement::from_string(
builder,
"PRAGMA foreign_keys = ON".to_owned(),
))
.await;
pool.execute(
builder.build(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(
ColumnDef::new(Users::UserId)
.string_len(255)
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Users::Email).string_len(255).not_null())
.col(
ColumnDef::new(Users::DisplayName)
.string_len(255)
.not_null(),
)
.col(ColumnDef::new(Users::FirstName).string_len(255))
.col(ColumnDef::new(Users::LastName).string_len(255))
.col(ColumnDef::new(Users::Avatar).binary())
.col(ColumnDef::new(Users::CreationDate).date_time().not_null())
.col(ColumnDef::new(Users::PasswordHash).binary())
.col(ColumnDef::new(Users::TotpSecret).string_len(64))
.col(ColumnDef::new(Users::MfaType).string_len(64))
.col(ColumnDef::new(Users::Uuid).string_len(36).not_null()),
),
)
.await?;
pool.execute(
builder.build(
Table::create()
.table(Groups::Table)
.if_not_exists()
.col(
ColumnDef::new(Groups::GroupId)
.integer()
.auto_increment()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(Groups::DisplayName)
.string_len(255)
.unique_key()
.not_null(),
)
.col(ColumnDef::new(Users::CreationDate).date_time().not_null())
.col(ColumnDef::new(Users::Uuid).string_len(36).not_null()),
),
)
.await?;
// If the creation_date column doesn't exist, add it.
if pool
.execute(
builder.build(
Table::alter().table(Groups::Table).add_column(
ColumnDef::new(Groups::CreationDate)
.date_time()
.not_null()
.default(chrono::Utc::now().naive_utc()),
),
),
)
.await
.is_ok()
{
warn!("`creation_date` column not found in `groups`, creating it");
}
// If the uuid column doesn't exist, add it.
if pool
.execute(
builder.build(
Table::alter().table(Groups::Table).add_column(
ColumnDef::new(Groups::Uuid)
.string_len(36)
.not_null()
.default(""),
),
),
)
.await
.is_ok()
{
warn!("`uuid` column not found in `groups`, creating it");
#[derive(FromQueryResult)]
struct ShortGroupDetails {
group_id: GroupId,
display_name: String,
creation_date: chrono::DateTime<chrono::Utc>,
}
for result in ShortGroupDetails::find_by_statement(
builder.build(
Query::select()
.from(Groups::Table)
.column(Groups::GroupId)
.column(Groups::DisplayName)
.column(Groups::CreationDate),
),
)
.all(pool)
.await?
{
pool.execute(
builder.build(
Query::update()
.table(Groups::Table)
.value(
Groups::Uuid,
Value::from(Uuid::from_name_and_date(
&result.display_name,
&result.creation_date,
)),
)
.and_where(Expr::col(Groups::GroupId).eq(result.group_id)),
),
)
.await?;
}
}
if pool
.execute(
builder.build(
Table::alter().table(Users::Table).add_column(
ColumnDef::new(Users::Uuid)
.string_len(36)
.not_null()
.default(""),
),
),
)
.await
.is_ok()
{
warn!("`uuid` column not found in `users`, creating it");
#[derive(FromQueryResult)]
struct ShortUserDetails {
user_id: UserId,
creation_date: chrono::DateTime<chrono::Utc>,
}
for result in ShortUserDetails::find_by_statement(
builder.build(
Query::select()
.from(Users::Table)
.column(Users::UserId)
.column(Users::CreationDate),
),
)
.all(pool)
.await?
{
pool.execute(
builder.build(
Query::update()
.table(Users::Table)
.value(
Users::Uuid,
Value::from(Uuid::from_name_and_date(
result.user_id.as_str(),
&result.creation_date,
)),
)
.and_where(Expr::col(Users::UserId).eq(result.user_id)),
),
)
.await?;
}
}
pool.execute(
builder.build(
Table::create()
.table(Memberships::Table)
.if_not_exists()
.col(
ColumnDef::new(Memberships::UserId)
.string_len(255)
.not_null(),
)
.col(ColumnDef::new(Memberships::GroupId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("MembershipUserForeignKey")
.from(Memberships::Table, Memberships::UserId)
.to(Users::Table, Users::UserId)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade),
)
.foreign_key(
ForeignKey::create()
.name("MembershipGroupForeignKey")
.from(Memberships::Table, Memberships::GroupId)
.to(Groups::Table, Groups::GroupId)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade),
),
),
)
.await?;
if pool
.query_one(
builder.build(
Query::select()
.from(Groups::Table)
.column(Groups::DisplayName)
.cond_where(Expr::col(Groups::DisplayName).eq("lldap_readonly")),
),
)
.await
.is_ok()
{
pool.execute(
builder.build(
Query::update()
.table(Groups::Table)
.values(vec![(Groups::DisplayName, "lldap_password_manager".into())])
.cond_where(Expr::col(Groups::DisplayName).eq("lldap_readonly")),
),
)
.await?;
}
pool.execute(
builder.build(
Table::create()
.table(Metadata::Table)
.if_not_exists()
.col(ColumnDef::new(Metadata::Version).small_integer()),
),
)
.await?;
pool.execute(
builder.build(
Query::insert()
.into_table(Metadata::Table)
.columns(vec![Metadata::Version])
.values_panic(vec![SchemaVersion(1).into()]),
),
)
.await?;
assert_eq!(get_schema_version(pool).await.unwrap().0, 1);
Ok(())
}
pub async fn migrate_from_version(
_pool: &DbConnection,
version: SchemaVersion,
) -> anyhow::Result<()> {
if version.0 > 1 {
anyhow::bail!("DB version downgrading is not supported");
}
Ok(())
}