This commit is contained in:
Felix Ableitner 2024-11-25 15:52:05 +01:00
parent aa4007f61a
commit be508fcdbc
11 changed files with 181 additions and 180 deletions

View file

@ -8,8 +8,9 @@ use lemmy_api_common::{
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
federation_allowlist::{AdminAllowInstance, AdminAllowInstanceForm}, federation_allowlist::{FederationAllowList, FederationAllowListForm},
instance::Instance, instance::Instance,
mod_log::admin::{AdminAllowInstance, AdminAllowInstanceForm},
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -27,17 +28,23 @@ pub async fn admin_allow_instance(
Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?; Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?;
} }
let instance_block_form = AdminAllowInstanceForm { let form = FederationAllowListForm {
instance_id: data.instance_id,
updated: None,
};
if data.allow {
FederationAllowList::allow(&mut context.pool(), &form).await?;
} else {
FederationAllowList::unallow(&mut context.pool(), data.instance_id).await?;
}
let mod_log_form = AdminAllowInstanceForm {
instance_id: data.instance_id, instance_id: data.instance_id,
admin_person_id: local_user_view.person.id, admin_person_id: local_user_view.person.id,
reason: data.reason.clone(), reason: data.reason.clone(),
allowed: data.allow,
}; };
AdminAllowInstance::insert(&mut context.pool(), &mod_log_form).await?;
if data.allow {
AdminAllowInstance::allow(&mut context.pool(), &instance_block_form).await?;
} else {
AdminAllowInstance::unallow(&mut context.pool(), data.instance_id).await?;
}
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -8,8 +8,9 @@ use lemmy_api_common::{
SuccessResponse, SuccessResponse,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm}, federation_blocklist::{FederationBlockList, FederationBlockListForm},
instance::Instance, instance::Instance,
mod_log::admin::{AdminBlockInstance, AdminBlockInstanceForm},
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -27,18 +28,26 @@ pub async fn admin_block_instance(
Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?; Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?;
} }
let instance_block_form = AdminBlockInstanceForm { let form = FederationBlockListForm {
instance_id: data.instance_id, instance_id: data.instance_id,
admin_person_id: local_user_view.person.id,
reason: data.reason.clone(),
expires: data.expires, expires: data.expires,
updated: None,
}; };
if data.block { if data.block {
AdminBlockInstance::block(&mut context.pool(), &instance_block_form).await?; FederationBlockList::block(&mut context.pool(), &form).await?;
} else { } else {
AdminBlockInstance::unblock(&mut context.pool(), data.instance_id).await?; FederationBlockList::unblock(&mut context.pool(), data.instance_id).await?;
} }
let mod_log_form = AdminBlockInstanceForm {
instance_id: data.instance_id,
admin_person_id: local_user_view.person.id,
blocked: data.block,
reason: data.reason.clone(),
expires: data.expires,
};
AdminBlockInstance::insert(&mut context.pool(), &mod_log_form).await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -1,10 +1,9 @@
use crate::{ use crate::{
newtypes::InstanceId, newtypes::InstanceId,
schema::{admin_allow_instance, federation_allowlist}, schema::{admin_allow_instance, federation_allowlist},
source::federation_allowlist::{ source::{
AdminAllowInstance, federation_allowlist::{FederationAllowList, FederationAllowListForm},
AdminAllowInstanceForm, mod_log::admin::{AdminAllowInstance, AdminAllowInstanceForm},
FederationAllowListForm,
}, },
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
}; };
@ -12,29 +11,25 @@ use diesel::{delete, dsl::insert_into, result::Error, ExpressionMethods, QueryDs
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
impl AdminAllowInstance { impl AdminAllowInstance {
pub async fn allow(pool: &mut DbPool<'_>, form: &AdminAllowInstanceForm) -> Result<(), Error> { pub async fn insert(pool: &mut DbPool<'_>, form: &AdminAllowInstanceForm) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
conn insert_into(admin_allow_instance::table)
.build_transaction() .values(form)
.run(|conn| { .execute(conn)
Box::pin(async move { .await?;
insert_into(admin_allow_instance::table)
.values(form)
.execute(conn)
.await?;
let form2 = FederationAllowListForm { Ok(())
instance_id: form.instance_id, }
updated: None, }
};
insert_into(federation_allowlist::table) impl FederationAllowList {
.values(form2) pub async fn allow(pool: &mut DbPool<'_>, form: &FederationAllowListForm) -> Result<(), Error> {
.execute(conn) let conn = &mut get_conn(pool).await?;
.await?; insert_into(federation_allowlist::table)
Ok(()) .values(form)
}) .execute(conn)
}) .await?;
.await Ok(())
} }
pub async fn unallow(pool: &mut DbPool<'_>, instance_id_: InstanceId) -> Result<(), Error> { pub async fn unallow(pool: &mut DbPool<'_>, instance_id_: InstanceId) -> Result<(), Error> {
use federation_allowlist::dsl::instance_id; use federation_allowlist::dsl::instance_id;
@ -50,14 +45,7 @@ impl AdminAllowInstance {
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{source::instance::Instance, utils::build_db_pool_for_tests};
source::{
instance::Instance,
person::{Person, PersonInsertForm},
},
traits::Crud,
utils::build_db_pool_for_tests,
};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -72,19 +60,16 @@ mod tests {
Instance::read_or_create(pool, "tld2.xyz".to_string()).await?, Instance::read_or_create(pool, "tld2.xyz".to_string()).await?,
Instance::read_or_create(pool, "tld3.xyz".to_string()).await?, Instance::read_or_create(pool, "tld3.xyz".to_string()).await?,
]; ];
let new_person_3 = PersonInsertForm::test_form(instances[0].id, "xyz");
let person = Person::create(pool, &new_person_3).await?;
let forms: Vec<_> = instances let forms: Vec<_> = instances
.iter() .iter()
.map(|i| AdminAllowInstanceForm { .map(|i| FederationAllowListForm {
instance_id: i.id, instance_id: i.id,
admin_person_id: person.id, updated: None,
reason: None,
}) })
.collect(); .collect();
for f in &forms { for f in &forms {
AdminAllowInstance::allow(pool, f).await?; FederationAllowList::allow(pool, f).await?;
} }
let allows = Instance::allowlist(pool).await?; let allows = Instance::allowlist(pool).await?;
@ -94,7 +79,7 @@ mod tests {
// Now test clearing them // Now test clearing them
for f in forms { for f in forms {
AdminAllowInstance::unallow(pool, f.instance_id).await?; FederationAllowList::unallow(pool, f.instance_id).await?;
} }
let allows = Instance::allowlist(pool).await?; let allows = Instance::allowlist(pool).await?;
assert_eq!(0, allows.len()); assert_eq!(0, allows.len());

View file

@ -1,10 +1,9 @@
use crate::{ use crate::{
newtypes::InstanceId, newtypes::InstanceId,
schema::{admin_block_instance, federation_blocklist}, schema::{admin_block_instance, federation_blocklist},
source::federation_blocklist::{ source::{
AdminBlockInstance, federation_blocklist::{FederationBlockList, FederationBlockListForm},
AdminBlockInstanceForm, mod_log::admin::{AdminBlockInstance, AdminBlockInstanceForm},
FederationBlockListForm,
}, },
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
}; };
@ -12,38 +11,32 @@ use diesel::{delete, dsl::insert_into, result::Error, ExpressionMethods, QueryDs
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
impl AdminBlockInstance { impl AdminBlockInstance {
pub async fn block(pool: &mut DbPool<'_>, form: &AdminBlockInstanceForm) -> Result<(), Error> { pub async fn insert(pool: &mut DbPool<'_>, form: &AdminBlockInstanceForm) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
conn insert_into(admin_block_instance::table)
.build_transaction() .values(form)
.run(|conn| { .execute(conn)
Box::pin(async move { .await?;
insert_into(admin_block_instance::table)
.values(form)
.execute(conn)
.await?;
let form2 = FederationBlockListForm { Ok(())
instance_id: form.instance_id, }
updated: None, }
expires: form.expires,
}; impl FederationBlockList {
insert_into(federation_blocklist::table) pub async fn block(pool: &mut DbPool<'_>, form: &FederationBlockListForm) -> Result<(), Error> {
.values(form2) let conn = &mut get_conn(pool).await?;
.execute(conn) insert_into(federation_blocklist::table)
.await?; .values(form)
Ok(()) .execute(conn)
}) .await?;
}) Ok(())
.await }
} pub async fn unblock(pool: &mut DbPool<'_>, instance_id_: InstanceId) -> Result<(), Error> {
pub async fn unblock(pool: &mut DbPool<'_>, instance_id: InstanceId) -> Result<(), Error> { use federation_blocklist::dsl::instance_id;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
delete( delete(federation_blocklist::table.filter(instance_id.eq(instance_id_)))
federation_blocklist::table.filter(federation_blocklist::dsl::instance_id.eq(instance_id)), .execute(conn)
) .await?;
.execute(conn)
.await?;
Ok(()) Ok(())
} }
} }

View file

@ -47,6 +47,7 @@ diesel::table! {
id -> Int4, id -> Int4,
instance_id -> Int4, instance_id -> Int4,
admin_person_id -> Int4, admin_person_id -> Int4,
allowed -> Bool,
reason -> Nullable<Text>, reason -> Nullable<Text>,
published -> Timestamptz, published -> Timestamptz,
} }
@ -57,6 +58,7 @@ diesel::table! {
id -> Int4, id -> Int4,
instance_id -> Int4, instance_id -> Int4,
admin_person_id -> Int4, admin_person_id -> Int4,
blocked -> Bool,
reason -> Nullable<Text>, reason -> Nullable<Text>,
expires -> Nullable<Timestamptz>, expires -> Nullable<Timestamptz>,
published -> Timestamptz, published -> Timestamptz,

View file

@ -1,12 +1,9 @@
use crate::newtypes::{InstanceId, PersonId}; use crate::newtypes::InstanceId;
#[cfg(feature = "full")]
use crate::schema::federation_allowlist;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "full")]
use {
crate::schema::{admin_allow_instance, federation_allowlist},
ts_rs::TS,
};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr( #[cfg_attr(
@ -29,38 +26,7 @@ pub struct FederationAllowList {
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = federation_allowlist))] #[cfg_attr(feature = "full", diesel(table_name = federation_allowlist))]
pub(crate) struct FederationAllowListForm { pub struct FederationAllowListForm {
pub instance_id: InstanceId, pub instance_id: InstanceId,
pub updated: Option<DateTime<Utc>>, pub updated: Option<DateTime<Utc>>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(TS, Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::instance::Instance))
)]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct AdminAllowInstance {
pub id: i32,
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
pub struct AdminAllowInstanceForm {
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub reason: Option<String>,
}

View file

@ -1,12 +1,9 @@
use crate::newtypes::{InstanceId, PersonId}; use crate::newtypes::InstanceId;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use { use {crate::schema::federation_blocklist, ts_rs::TS};
crate::schema::{admin_block_instance, federation_blocklist},
ts_rs::TS,
};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr( #[cfg_attr(
@ -33,42 +30,8 @@ pub struct FederationBlockList {
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))] #[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))]
pub(crate) struct FederationBlockListForm { pub struct FederationBlockListForm {
pub instance_id: InstanceId, pub instance_id: InstanceId,
pub updated: Option<DateTime<Utc>>, pub updated: Option<DateTime<Utc>>,
pub expires: Option<DateTime<Utc>>, pub expires: Option<DateTime<Utc>>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(TS, Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::instance::Instance))
)]
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct AdminBlockInstance {
pub id: i32,
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub expires: Option<DateTime<Utc>>,
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
pub struct AdminBlockInstanceForm {
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub reason: Option<String>,
pub expires: Option<DateTime<Utc>>,
}

View file

@ -1,6 +1,8 @@
use crate::newtypes::{CommunityId, PersonId, PostId}; use crate::newtypes::{CommunityId, InstanceId, PersonId, PostId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::{ use crate::schema::{
admin_allow_instance,
admin_block_instance,
admin_purge_comment, admin_purge_comment,
admin_purge_community, admin_purge_community,
admin_purge_person, admin_purge_person,
@ -103,3 +105,72 @@ pub struct AdminPurgeCommentForm {
pub post_id: PostId, pub post_id: PostId,
pub reason: Option<String>, pub reason: Option<String>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(TS, Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::instance::Instance))
)]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct AdminAllowInstance {
pub id: i32,
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub allowed: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
pub struct AdminAllowInstanceForm {
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub allowed: bool,
pub reason: Option<String>,
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(TS, Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::instance::Instance))
)]
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct AdminBlockInstance {
pub id: i32,
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub blocked: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub expires: Option<DateTime<Utc>>,
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
pub struct AdminBlockInstanceForm {
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub blocked: bool,
pub reason: Option<String>,
pub expires: Option<DateTime<Utc>>,
}

View file

@ -5,11 +5,16 @@ use lemmy_db_schema::{
source::{ source::{
comment::Comment, comment::Comment,
community::Community, community::Community,
federation_allowlist::AdminAllowInstance,
federation_blocklist::AdminBlockInstance,
instance::Instance, instance::Instance,
mod_log::{ mod_log::{
admin::{AdminPurgeComment, AdminPurgeCommunity, AdminPurgePerson, AdminPurgePost}, admin::{
AdminAllowInstance,
AdminBlockInstance,
AdminPurgeComment,
AdminPurgeCommunity,
AdminPurgePerson,
AdminPurgePost,
},
moderator::{ moderator::{
ModAdd, ModAdd,
ModAddCommunity, ModAddCommunity,

View file

@ -201,8 +201,8 @@ mod test {
use chrono::DateTime; use chrono::DateTime;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
federation_allowlist::{AdminAllowInstance, AdminAllowInstanceForm}, federation_allowlist::{FederationAllowList, FederationAllowListForm},
federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm}, federation_blocklist::{FederationBlockList, FederationBlockListForm},
instance::InstanceForm, instance::InstanceForm,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
}, },
@ -325,13 +325,12 @@ mod test {
let instance_id = data.instances[0].id; let instance_id = data.instances[0].id;
let form = PersonInsertForm::new("tim".to_string(), String::new(), instance_id); let form = PersonInsertForm::new("tim".to_string(), String::new(), instance_id);
let person = Person::create(&mut data.context.pool(), &form).await?; let person = Person::create(&mut data.context.pool(), &form).await?;
let form = AdminBlockInstanceForm { let form = FederationBlockListForm {
instance_id, instance_id,
admin_person_id: person.id, updated: None,
reason: None,
expires: None, expires: None,
}; };
AdminBlockInstance::block(&mut data.context.pool(), &form).await?; FederationBlockList::block(&mut data.context.pool(), &form).await?;
data.run().await?; data.run().await?;
let workers = &data.send_manager.workers; let workers = &data.send_manager.workers;
assert_eq!(2, workers.len()); assert_eq!(2, workers.len());
@ -352,12 +351,11 @@ mod test {
let instance_id = data.instances[0].id; let instance_id = data.instances[0].id;
let form = PersonInsertForm::new("tim".to_string(), String::new(), instance_id); let form = PersonInsertForm::new("tim".to_string(), String::new(), instance_id);
let person = Person::create(&mut data.context.pool(), &form).await?; let person = Person::create(&mut data.context.pool(), &form).await?;
let form = AdminAllowInstanceForm { let form = FederationAllowListForm {
instance_id: data.instances[0].id, instance_id: data.instances[0].id,
admin_person_id: person.id, updated: None,
reason: None,
}; };
AdminAllowInstance::allow(&mut data.context.pool(), &form).await?; FederationAllowList::allow(&mut data.context.pool(), &form).await?;
data.run().await?; data.run().await?;
let workers = &data.send_manager.workers; let workers = &data.send_manager.workers;
assert_eq!(1, workers.len()); assert_eq!(1, workers.len());

View file

@ -5,6 +5,7 @@ CREATE TABLE admin_block_instance (
id serial PRIMARY KEY, id serial PRIMARY KEY,
instance_id int NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE, instance_id int NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE,
admin_person_id int NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE, admin_person_id int NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE,
blocked bool not null,
reason text, reason text,
expires timestamptz, expires timestamptz,
published timestamptz NOT NULL DEFAULT now() published timestamptz NOT NULL DEFAULT now()
@ -14,6 +15,7 @@ CREATE TABLE admin_allow_instance (
id serial PRIMARY KEY, id serial PRIMARY KEY,
instance_id int NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE, instance_id int NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE,
admin_person_id int NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE, admin_person_id int NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE,
allowed bool not null,
reason text, reason text,
published timestamptz NOT NULL DEFAULT now() published timestamptz NOT NULL DEFAULT now()
); );