You've already forked AstralRinth
forked from didirus/AstralRinth
Add report + moderation messaging (#567)
* Add report + moderation messaging * Add system messages * address review comments * Remove ds store * Update messaging * run prep --------- Co-authored-by: Geometrically <geometrically@Jais-MacBook-Pro.local>
This commit is contained in:
@@ -10,6 +10,11 @@ pub struct ProjectType {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct SideType {
|
||||
pub id: SideTypeId,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct Loader {
|
||||
pub id: LoaderId,
|
||||
pub loader: String,
|
||||
@@ -46,23 +51,7 @@ pub struct DonationPlatform {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct CategoryBuilder<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub project_type: Option<&'a ProjectTypeId>,
|
||||
pub icon: Option<&'a str>,
|
||||
pub header: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl Category {
|
||||
pub fn builder() -> CategoryBuilder<'static> {
|
||||
CategoryBuilder {
|
||||
name: None,
|
||||
project_type: None,
|
||||
icon: None,
|
||||
header: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_id<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
@@ -105,26 +94,6 @@ impl Category {
|
||||
Ok(result.map(|r| CategoryId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get_name<'a, E>(
|
||||
id: CategoryId,
|
||||
exec: E,
|
||||
) -> Result<String, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT category FROM categories
|
||||
WHERE id = $1
|
||||
",
|
||||
id as CategoryId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.category)
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<Category>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -152,118 +121,9 @@ impl Category {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn remove<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM categories
|
||||
WHERE category = $1
|
||||
",
|
||||
name
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CategoryBuilder<'a> {
|
||||
/// The name of the category. Must be ASCII alphanumeric or `-`/`_`
|
||||
pub fn name(
|
||||
self,
|
||||
name: &'a str,
|
||||
) -> Result<CategoryBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
name: Some(name),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header(
|
||||
self,
|
||||
header: &'a str,
|
||||
) -> Result<CategoryBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
header: Some(header),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn project_type(
|
||||
self,
|
||||
project_type: &'a ProjectTypeId,
|
||||
) -> Result<CategoryBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
project_type: Some(project_type),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icon(
|
||||
self,
|
||||
icon: &'a str,
|
||||
) -> Result<CategoryBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
icon: Some(icon),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn insert<'b, E>(
|
||||
self,
|
||||
exec: E,
|
||||
) -> Result<CategoryId, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||
{
|
||||
let id = *self.project_type.ok_or_else(|| {
|
||||
DatabaseError::Other("No project type specified.".to_string())
|
||||
})?;
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO categories (category, project_type, icon, header)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
",
|
||||
self.name,
|
||||
id as ProjectTypeId,
|
||||
self.icon,
|
||||
self.header
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(CategoryId(result.id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoaderBuilder<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub icon: Option<&'a str>,
|
||||
pub supported_project_types: Option<&'a [ProjectTypeId]>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
pub fn builder() -> LoaderBuilder<'static> {
|
||||
LoaderBuilder {
|
||||
name: None,
|
||||
icon: None,
|
||||
supported_project_types: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_id<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
@@ -284,26 +144,6 @@ impl Loader {
|
||||
Ok(result.map(|r| LoaderId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get_name<'a, E>(
|
||||
id: LoaderId,
|
||||
exec: E,
|
||||
) -> Result<String, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT loader FROM loaders
|
||||
WHERE id = $1
|
||||
",
|
||||
id as LoaderId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.loader)
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<Loader>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -337,110 +177,6 @@ impl Loader {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// TODO: remove loaders with projects using them
|
||||
pub async fn remove<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM loaders
|
||||
WHERE loader = $1
|
||||
",
|
||||
name
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LoaderBuilder<'a> {
|
||||
/// The name of the loader. Must be ASCII alphanumeric or `-`/`_`
|
||||
pub fn name(
|
||||
self,
|
||||
name: &'a str,
|
||||
) -> Result<LoaderBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
name: Some(name),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icon(
|
||||
self,
|
||||
icon: &'a str,
|
||||
) -> Result<LoaderBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
icon: Some(icon),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn supported_project_types(
|
||||
self,
|
||||
supported_project_types: &'a [ProjectTypeId],
|
||||
) -> Result<LoaderBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
supported_project_types: Some(supported_project_types),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<LoaderId, super::DatabaseError> {
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO loaders (loader, icon)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (loader) DO NOTHING
|
||||
RETURNING id
|
||||
",
|
||||
self.name,
|
||||
self.icon
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if let Some(project_types) = self.supported_project_types {
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM loaders_project_types
|
||||
WHERE joining_loader_id = $1
|
||||
",
|
||||
result.id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
for project_type in project_types {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id)
|
||||
VALUES ($1, $2)
|
||||
",
|
||||
result.id,
|
||||
project_type.0,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(LoaderId(result.id))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -475,26 +211,6 @@ impl GameVersion {
|
||||
Ok(result.map(|r| GameVersionId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get_name<'a, E>(
|
||||
id: GameVersionId,
|
||||
exec: E,
|
||||
) -> Result<String, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT version FROM game_versions
|
||||
WHERE id = $1
|
||||
",
|
||||
id as GameVersionId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.version)
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<GameVersion>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -595,31 +311,6 @@ impl GameVersion {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn remove<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM game_versions
|
||||
WHERE version = $1
|
||||
",
|
||||
name
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GameVersionBuilder<'a> {
|
||||
@@ -681,17 +372,7 @@ impl<'a> GameVersionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DonationPlatformBuilder<'a> {
|
||||
pub short: Option<&'a str>,
|
||||
pub name: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl DonationPlatform {
|
||||
pub fn builder() -> DonationPlatformBuilder<'static> {
|
||||
DonationPlatformBuilder::default()
|
||||
}
|
||||
|
||||
pub async fn get_id<'a, E>(
|
||||
id: &str,
|
||||
exec: E,
|
||||
@@ -712,30 +393,6 @@ impl DonationPlatform {
|
||||
Ok(result.map(|r| DonationPlatformId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get<'a, E>(
|
||||
id: DonationPlatformId,
|
||||
exec: E,
|
||||
) -> Result<DonationPlatform, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT short, name FROM donation_platforms
|
||||
WHERE id = $1
|
||||
",
|
||||
id as DonationPlatformId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(DonationPlatform {
|
||||
id,
|
||||
short: result.short,
|
||||
name: result.name,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(
|
||||
exec: E,
|
||||
) -> Result<Vec<DonationPlatform>, DatabaseError>
|
||||
@@ -760,89 +417,9 @@ impl DonationPlatform {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn remove<'a, E>(
|
||||
short: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM donation_platforms
|
||||
WHERE short = $1
|
||||
",
|
||||
short
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DonationPlatformBuilder<'a> {
|
||||
/// The donation platform short name. Spaces must be replaced with '_' for it to be valid
|
||||
pub fn short(
|
||||
self,
|
||||
short: &'a str,
|
||||
) -> Result<DonationPlatformBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
short: Some(short),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
/// The donation platform long name
|
||||
pub fn name(
|
||||
self,
|
||||
name: &'a str,
|
||||
) -> Result<DonationPlatformBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
name: Some(name),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn insert<'b, E>(
|
||||
self,
|
||||
exec: E,
|
||||
) -> Result<DonationPlatformId, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO donation_platforms (short, name)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (short) DO NOTHING
|
||||
RETURNING id
|
||||
",
|
||||
self.short,
|
||||
self.name,
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(DonationPlatformId(result.id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReportTypeBuilder<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl ReportType {
|
||||
pub fn builder() -> ReportTypeBuilder<'static> {
|
||||
ReportTypeBuilder { name: None }
|
||||
}
|
||||
|
||||
pub async fn get_id<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
@@ -863,26 +440,6 @@ impl ReportType {
|
||||
Ok(result.map(|r| ReportTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get_name<'a, E>(
|
||||
id: ReportTypeId,
|
||||
exec: E,
|
||||
) -> Result<String, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT name FROM report_types
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ReportTypeId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.name)
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -899,74 +456,9 @@ impl ReportType {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn remove<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM report_types
|
||||
WHERE name = $1
|
||||
",
|
||||
name
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ReportTypeBuilder<'a> {
|
||||
/// The name of the report type. Must be ASCII alphanumeric or `-`/`_`
|
||||
pub fn name(
|
||||
self,
|
||||
name: &'a str,
|
||||
) -> Result<ReportTypeBuilder<'a>, DatabaseError> {
|
||||
Ok(Self { name: Some(name) })
|
||||
}
|
||||
|
||||
pub async fn insert<'b, E>(
|
||||
self,
|
||||
exec: E,
|
||||
) -> Result<ReportTypeId, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO report_types (name)
|
||||
VALUES ($1)
|
||||
ON CONFLICT (name) DO NOTHING
|
||||
RETURNING id
|
||||
",
|
||||
self.name
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(ReportTypeId(result.id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProjectTypeBuilder<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl ProjectType {
|
||||
pub fn builder() -> ProjectTypeBuilder<'static> {
|
||||
ProjectTypeBuilder { name: None }
|
||||
}
|
||||
|
||||
pub async fn get_id<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
@@ -987,53 +479,6 @@ impl ProjectType {
|
||||
Ok(result.map(|r| ProjectTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn get_many_id<'a, E>(
|
||||
names: &[String],
|
||||
exec: E,
|
||||
) -> Result<Vec<ProjectType>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let project_types = sqlx::query!(
|
||||
"
|
||||
SELECT id, name FROM project_types
|
||||
WHERE name = ANY($1)
|
||||
",
|
||||
names
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|x| ProjectType {
|
||||
id: ProjectTypeId(x.id),
|
||||
name: x.name,
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<ProjectType>>()
|
||||
.await?;
|
||||
|
||||
Ok(project_types)
|
||||
}
|
||||
|
||||
pub async fn get_name<'a, E>(
|
||||
id: ProjectTypeId,
|
||||
exec: E,
|
||||
) -> Result<String, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT name FROM project_types
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ProjectTypeId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.name)
|
||||
}
|
||||
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
@@ -1050,62 +495,43 @@ impl ProjectType {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove loaders with mods using them
|
||||
pub async fn remove<'a, E>(
|
||||
impl SideType {
|
||||
pub async fn get_id<'a, E>(
|
||||
name: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, DatabaseError>
|
||||
) -> Result<Option<SideTypeId>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM project_types
|
||||
SELECT id FROM side_types
|
||||
WHERE name = $1
|
||||
",
|
||||
name
|
||||
)
|
||||
.execute(exec)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
// Nothing was deleted
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ProjectTypeBuilder<'a> {
|
||||
/// The name of the project type. Must be ASCII alphanumeric or `-`/`_`
|
||||
pub fn name(
|
||||
self,
|
||||
name: &'a str,
|
||||
) -> Result<ProjectTypeBuilder<'a>, DatabaseError> {
|
||||
Ok(Self { name: Some(name) })
|
||||
Ok(result.map(|r| SideTypeId(r.id)))
|
||||
}
|
||||
|
||||
pub async fn insert<'b, E>(
|
||||
self,
|
||||
exec: E,
|
||||
) -> Result<ProjectTypeId, DatabaseError>
|
||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO project_types (name)
|
||||
VALUES ($1)
|
||||
ON CONFLICT (name) DO NOTHING
|
||||
RETURNING id
|
||||
",
|
||||
self.name
|
||||
SELECT name FROM side_types
|
||||
"
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| c.name)) })
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
Ok(ProjectTypeId(result.id))
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,22 @@ generate_ids!(
|
||||
NotificationId
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Type)]
|
||||
generate_ids!(
|
||||
pub generate_thread_id,
|
||||
ThreadId,
|
||||
8,
|
||||
"SELECT EXISTS(SELECT 1 FROM threads WHERE id=$1)",
|
||||
ThreadId
|
||||
);
|
||||
generate_ids!(
|
||||
pub generate_thread_message_id,
|
||||
ThreadMessageId,
|
||||
8,
|
||||
"SELECT EXISTS(SELECT 1 FROM threads_messages WHERE id=$1)",
|
||||
ThreadMessageId
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Type, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct UserId(pub i64);
|
||||
|
||||
@@ -169,6 +184,13 @@ pub struct NotificationId(pub i64);
|
||||
#[sqlx(transparent)]
|
||||
pub struct NotificationActionId(pub i32);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Type, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct ThreadId(pub i64);
|
||||
#[derive(Copy, Clone, Debug, Type, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct ThreadMessageId(pub i64);
|
||||
|
||||
use crate::models::ids;
|
||||
|
||||
impl From<ids::ProjectId> for ProjectId {
|
||||
@@ -231,3 +253,23 @@ impl From<NotificationId> for ids::NotificationId {
|
||||
ids::NotificationId(id.0 as u64)
|
||||
}
|
||||
}
|
||||
impl From<ids::ThreadId> for ThreadId {
|
||||
fn from(id: ids::ThreadId) -> Self {
|
||||
ThreadId(id.0 as i64)
|
||||
}
|
||||
}
|
||||
impl From<ThreadId> for ids::ThreadId {
|
||||
fn from(id: ThreadId) -> Self {
|
||||
ids::ThreadId(id.0 as u64)
|
||||
}
|
||||
}
|
||||
impl From<ids::ThreadMessageId> for ThreadMessageId {
|
||||
fn from(id: ids::ThreadMessageId) -> Self {
|
||||
ThreadMessageId(id.0 as i64)
|
||||
}
|
||||
}
|
||||
impl From<ThreadMessageId> for ids::ThreadMessageId {
|
||||
fn from(id: ThreadMessageId) -> Self {
|
||||
ids::ThreadMessageId(id.0 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#![allow(dead_code)]
|
||||
// TODO: remove attr once routes are created
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod categories;
|
||||
@@ -10,6 +6,7 @@ pub mod notification_item;
|
||||
pub mod project_item;
|
||||
pub mod report_item;
|
||||
pub mod team_item;
|
||||
pub mod thread_item;
|
||||
pub mod user_item;
|
||||
pub mod version_item;
|
||||
|
||||
@@ -17,6 +14,7 @@ pub use ids::*;
|
||||
pub use project_item::Project;
|
||||
pub use team_item::Team;
|
||||
pub use team_item::TeamMember;
|
||||
pub use thread_item::{Thread, ThreadMessage};
|
||||
pub use user_item::User;
|
||||
pub use version_item::Version;
|
||||
|
||||
@@ -28,82 +26,6 @@ pub enum DatabaseError {
|
||||
RandomId,
|
||||
#[error("A database request failed")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl ids::SideTypeId {
|
||||
pub async fn get_id<'a, E>(
|
||||
side: &crate::models::projects::SideType,
|
||||
exec: E,
|
||||
) -> Result<Option<Self>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM side_types
|
||||
WHERE name = $1
|
||||
",
|
||||
side.as_str()
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ids::SideTypeId(r.id)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ids::DonationPlatformId {
|
||||
pub async fn get_id<'a, E>(
|
||||
id: &str,
|
||||
exec: E,
|
||||
) -> Result<Option<Self>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM donation_platforms
|
||||
WHERE short = $1
|
||||
",
|
||||
id
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ids::DonationPlatformId(r.id)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ids::ProjectTypeId {
|
||||
pub async fn get_id<'a, E>(
|
||||
project_type: String,
|
||||
exec: E,
|
||||
) -> Result<Option<Self>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM project_types
|
||||
WHERE name = $1
|
||||
",
|
||||
project_type
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ProjectTypeId(r.id)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_postgres_date(input: &str) -> DateTime<Utc> {
|
||||
let mut result = DateTime::parse_from_str(input, "%Y-%m-%d %T.%f%#z");
|
||||
|
||||
if result.is_err() {
|
||||
result = DateTime::parse_from_str(input, "%Y-%m-%d %T%#z")
|
||||
}
|
||||
|
||||
result
|
||||
.map(|x| x.with_timezone(&Utc))
|
||||
.unwrap_or_else(|_| Utc::now())
|
||||
#[error("Error while parsing JSON: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ pub struct ProjectBuilder {
|
||||
pub donation_urls: Vec<DonationUrl>,
|
||||
pub gallery_items: Vec<GalleryItem>,
|
||||
pub color: Option<u32>,
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
impl ProjectBuilder {
|
||||
@@ -146,6 +147,7 @@ impl ProjectBuilder {
|
||||
color: self.color,
|
||||
loaders: vec![],
|
||||
game_versions: vec![],
|
||||
thread_id: Some(self.thread_id),
|
||||
};
|
||||
project_struct.insert(&mut *transaction).await?;
|
||||
|
||||
@@ -230,6 +232,7 @@ pub struct Project {
|
||||
pub color: Option<u32>,
|
||||
pub loaders: Vec<String>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
@@ -244,14 +247,14 @@ impl Project {
|
||||
published, downloads, icon_url, issues_url,
|
||||
source_url, wiki_url, status, requested_status, discord_url,
|
||||
client_side, server_side, license_url, license,
|
||||
slug, project_type, color
|
||||
slug, project_type, color, thread_id
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6, $7, $8, $9,
|
||||
$10, $11, $12, $13, $14,
|
||||
$15, $16, $17, $18,
|
||||
LOWER($19), $20, $21
|
||||
LOWER($19), $20, $21, $22
|
||||
)
|
||||
",
|
||||
self.id as ProjectId,
|
||||
@@ -274,7 +277,8 @@ impl Project {
|
||||
&self.license,
|
||||
self.slug.as_ref(),
|
||||
self.project_type as ProjectTypeId,
|
||||
self.color.map(|x| x as i32)
|
||||
self.color.map(|x| x as i32),
|
||||
self.thread_id.map(|x| x.0),
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -313,7 +317,7 @@ impl Project {
|
||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||
team_id, client_side, server_side, license, slug,
|
||||
moderation_message, moderation_message_body, flame_anvil_project,
|
||||
flame_anvil_user, webhook_sent, color, loaders, game_versions
|
||||
flame_anvil_user, webhook_sent, color, loaders, game_versions, thread_id
|
||||
FROM mods
|
||||
WHERE id = ANY($1)
|
||||
",
|
||||
@@ -359,6 +363,7 @@ impl Project {
|
||||
loaders: m.loaders,
|
||||
game_versions: m.game_versions,
|
||||
queued: m.queued,
|
||||
thread_id: m.thread_id.map(ThreadId),
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Project>>()
|
||||
@@ -386,6 +391,26 @@ impl Project {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let thread_id = sqlx::query!(
|
||||
"
|
||||
SELECT thread_id FROM mods
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ProjectId
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if let Some(thread_id) = thread_id {
|
||||
if let Some(id) = thread_id.thread_id {
|
||||
crate::database::models::Thread::remove_full(
|
||||
ThreadId(id),
|
||||
transaction,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM mod_follows
|
||||
@@ -654,7 +679,7 @@ impl Project {
|
||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent, m.color,
|
||||
m.loaders loaders, m.game_versions game_versions,
|
||||
m.loaders loaders, m.game_versions game_versions, m.thread_id thread_id,
|
||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
|
||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,
|
||||
JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,
|
||||
@@ -720,6 +745,7 @@ impl Project {
|
||||
loaders: m.loaders,
|
||||
game_versions: m.game_versions,
|
||||
queued: m.queued,
|
||||
thread_id: m.thread_id.map(ThreadId),
|
||||
},
|
||||
project_type: m.project_type_name,
|
||||
categories: m.categories.unwrap_or_default(),
|
||||
|
||||
@@ -10,6 +10,8 @@ pub struct Report {
|
||||
pub body: String,
|
||||
pub reporter: UserId,
|
||||
pub created: DateTime<Utc>,
|
||||
pub closed: bool,
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
pub struct QueryReport {
|
||||
@@ -21,6 +23,8 @@ pub struct QueryReport {
|
||||
pub body: String,
|
||||
pub reporter: UserId,
|
||||
pub created: DateTime<Utc>,
|
||||
pub closed: bool,
|
||||
pub thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
impl Report {
|
||||
@@ -32,11 +36,11 @@ impl Report {
|
||||
"
|
||||
INSERT INTO reports (
|
||||
id, report_type_id, mod_id, version_id, user_id,
|
||||
body, reporter
|
||||
body, reporter, thread_id
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6, $7
|
||||
$6, $7, $8
|
||||
)
|
||||
",
|
||||
self.id as ReportId,
|
||||
@@ -45,7 +49,8 @@ impl Report {
|
||||
self.version_id.map(|x| x.0 as i64),
|
||||
self.user_id.map(|x| x.0 as i64),
|
||||
self.body,
|
||||
self.reporter as UserId
|
||||
self.reporter as UserId,
|
||||
self.thread_id as ThreadId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -78,7 +83,7 @@ impl Report {
|
||||
report_ids.iter().map(|x| x.0).collect();
|
||||
let reports = sqlx::query!(
|
||||
"
|
||||
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created
|
||||
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed
|
||||
FROM reports r
|
||||
INNER JOIN report_types rt ON rt.id = r.report_type_id
|
||||
WHERE r.id = ANY($1)
|
||||
@@ -97,6 +102,8 @@ impl Report {
|
||||
body: x.body,
|
||||
reporter: UserId(x.reporter),
|
||||
created: x.created,
|
||||
closed: x.closed,
|
||||
thread_id: x.thread_id.map(ThreadId),
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<QueryReport>>()
|
||||
@@ -105,33 +112,50 @@ impl Report {
|
||||
Ok(reports)
|
||||
}
|
||||
|
||||
pub async fn remove_full<'a, E>(
|
||||
pub async fn remove_full(
|
||||
id: ReportId,
|
||||
exec: E,
|
||||
) -> Result<Option<()>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<()>, sqlx::error::Error> {
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM reports WHERE id = $1)
|
||||
",
|
||||
id as ReportId
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if !result.exists.unwrap_or(false) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let thread_id = sqlx::query!(
|
||||
"
|
||||
SELECT thread_id FROM reports
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ReportId
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if let Some(thread_id) = thread_id {
|
||||
if let Some(id) = thread_id.thread_id {
|
||||
crate::database::models::Thread::remove_full(
|
||||
ThreadId(id),
|
||||
transaction,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM reports WHERE id = $1
|
||||
",
|
||||
id as ReportId,
|
||||
)
|
||||
.execute(exec)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(Some(()))
|
||||
|
||||
@@ -104,53 +104,6 @@ pub struct QueryTeamMember {
|
||||
}
|
||||
|
||||
impl TeamMember {
|
||||
/// Lists the members of a team
|
||||
pub async fn get_from_team<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
executor: E,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, user_id, role, permissions, accepted, payouts_split, ordering
|
||||
FROM team_members
|
||||
WHERE team_id = $1
|
||||
ORDER BY ordering
|
||||
",
|
||||
id as TeamId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async {
|
||||
if let Some(m) = e.right() {
|
||||
Ok(Some(Ok(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: id,
|
||||
user_id: UserId(m.user_id),
|
||||
role: m.role,
|
||||
permissions: Permissions::from_bits(m.permissions as u64)
|
||||
.unwrap_or_default(),
|
||||
accepted: m.accepted,
|
||||
payouts_split: m.payouts_split,
|
||||
ordering: m.ordering,
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.try_collect::<Vec<Result<TeamMember, super::DatabaseError>>>()
|
||||
.await?;
|
||||
|
||||
let team_members = team_members
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<TeamMember>, super::DatabaseError>>()?;
|
||||
|
||||
Ok(team_members)
|
||||
}
|
||||
|
||||
// Lists the full members of a team
|
||||
pub async fn get_from_team_full<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
@@ -232,100 +185,6 @@ impl TeamMember {
|
||||
Ok(team_members)
|
||||
}
|
||||
|
||||
/// Lists the team members for a user. Does not list pending requests.
|
||||
pub async fn get_from_user_public<'a, 'b, E>(
|
||||
id: UserId,
|
||||
executor: E,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, team_id, role, permissions, accepted, payouts_split, ordering
|
||||
FROM team_members
|
||||
WHERE (user_id = $1 AND accepted = TRUE)
|
||||
ORDER BY ordering
|
||||
",
|
||||
id as UserId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async {
|
||||
if let Some(m) = e.right() {
|
||||
Ok(Some(Ok(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: TeamId(m.team_id),
|
||||
user_id: id,
|
||||
role: m.role,
|
||||
permissions: Permissions::from_bits(m.permissions as u64)
|
||||
.unwrap_or_default(),
|
||||
accepted: m.accepted,
|
||||
payouts_split: m.payouts_split,
|
||||
ordering: m.ordering,
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.try_collect::<Vec<Result<TeamMember, super::DatabaseError>>>()
|
||||
.await?;
|
||||
|
||||
let team_members = team_members
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<TeamMember>, super::DatabaseError>>()?;
|
||||
|
||||
Ok(team_members)
|
||||
}
|
||||
|
||||
/// Lists the team members for a user. Includes pending requests.
|
||||
pub async fn get_from_user_private<'a, 'b, E>(
|
||||
id: UserId,
|
||||
executor: E,
|
||||
) -> Result<Vec<TeamMember>, super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let team_members = sqlx::query!(
|
||||
"
|
||||
SELECT id, team_id, role, permissions, accepted, payouts_split, ordering
|
||||
FROM team_members
|
||||
WHERE user_id = $1
|
||||
ORDER BY ordering
|
||||
",
|
||||
id as UserId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async {
|
||||
if let Some(m) = e.right() {
|
||||
Ok(Some(Ok(TeamMember {
|
||||
id: TeamMemberId(m.id),
|
||||
team_id: TeamId(m.team_id),
|
||||
user_id: id,
|
||||
role: m.role,
|
||||
permissions: Permissions::from_bits(m.permissions as u64)
|
||||
.unwrap_or_default(),
|
||||
accepted: m.accepted,
|
||||
payouts_split: m.payouts_split,
|
||||
ordering: m.ordering,
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.try_collect::<Vec<Result<TeamMember, super::DatabaseError>>>()
|
||||
.await?;
|
||||
|
||||
let team_members = team_members
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<TeamMember>, super::DatabaseError>>()?;
|
||||
|
||||
Ok(team_members)
|
||||
}
|
||||
|
||||
/// Gets a team member from a user id and team id. Does not return pending members.
|
||||
pub async fn get_from_user_id<'a, 'b, E>(
|
||||
id: TeamId,
|
||||
|
||||
267
src/database/models/thread_item.rs
Normal file
267
src/database/models/thread_item.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::models::threads::{MessageBody, ThreadType};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct ThreadBuilder {
|
||||
pub type_: ThreadType,
|
||||
pub members: Vec<UserId>,
|
||||
}
|
||||
|
||||
pub struct Thread {
|
||||
pub id: ThreadId,
|
||||
pub type_: ThreadType,
|
||||
pub messages: Vec<ThreadMessage>,
|
||||
pub members: Vec<UserId>,
|
||||
}
|
||||
|
||||
pub struct ThreadMessageBuilder {
|
||||
pub author_id: Option<UserId>,
|
||||
pub body: MessageBody,
|
||||
pub thread_id: ThreadId,
|
||||
pub show_in_mod_inbox: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ThreadMessage {
|
||||
pub id: ThreadMessageId,
|
||||
pub thread_id: ThreadId,
|
||||
pub author_id: Option<UserId>,
|
||||
pub body: MessageBody,
|
||||
pub created: DateTime<Utc>,
|
||||
pub show_in_mod_inbox: bool,
|
||||
}
|
||||
|
||||
impl ThreadMessageBuilder {
|
||||
pub async fn insert(
|
||||
&self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ThreadMessageId, DatabaseError> {
|
||||
let thread_message_id =
|
||||
generate_thread_message_id(&mut *transaction).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO threads_messages (
|
||||
id, author_id, body, thread_id, show_in_mod_inbox
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5
|
||||
)
|
||||
",
|
||||
thread_message_id as ThreadMessageId,
|
||||
self.author_id.map(|x| x.0),
|
||||
serde_json::value::to_value(self.body.clone())?,
|
||||
self.thread_id as ThreadId,
|
||||
self.show_in_mod_inbox,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(thread_message_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadBuilder {
|
||||
pub async fn insert(
|
||||
&self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ThreadId, DatabaseError> {
|
||||
let thread_id = generate_thread_id(&mut *transaction).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO threads (
|
||||
id, thread_type
|
||||
)
|
||||
VALUES (
|
||||
$1, $2
|
||||
)
|
||||
",
|
||||
thread_id as ThreadId,
|
||||
self.type_.as_str(),
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
for member in &self.members {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO threads_members (
|
||||
thread_id, user_id
|
||||
)
|
||||
VALUES (
|
||||
$1, $2
|
||||
)
|
||||
",
|
||||
thread_id as ThreadId,
|
||||
*member as UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(thread_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub async fn get<'a, E>(
|
||||
id: ThreadId,
|
||||
exec: E,
|
||||
) -> Result<Option<Thread>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
Self::get_many(&[id], exec)
|
||||
.await
|
||||
.map(|x| x.into_iter().next())
|
||||
}
|
||||
|
||||
pub async fn get_many<'a, E>(
|
||||
thread_ids: &[ThreadId],
|
||||
exec: E,
|
||||
) -> Result<Vec<Thread>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let thread_ids_parsed: Vec<i64> =
|
||||
thread_ids.iter().map(|x| x.0).collect();
|
||||
let threads = sqlx::query!(
|
||||
"
|
||||
SELECT t.id, t.thread_type,
|
||||
ARRAY_AGG(DISTINCT tm.user_id) filter (where tm.user_id is not null) members,
|
||||
JSONB_AGG(DISTINCT jsonb_build_object('id', tmsg.id, 'author_id', tmsg.author_id, 'thread_id', tmsg.thread_id, 'body', tmsg.body, 'created', tmsg.created)) filter (where tmsg.id is not null) messages
|
||||
FROM threads t
|
||||
LEFT OUTER JOIN threads_messages tmsg ON tmsg.thread_id = t.id
|
||||
LEFT OUTER JOIN threads_members tm ON tm.thread_id = t.id
|
||||
WHERE t.id = ANY($1)
|
||||
GROUP BY t.id
|
||||
",
|
||||
&thread_ids_parsed
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|x| Thread {
|
||||
id: ThreadId(x.id),
|
||||
type_: ThreadType::from_str(&x.thread_type),
|
||||
messages: serde_json::from_value(
|
||||
x.messages.unwrap_or_default(),
|
||||
)
|
||||
.ok()
|
||||
.unwrap_or_default(),
|
||||
members: x.members.unwrap_or_default().into_iter().map(UserId).collect(),
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Thread>>()
|
||||
.await?;
|
||||
|
||||
Ok(threads)
|
||||
}
|
||||
|
||||
pub async fn remove_full(
|
||||
id: ThreadId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<()>, sqlx::error::Error> {
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM threads_messages
|
||||
WHERE thread_id = $1
|
||||
",
|
||||
id as ThreadId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM threads_members
|
||||
WHERE thread_id = $1
|
||||
",
|
||||
id as ThreadId
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM threads
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ThreadId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadMessage {
|
||||
pub async fn get<'a, E>(
|
||||
id: ThreadMessageId,
|
||||
exec: E,
|
||||
) -> Result<Option<ThreadMessage>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
Self::get_many(&[id], exec)
|
||||
.await
|
||||
.map(|x| x.into_iter().next())
|
||||
}
|
||||
|
||||
pub async fn get_many<'a, E>(
|
||||
message_ids: &[ThreadMessageId],
|
||||
exec: E,
|
||||
) -> Result<Vec<ThreadMessage>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let message_ids_parsed: Vec<i64> =
|
||||
message_ids.iter().map(|x| x.0).collect();
|
||||
let messages = sqlx::query!(
|
||||
"
|
||||
SELECT tm.id, tm.author_id, tm.thread_id, tm.body, tm.created, tm.show_in_mod_inbox
|
||||
FROM threads_messages tm
|
||||
WHERE tm.id = ANY($1)
|
||||
",
|
||||
&message_ids_parsed
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|x| ThreadMessage {
|
||||
id: ThreadMessageId(x.id),
|
||||
thread_id: ThreadId(x.thread_id),
|
||||
author_id: x.author_id.map(UserId),
|
||||
body: serde_json::from_value(x.body)
|
||||
.unwrap_or(MessageBody::Deleted),
|
||||
created: x.created,
|
||||
show_in_mod_inbox: x.show_in_mod_inbox,
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<ThreadMessage>>()
|
||||
.await?;
|
||||
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub async fn remove_full(
|
||||
id: ThreadMessageId,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<()>, sqlx::error::Error> {
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM threads_messages
|
||||
WHERE id = $1
|
||||
",
|
||||
id as ThreadMessageId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
@@ -451,6 +451,28 @@ impl User {
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE threads_messages
|
||||
SET body = '{"type": "deleted"}', author_id = $2
|
||||
WHERE author_id = $1
|
||||
"#,
|
||||
id as UserId,
|
||||
deleted_user as UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM threads_members
|
||||
WHERE user_id = $1
|
||||
",
|
||||
id as UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM users
|
||||
|
||||
@@ -568,66 +568,6 @@ impl Version {
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
pub async fn get<'a, 'b, E>(
|
||||
id: VersionId,
|
||||
executor: E,
|
||||
) -> Result<Option<Self>, sqlx::error::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
Self::get_many(&[id], executor)
|
||||
.await
|
||||
.map(|x| x.into_iter().next())
|
||||
}
|
||||
|
||||
pub async fn get_many<'a, E>(
|
||||
version_ids: &[VersionId],
|
||||
exec: E,
|
||||
) -> Result<Vec<Version>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let version_ids_parsed: Vec<i64> =
|
||||
version_ids.iter().map(|x| x.0).collect();
|
||||
let versions = sqlx::query!(
|
||||
"
|
||||
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
|
||||
v.changelog, v.date_published, v.downloads,
|
||||
v.version_type, v.featured, v.status, v.requested_status
|
||||
FROM versions v
|
||||
WHERE v.id = ANY($1)
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
&version_ids_parsed
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|v| Version {
|
||||
id: VersionId(v.id),
|
||||
project_id: ProjectId(v.mod_id),
|
||||
author_id: UserId(v.author_id),
|
||||
name: v.name,
|
||||
version_number: v.version_number,
|
||||
changelog: v.changelog,
|
||||
changelog_url: None,
|
||||
date_published: v.date_published,
|
||||
downloads: v.downloads,
|
||||
featured: v.featured,
|
||||
version_type: v.version_type,
|
||||
status: VersionStatus::from_str(&v.status),
|
||||
requested_status: v
|
||||
.requested_status
|
||||
.map(|x| VersionStatus::from_str(&x)),
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Version>>()
|
||||
.await?;
|
||||
|
||||
Ok(versions)
|
||||
}
|
||||
|
||||
pub async fn get_full<'a, 'b, E>(
|
||||
id: VersionId,
|
||||
executor: E,
|
||||
|
||||
Reference in New Issue
Block a user