server: enforce email and uuid unicity

This commit is contained in:
Valentin Tolmer
2023-04-13 10:37:54 +02:00
committed by nitnelave
parent d1f1eb8e80
commit 4e85a4718f
3 changed files with 131 additions and 22 deletions

View File

@@ -2,15 +2,14 @@ use crate::domain::{
sql_tables::{DbConnection, SchemaVersion},
types::{GroupId, UserId, Uuid},
};
use anyhow::Context;
use sea_orm::{
sea_query::{self, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Query, Table, Value},
sea_query::{self, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Index, Query, Table, Value},
ConnectionTrait, FromQueryResult, Iden, Statement, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use tracing::{info, instrument, warn};
use super::sql_tables::LAST_SCHEMA_VERSION;
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum Users {
Table,
@@ -460,30 +459,81 @@ async fn migrate_to_v3(pool: &DbConnection) -> anyhow::Result<()> {
Ok(())
}
async fn migrate_to_v4(pool: &DbConnection) -> anyhow::Result<()> {
let builder = pool.get_database_backend();
// Make emails and UUIDs unique.
pool.execute(
builder.build(
Index::create()
.name("unique-user-email")
.table(Users::Table)
.col(Users::Email)
.unique(),
),
)
.await
.context("while enforcing unicity on emails (2 users have the same email)")?;
pool.execute(
builder.build(
Index::create()
.name("unique-user-uuid")
.table(Users::Table)
.col(Users::Uuid)
.unique(),
),
)
.await
.context("while enforcing unicity on user UUIDs (2 users have the same UUID)")?;
pool.execute(
builder.build(
Index::create()
.name("unique-group-uuid")
.table(Groups::Table)
.col(Groups::Uuid)
.unique(),
),
)
.await
.context("while enforcing unicity on group UUIDs (2 groups have the same UUID)")?;
Ok(())
}
// This is needed to make an array of async functions.
macro_rules! to_sync {
($l:ident) => {
|pool| -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>>>> {
Box::pin($l(pool))
}
};
}
pub async fn migrate_from_version(
pool: &DbConnection,
version: SchemaVersion,
last_version: SchemaVersion,
) -> anyhow::Result<()> {
match version.cmp(&LAST_SCHEMA_VERSION) {
std::cmp::Ordering::Less => info!(
"Upgrading DB schema from {} to {}",
version.0, LAST_SCHEMA_VERSION.0
),
match version.cmp(&last_version) {
std::cmp::Ordering::Less => (),
std::cmp::Ordering::Equal => return Ok(()),
std::cmp::Ordering::Greater => anyhow::bail!("DB version downgrading is not supported"),
}
if version < SchemaVersion(2) {
migrate_to_v2(pool).await?;
}
if version < SchemaVersion(3) {
migrate_to_v3(pool).await?;
let migrations = [
to_sync!(migrate_to_v2),
to_sync!(migrate_to_v3),
to_sync!(migrate_to_v4),
];
for migration in 2..=4 {
if version < SchemaVersion(migration) && SchemaVersion(migration) <= last_version {
info!("Upgrading DB schema from {} to {}", version.0, migration);
migrations[(migration - 2) as usize](pool).await?;
}
}
let builder = pool.get_database_backend();
pool.execute(
builder.build(
Query::update()
.table(Metadata::Table)
.value(Metadata::Version, Value::from(LAST_SCHEMA_VERSION)),
.value(Metadata::Version, Value::from(last_version)),
),
)
.await?;