diff --git a/migrations/20231008093906_alias_table.sql b/migrations/20231008093906_alias_table.sql new file mode 100644 index 0000000..d0b3c69 --- /dev/null +++ b/migrations/20231008093906_alias_table.sql @@ -0,0 +1,4 @@ +-- Add migration script here +DROP TABLE alias_recipient; +DELETE FROM emails WHERE alias = true; +ALTER TABLE emails DROP COLUMN alias; diff --git a/migrations/20231008094925_lists.sql b/migrations/20231008094925_lists.sql new file mode 100644 index 0000000..2bc6761 --- /dev/null +++ b/migrations/20231008094925_lists.sql @@ -0,0 +1,7 @@ +CREATE TYPE mail_type AS ENUM ('primary', 'alias', 'list'); + +ALTER TABLE emails + ADD COLUMN type mail_type DEFAULT 'primary'; + +ALTER TABLE emails + ALTER COLUMN type DROP DEFAULT; diff --git a/migrations/20231008100114_lists_recipients.sql b/migrations/20231008100114_lists_recipients.sql new file mode 100644 index 0000000..5a02253 --- /dev/null +++ b/migrations/20231008100114_lists_recipients.sql @@ -0,0 +1,12 @@ +-- Add migration script here + +ALTER TABLE emails + ADD CONSTRAINT email_type_unique UNIQUE (mail, type); + +CREATE TABLE list_recipients ( + list TEXT NOT NULL, + type mail_type NOT NULL CHECK (type = 'list'), + recipient TEXT NOT NULL, + PRIMARY KEY (list, recipient), + FOREIGN KEY (list, type) REFERENCES emails (mail, type) +); diff --git a/migrations/20231008101016_type_not_null.sql b/migrations/20231008101016_type_not_null.sql new file mode 100644 index 0000000..484d307 --- /dev/null +++ b/migrations/20231008101016_type_not_null.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE emails + ALTER COLUMN type SET NOT NULL; diff --git a/src/main.rs b/src/main.rs index 6c37065..39eaec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -514,8 +514,8 @@ struct HomeQuery { } #[derive(Deserialize, Debug)] -struct AliasRecipient { - alias: String, +struct ListRecipient { + list: String, recipient: String, } @@ -535,7 +535,7 @@ async fn home( r#" SELECT mail FROM emails - WHERE id = $1 AND alias = false + WHERE id = $1 AND type = 'primary' ORDER BY lower(substring(mail from position('@' in mail)+1 )),lower(mail) "#, user @@ -543,12 +543,12 @@ async fn home( .fetch_all(&state.db) .await?; - let aliases = sqlx::query_as!( + let lists = sqlx::query_as!( Mail, r#" SELECT mail FROM emails - WHERE id = $1 AND alias = true + WHERE id = $1 AND type = 'list' ORDER BY lower(substring(mail from position('@' in mail)+1 )),lower(mail) "#, user @@ -556,23 +556,23 @@ async fn home( .fetch_all(&state.db) .await?; - let mut alias_stream = sqlx::query_as!( - AliasRecipient, + let mut recipient_stream = sqlx::query_as!( + ListRecipient, r#" - SELECT alias_recipient.mail as alias,recipient - FROM emails,alias_recipient - WHERE id = $1 AND alias = true AND emails.mail = alias_recipient.mail + SELECT list_recipients.list as list,recipient + FROM emails,list_recipients + WHERE id = $1 AND emails.type = 'list' AND emails.mail = list_recipients.list "#, user ) .fetch(&state.db); - let mut aliases: HashMap<_, _> = aliases.into_iter().map(|a| (a.mail, Vec::new())).collect(); - while let Some(alias) = alias_stream.try_next().await? { - aliases.get_mut(&alias.alias).unwrap().push(alias.recipient); + let mut lists: HashMap<_, _> = lists.into_iter().map(|a| (a.mail, Vec::new())).collect(); + while let Some(list) = recipient_stream.try_next().await? { + lists.get_mut(&list.list).unwrap().push(list.recipient); } - let aliases: Vec<_> = aliases + let aliases: Vec<_> = lists .into_iter() .map(|(mail, recipients)| Alias { mail, recipients }) .sorted_by(|a, b| a.mail.cmp(&b.mail)) @@ -581,7 +581,7 @@ async fn home( let mut context = tera::Context::new(); context.insert("mails", &mails); context.insert("mail_domain", &state.mail_domain); - context.insert("aliases", &aliases); + context.insert("lists", &aliases); if let Some(err) = query.user_error { tracing::info!("User error: {err:?}"); context.insert("user_error", &err.to_string()); @@ -615,14 +615,14 @@ async fn delete_mail( } #[tracing::instrument(skip(state))] -async fn delete_alias( +async fn delete_list( state: State>, User(user): User, Form(delete): Form, ) -> Result { let mut tx = state.db.begin().await?; - sqlx::query!("DELETE FROM alias_recipient WHERE mail = $1", delete.mail) + sqlx::query!("DELETE FROM list_recipients WHERE list = $1", delete.mail) .execute(&mut *tx) .await?; @@ -696,7 +696,7 @@ async fn add_mail( Ok(UserError::MailAlreadyExists.into()) } else { sqlx::query!( - "INSERT INTO emails (id, mail) VALUES ($1, $2) ON CONFLICT DO NOTHING", + "INSERT INTO emails (id, mail, type) VALUES ($1, $2, 'primary') ON CONFLICT DO NOTHING", user, add.mail ) @@ -707,7 +707,7 @@ async fn add_mail( } } -async fn add_alias( +async fn add_list( state: State>, User(user): User, Form(add): Form, @@ -726,7 +726,7 @@ async fn add_alias( Ok(UserError::MailAlreadyExists.into()) } else { sqlx::query!( - "INSERT INTO emails (id, mail, alias) VALUES ($1, $2, true) ON CONFLICT DO NOTHING", + "INSERT INTO emails (id, mail, type) VALUES ($1, $2, 'list') ON CONFLICT DO NOTHING", user, add.mail ) @@ -741,12 +741,12 @@ async fn add_alias( async fn add_recipient( state: State>, User(user): User, - Form(add): Form, + Form(add): Form, ) -> Result { - let can_use_alias = sqlx::query!( + let can_use_list = sqlx::query!( "SELECT COUNT(*) FROM emails WHERE id = $1 AND mail = $2", user, - add.alias + add.list ) .fetch_one(&state.db) .await? @@ -754,14 +754,14 @@ async fn add_recipient( .expect("count should not be null") > 0; - if !can_use_alias { + if !can_use_list { tracing::error!("User is not authorized to use this alias"); return Err(Error::InternalError); } sqlx::query!( - "INSERT INTO alias_recipient (mail, recipient) VALUES ($1, $2) ON CONFLICT DO NOTHING", - add.alias, + "INSERT INTO list_recipients (list, recipient, type) VALUES ($1, $2, 'list') ON CONFLICT DO NOTHING", + add.list, add.recipient ) .execute(&state.db) @@ -774,12 +774,12 @@ async fn add_recipient( async fn delete_recipient( state: State>, User(user): User, - Form(delete): Form, + Form(delete): Form, ) -> Result { let can_use_alias = sqlx::query!( "SELECT COUNT(*) FROM emails WHERE id = $1 AND mail = $2", user, - delete.alias + delete.list ) .fetch_one(&state.db) .await? @@ -793,8 +793,8 @@ async fn delete_recipient( } let rows_affected = sqlx::query!( - "DELETE FROM alias_recipient WHERE mail = $1 AND recipient = $2", - delete.alias, + "DELETE FROM list_recipients WHERE list = $1 AND recipient = $2", + delete.list, delete.recipient, ) .execute(&state.db) @@ -870,10 +870,10 @@ async fn main() -> color_eyre::Result<()> { .route("/", get(home)) .route("/mail/delete", post(delete_mail)) .route("/mail/add", post(add_mail)) - .route("/alias/add", post(add_alias)) - .route("/alias/recipient/add", post(add_recipient)) - .route("/alias/recipient/delete", post(delete_recipient)) - .route("/alias/delete", post(delete_alias)) + .route("/list/add", post(add_list)) + .route("/list/recipient/add", post(add_recipient)) + .route("/list/recipient/delete", post(delete_recipient)) + .route("/list/delete", post(delete_list)) .route("/password", post(set_password)) .fallback(page_not_found) .with_state(Arc::new(AppState { diff --git a/templates/home.html b/templates/home.html index 402a636..3332c0d 100644 --- a/templates/home.html +++ b/templates/home.html @@ -135,27 +135,27 @@ payload=[], prefill=true) }} -

Aliases

+

Lists

    - {% for alias in aliases %} + {% for list in lists %}
  • - {{ alias.mail }} - {{ self::delete_modal(modal_id="aliasDelete" ~ loop.index, - confirm_text="Delete alias '" ~ alias.mail ~ "'", - action="/alias/delete", - payload=["mail", alias.mail]) + {{ list.mail }} + {{ self::delete_modal(modal_id="listDelete" ~ loop.index, + confirm_text="Delete list '" ~ list.mail ~ "'", + action="/list/delete", + payload=["name", list.mail]) }}
      - {% set alias_idx = loop.index %} - {% for recpt in alias.recipients %} + {% set list_idx = loop.index %} + {% for recpt in list.recipients %}
    • {{ recpt }} - {{ self::delete_modal(modal_id="aliasRctDelete" ~ alias_idx ~ loop.index, - confirm_text="Delete recipient '" ~ recpt ~ "' for '" ~ alias.mail ~ "'", - action="/alias/recipient/delete", - payload=["alias", alias.mail, "recipient", recpt]) + {{ self::delete_modal(modal_id="listRctDelete" ~ list_idx ~ loop.index, + confirm_text="Delete recipient '" ~ recpt ~ "' for '" ~ list.mail ~ "'", + action="/list/recipient/delete", + payload=["list", list.mail, "recipient", recpt]) }}
    • {% endfor %} @@ -164,18 +164,18 @@ add_button="Add Recipient", button_classes="mt-2 w-25", add_text="Add a new recipient", - action="/alias/recipient/add", + action="/list/recipient/add", input_name="recipient", - payload=["alias", alias.mail]) + payload=["list", list.mail]) }} {% endfor %}
    {{ self::add_modal(modal_id="addAlias", - add_button="Add new alias", + add_button="Add new list", button_classes="mt-2", - add_text="Add new alias", - action="/alias/add", + add_text="Add new list", + action="/list/add", input_name="mail", payload=[], prefill=true)