Added a route for importing templates
This commit is contained in:
parent
b8ee99cb45
commit
0a9c390f32
@ -195,7 +195,7 @@ pub async fn initialize(
|
|||||||
routes::dashboard::api::guild::get_guild_roles,
|
routes::dashboard::api::guild::get_guild_roles,
|
||||||
routes::dashboard::api::guild::get_guild_emojis,
|
routes::dashboard::api::guild::get_guild_emojis,
|
||||||
routes::dashboard::api::guild::get_reminder_templates,
|
routes::dashboard::api::guild::get_reminder_templates,
|
||||||
routes::dashboard::api::guild::create_reminder_template,
|
routes::dashboard::api::guild::create_guild_reminder_template,
|
||||||
routes::dashboard::api::guild::delete_reminder_template,
|
routes::dashboard::api::guild::delete_reminder_template,
|
||||||
routes::dashboard::api::guild::create_guild_reminder,
|
routes::dashboard::api::guild::create_guild_reminder,
|
||||||
routes::dashboard::api::guild::get_reminders,
|
routes::dashboard::api::guild::get_reminders,
|
||||||
@ -204,11 +204,12 @@ pub async fn initialize(
|
|||||||
routes::dashboard::api::guild::todos::get_todo,
|
routes::dashboard::api::guild::todos::get_todo,
|
||||||
routes::dashboard::api::guild::todos::update_todo,
|
routes::dashboard::api::guild::todos::update_todo,
|
||||||
routes::dashboard::api::guild::todos::delete_todo,
|
routes::dashboard::api::guild::todos::delete_todo,
|
||||||
routes::dashboard::export::export_reminders,
|
routes::dashboard::export::reminders::export,
|
||||||
routes::dashboard::export::export_reminder_templates,
|
routes::dashboard::export::reminders::import,
|
||||||
routes::dashboard::export::export_todos,
|
routes::dashboard::export::reminder_templates::export,
|
||||||
routes::dashboard::export::import_reminders,
|
routes::dashboard::export::reminder_templates::import,
|
||||||
routes::dashboard::export::import_todos,
|
routes::dashboard::export::todos::export,
|
||||||
|
routes::dashboard::export::todos::import,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.launch()
|
.launch()
|
||||||
|
@ -6,9 +6,12 @@ use rocket::{
|
|||||||
serde::json::{json, Json},
|
serde::json::{json, Json},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
|
use serenity::all::GuildId;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
use crate::web::guards::transaction::Transaction;
|
||||||
|
use crate::web::routes::dashboard::create_reminder_template;
|
||||||
use crate::web::{
|
use crate::web::{
|
||||||
check_authorization,
|
check_authorization,
|
||||||
consts::{
|
consts::{
|
||||||
@ -49,109 +52,32 @@ pub async fn get_reminder_templates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/api/guild/<id>/templates", data = "<reminder_template>")]
|
#[post("/api/guild/<id>/templates", data = "<reminder_template>")]
|
||||||
pub async fn create_reminder_template(
|
pub async fn create_guild_reminder_template(
|
||||||
id: u64,
|
id: u64,
|
||||||
reminder_template: Json<ReminderTemplate>,
|
reminder_template: Json<ReminderTemplate>,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
ctx: &State<Context>,
|
ctx: &State<Context>,
|
||||||
pool: &State<Pool<MySql>>,
|
mut transaction: Transaction<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
check_authorization(cookies, ctx.inner(), id).await?;
|
check_authorization(cookies, ctx.inner(), id).await?;
|
||||||
|
|
||||||
// validate lengths
|
match create_reminder_template(
|
||||||
check_length!(MAX_CONTENT_LENGTH, reminder_template.content);
|
ctx.inner(),
|
||||||
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder_template.embed_description);
|
&mut transaction,
|
||||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder_template.embed_title);
|
GuildId::new(id),
|
||||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder_template.embed_author);
|
reminder_template.into_inner(),
|
||||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder_template.embed_footer);
|
|
||||||
check_length_opt!(MAX_EMBED_FIELDS, reminder_template.embed_fields);
|
|
||||||
if let Some(fields) = &reminder_template.embed_fields {
|
|
||||||
for field in &fields.0 {
|
|
||||||
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
|
||||||
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check_length_opt!(MAX_USERNAME_LENGTH, reminder_template.username);
|
|
||||||
check_length_opt!(
|
|
||||||
MAX_URL_LENGTH,
|
|
||||||
reminder_template.embed_footer_url,
|
|
||||||
reminder_template.embed_thumbnail_url,
|
|
||||||
reminder_template.embed_author_url,
|
|
||||||
reminder_template.embed_image_url,
|
|
||||||
reminder_template.avatar
|
|
||||||
);
|
|
||||||
|
|
||||||
// validate urls
|
|
||||||
check_url_opt!(
|
|
||||||
reminder_template.embed_footer_url,
|
|
||||||
reminder_template.embed_thumbnail_url,
|
|
||||||
reminder_template.embed_author_url,
|
|
||||||
reminder_template.embed_image_url,
|
|
||||||
reminder_template.avatar
|
|
||||||
);
|
|
||||||
|
|
||||||
let name = if reminder_template.name.is_empty() {
|
|
||||||
template_name_default()
|
|
||||||
} else {
|
|
||||||
reminder_template.name.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
match sqlx::query!(
|
|
||||||
"INSERT INTO reminder_template
|
|
||||||
(guild_id,
|
|
||||||
name,
|
|
||||||
attachment,
|
|
||||||
attachment_name,
|
|
||||||
avatar,
|
|
||||||
content,
|
|
||||||
embed_author,
|
|
||||||
embed_author_url,
|
|
||||||
embed_color,
|
|
||||||
embed_description,
|
|
||||||
embed_footer,
|
|
||||||
embed_footer_url,
|
|
||||||
embed_image_url,
|
|
||||||
embed_thumbnail_url,
|
|
||||||
embed_title,
|
|
||||||
embed_fields,
|
|
||||||
interval_seconds,
|
|
||||||
interval_days,
|
|
||||||
interval_months,
|
|
||||||
tts,
|
|
||||||
username
|
|
||||||
) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
reminder_template.attachment,
|
|
||||||
reminder_template.attachment_name,
|
|
||||||
reminder_template.avatar,
|
|
||||||
reminder_template.content,
|
|
||||||
reminder_template.embed_author,
|
|
||||||
reminder_template.embed_author_url,
|
|
||||||
reminder_template.embed_color,
|
|
||||||
reminder_template.embed_description,
|
|
||||||
reminder_template.embed_footer,
|
|
||||||
reminder_template.embed_footer_url,
|
|
||||||
reminder_template.embed_image_url,
|
|
||||||
reminder_template.embed_thumbnail_url,
|
|
||||||
reminder_template.embed_title,
|
|
||||||
reminder_template.embed_fields,
|
|
||||||
reminder_template.interval_seconds,
|
|
||||||
reminder_template.interval_days,
|
|
||||||
reminder_template.interval_months,
|
|
||||||
reminder_template.tts,
|
|
||||||
reminder_template.username,
|
|
||||||
)
|
)
|
||||||
.fetch_all(pool.inner())
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Ok(json!({})),
|
Ok(r) => match transaction.commit().await {
|
||||||
Err(e) => {
|
Ok(_) => Ok(r),
|
||||||
warn!("Could not create template for {}: {:?}", id, e);
|
Err(e) => {
|
||||||
|
warn!("Couldn't commit transaction: {:?}", e);
|
||||||
|
json_err!("Couldn't commit transaction.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
json_err!("Could not create template")
|
Err(e) => Err(e),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
src/web/routes/dashboard/export/mod.rs
Normal file
3
src/web/routes/dashboard/export/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod reminder_templates;
|
||||||
|
pub mod reminders;
|
||||||
|
pub mod todos;
|
181
src/web/routes/dashboard/export/reminder_templates.rs
Normal file
181
src/web/routes/dashboard/export/reminder_templates.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use crate::web::routes::dashboard::{create_reminder_template, ReminderTemplate};
|
||||||
|
use crate::web::{
|
||||||
|
check_authorization,
|
||||||
|
guards::transaction::Transaction,
|
||||||
|
routes::{
|
||||||
|
dashboard::{
|
||||||
|
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
||||||
|
},
|
||||||
|
JsonResult,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::Database;
|
||||||
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
|
use log::warn;
|
||||||
|
use rocket::{
|
||||||
|
get,
|
||||||
|
http::CookieJar,
|
||||||
|
put,
|
||||||
|
serde::json::{json, Json},
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
|
};
|
||||||
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
#[get("/api/guild/<id>/export/reminder_templates")]
|
||||||
|
pub async fn export(
|
||||||
|
id: u64,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
ctx: &State<Context>,
|
||||||
|
pool: &State<Pool<MySql>>,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_authorization(cookies, ctx.inner(), id).await?;
|
||||||
|
|
||||||
|
let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
|
||||||
|
|
||||||
|
match sqlx::query_as_unchecked!(
|
||||||
|
ReminderTemplateCsv,
|
||||||
|
"SELECT
|
||||||
|
name,
|
||||||
|
attachment,
|
||||||
|
attachment_name,
|
||||||
|
avatar,
|
||||||
|
content,
|
||||||
|
embed_author,
|
||||||
|
embed_author_url,
|
||||||
|
embed_color,
|
||||||
|
embed_description,
|
||||||
|
embed_footer,
|
||||||
|
embed_footer_url,
|
||||||
|
embed_image_url,
|
||||||
|
embed_thumbnail_url,
|
||||||
|
embed_title,
|
||||||
|
embed_fields,
|
||||||
|
interval_seconds,
|
||||||
|
interval_days,
|
||||||
|
interval_months,
|
||||||
|
tts,
|
||||||
|
username
|
||||||
|
FROM reminder_template WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(templates) => {
|
||||||
|
templates.iter().for_each(|template| {
|
||||||
|
csv_writer.serialize(template).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
match csv_writer.into_inner() {
|
||||||
|
Ok(inner) => match String::from_utf8(inner) {
|
||||||
|
Ok(encoded) => Ok(json!({ "body": encoded })),
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to write UTF-8: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("Failed to write UTF-8")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to extract CSV: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("Failed to extract CSV")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not fetch templates from {}: {:?}", id, e);
|
||||||
|
|
||||||
|
json_err!("Failed to query templates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/api/guild/<id>/export/reminder_templates", data = "<body>")]
|
||||||
|
pub async fn import(
|
||||||
|
id: u64,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
body: Json<ImportBody>,
|
||||||
|
ctx: &State<Context>,
|
||||||
|
mut transaction: Transaction<'_>,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_authorization(cookies, ctx.inner(), id).await?;
|
||||||
|
|
||||||
|
match BASE64_STANDARD.decode(&body.body) {
|
||||||
|
Ok(body) => {
|
||||||
|
let mut reader = csv::Reader::from_reader(body.as_slice());
|
||||||
|
let mut count = 0;
|
||||||
|
|
||||||
|
for result in reader.deserialize::<ReminderTemplateCsv>() {
|
||||||
|
match result {
|
||||||
|
Ok(record) => {
|
||||||
|
let reminder_template = ReminderTemplate {
|
||||||
|
id: 0,
|
||||||
|
guild_id: 0,
|
||||||
|
name: record.name,
|
||||||
|
attachment: record.attachment,
|
||||||
|
attachment_name: record.attachment_name,
|
||||||
|
avatar: record.avatar,
|
||||||
|
content: record.content,
|
||||||
|
embed_author: record.embed_author,
|
||||||
|
embed_author_url: record.embed_author_url,
|
||||||
|
embed_color: record.embed_color,
|
||||||
|
embed_description: record.embed_description,
|
||||||
|
embed_footer: record.embed_footer,
|
||||||
|
embed_footer_url: record.embed_footer_url,
|
||||||
|
embed_image_url: record.embed_image_url,
|
||||||
|
embed_thumbnail_url: record.embed_thumbnail_url,
|
||||||
|
embed_title: record.embed_title,
|
||||||
|
embed_fields: record
|
||||||
|
.embed_fields
|
||||||
|
.map(|s| serde_json::from_str(&s).ok())
|
||||||
|
.flatten(),
|
||||||
|
interval_seconds: record.interval_seconds,
|
||||||
|
interval_days: record.interval_days,
|
||||||
|
interval_months: record.interval_months,
|
||||||
|
tts: record.tts,
|
||||||
|
username: record.username,
|
||||||
|
};
|
||||||
|
|
||||||
|
create_reminder_template(
|
||||||
|
ctx.inner(),
|
||||||
|
&mut transaction,
|
||||||
|
GuildId::new(id),
|
||||||
|
reminder_template,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Couldn't deserialize CSV row: {:?}", e);
|
||||||
|
|
||||||
|
return json_err!(format!("Deserialize error: {:?}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match transaction.commit().await {
|
||||||
|
Ok(_) => Ok(json!({
|
||||||
|
"message": format!("Imported {} reminder templates", count)
|
||||||
|
})),
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to commit transaction: {:?}", e);
|
||||||
|
json_err!("Couldn't commit transaction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
json_err!("Malformed base64")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ use serenity::{
|
|||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
#[get("/api/guild/<id>/export/reminders")]
|
#[get("/api/guild/<id>/export/reminders")]
|
||||||
pub async fn export_reminders(
|
pub async fn export(
|
||||||
id: u64,
|
id: u64,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
ctx: &State<Context>,
|
ctx: &State<Context>,
|
||||||
@ -127,7 +127,7 @@ pub async fn export_reminders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[put("/api/guild/<id>/export/reminders", data = "<body>")]
|
#[put("/api/guild/<id>/export/reminders", data = "<body>")]
|
||||||
pub(crate) async fn import_reminders(
|
pub async fn import(
|
||||||
id: u64,
|
id: u64,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
body: Json<ImportBody>,
|
body: Json<ImportBody>,
|
||||||
@ -228,224 +228,3 @@ pub(crate) async fn import_reminders(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/guild/<id>/export/todos")]
|
|
||||||
pub async fn export_todos(
|
|
||||||
id: u64,
|
|
||||||
cookies: &CookieJar<'_>,
|
|
||||||
ctx: &State<Context>,
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
) -> JsonResult {
|
|
||||||
check_authorization(cookies, ctx.inner(), id).await?;
|
|
||||||
|
|
||||||
let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
|
|
||||||
|
|
||||||
match sqlx::query_as_unchecked!(
|
|
||||||
TodoCsv,
|
|
||||||
"SELECT value, CONCAT('#', channels.channel) AS channel_id FROM todos
|
|
||||||
LEFT JOIN channels ON todos.channel_id = channels.id
|
|
||||||
INNER JOIN guilds ON todos.guild_id = guilds.id
|
|
||||||
WHERE guilds.guild = ?",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
.fetch_all(pool.inner())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(todos) => {
|
|
||||||
todos.iter().for_each(|todo| {
|
|
||||||
csv_writer.serialize(todo).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
match csv_writer.into_inner() {
|
|
||||||
Ok(inner) => match String::from_utf8(inner) {
|
|
||||||
Ok(encoded) => Ok(json!({ "body": encoded })),
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to write UTF-8: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("Failed to write UTF-8")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to extract CSV: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("Failed to extract CSV")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Could not fetch templates from {}: {:?}", id, e);
|
|
||||||
|
|
||||||
json_err!("Failed to query templates")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/api/guild/<id>/export/todos", data = "<body>")]
|
|
||||||
pub async fn import_todos(
|
|
||||||
id: u64,
|
|
||||||
cookies: &CookieJar<'_>,
|
|
||||||
body: Json<ImportBody>,
|
|
||||||
ctx: &State<Context>,
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
) -> JsonResult {
|
|
||||||
check_authorization(cookies, ctx.inner(), id).await?;
|
|
||||||
|
|
||||||
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
|
|
||||||
|
|
||||||
match channels_res {
|
|
||||||
Ok(channels) => match BASE64_STANDARD.decode(&body.body) {
|
|
||||||
Ok(body) => {
|
|
||||||
let mut reader = csv::Reader::from_reader(body.as_slice());
|
|
||||||
|
|
||||||
let query_placeholder = "(?, (SELECT id FROM channels WHERE channel = ?), (SELECT id FROM guilds WHERE guild = ?))";
|
|
||||||
let mut query_params = vec![];
|
|
||||||
|
|
||||||
for result in reader.deserialize::<TodoCsv>() {
|
|
||||||
match result {
|
|
||||||
Ok(record) => match record.channel_id {
|
|
||||||
Some(channel_id) => {
|
|
||||||
let channel_id = channel_id.split_at(1).1;
|
|
||||||
|
|
||||||
match channel_id.parse::<u64>() {
|
|
||||||
Ok(channel_id) => {
|
|
||||||
if channels.contains_key(&ChannelId::new(channel_id)) {
|
|
||||||
query_params.push((record.value, Some(channel_id), id));
|
|
||||||
} else {
|
|
||||||
return json_err!(format!(
|
|
||||||
"Invalid channel ID {}",
|
|
||||||
channel_id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
return json_err!(format!(
|
|
||||||
"Invalid channel ID {}",
|
|
||||||
channel_id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
|
||||||
query_params.push((record.value, None, id));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Couldn't deserialize CSV row: {:?}", e);
|
|
||||||
|
|
||||||
return json_err!("Deserialize error. Aborted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let query_str = format!(
|
|
||||||
"INSERT INTO todos (value, channel_id, guild_id) VALUES {}",
|
|
||||||
vec![query_placeholder].repeat(query_params.len()).join(",")
|
|
||||||
);
|
|
||||||
let mut query = sqlx::query(&query_str);
|
|
||||||
|
|
||||||
for param in query_params {
|
|
||||||
query = query.bind(param.0).bind(param.1).bind(param.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = query.execute(pool.inner()).await;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(_) => Ok(json!({})),
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Couldn't execute todo query: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("An unexpected error occured.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
json_err!("Malformed base64")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Couldn't fetch channels for guild {}: {:?}", id, e);
|
|
||||||
|
|
||||||
json_err!("Couldn't fetch channels.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/guild/<id>/export/reminder_templates")]
|
|
||||||
pub async fn export_reminder_templates(
|
|
||||||
id: u64,
|
|
||||||
cookies: &CookieJar<'_>,
|
|
||||||
ctx: &State<Context>,
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
) -> JsonResult {
|
|
||||||
check_authorization(cookies, ctx.inner(), id).await?;
|
|
||||||
|
|
||||||
let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
|
|
||||||
|
|
||||||
match sqlx::query_as_unchecked!(
|
|
||||||
ReminderTemplateCsv,
|
|
||||||
"SELECT
|
|
||||||
name,
|
|
||||||
attachment,
|
|
||||||
attachment_name,
|
|
||||||
avatar,
|
|
||||||
content,
|
|
||||||
embed_author,
|
|
||||||
embed_author_url,
|
|
||||||
embed_color,
|
|
||||||
embed_description,
|
|
||||||
embed_footer,
|
|
||||||
embed_footer_url,
|
|
||||||
embed_image_url,
|
|
||||||
embed_thumbnail_url,
|
|
||||||
embed_title,
|
|
||||||
embed_fields,
|
|
||||||
interval_seconds,
|
|
||||||
interval_days,
|
|
||||||
interval_months,
|
|
||||||
tts,
|
|
||||||
username
|
|
||||||
FROM reminder_template WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
.fetch_all(pool.inner())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(templates) => {
|
|
||||||
templates.iter().for_each(|template| {
|
|
||||||
csv_writer.serialize(template).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
match csv_writer.into_inner() {
|
|
||||||
Ok(inner) => match String::from_utf8(inner) {
|
|
||||||
Ok(encoded) => Ok(json!({ "body": encoded })),
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to write UTF-8: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("Failed to write UTF-8")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to extract CSV: {:?}", e);
|
|
||||||
|
|
||||||
json_err!("Failed to extract CSV")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Could not fetch templates from {}: {:?}", id, e);
|
|
||||||
|
|
||||||
json_err!("Failed to query templates")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
176
src/web/routes/dashboard/export/todos.rs
Normal file
176
src/web/routes/dashboard/export/todos.rs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
use crate::web::{
|
||||||
|
check_authorization,
|
||||||
|
guards::transaction::Transaction,
|
||||||
|
routes::{
|
||||||
|
dashboard::{
|
||||||
|
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
||||||
|
},
|
||||||
|
JsonResult,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::Database;
|
||||||
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
|
use log::warn;
|
||||||
|
use rocket::{
|
||||||
|
get,
|
||||||
|
http::CookieJar,
|
||||||
|
put,
|
||||||
|
serde::json::{json, Json},
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
|
};
|
||||||
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
#[get("/api/guild/<id>/export/todos")]
|
||||||
|
pub async fn export(
|
||||||
|
id: u64,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
ctx: &State<Context>,
|
||||||
|
pool: &State<Pool<MySql>>,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_authorization(cookies, ctx.inner(), id).await?;
|
||||||
|
|
||||||
|
let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);
|
||||||
|
|
||||||
|
match sqlx::query_as_unchecked!(
|
||||||
|
TodoCsv,
|
||||||
|
"SELECT value, CONCAT('#', channels.channel) AS channel_id FROM todos
|
||||||
|
LEFT JOIN channels ON todos.channel_id = channels.id
|
||||||
|
INNER JOIN guilds ON todos.guild_id = guilds.id
|
||||||
|
WHERE guilds.guild = ?",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(todos) => {
|
||||||
|
todos.iter().for_each(|todo| {
|
||||||
|
csv_writer.serialize(todo).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
match csv_writer.into_inner() {
|
||||||
|
Ok(inner) => match String::from_utf8(inner) {
|
||||||
|
Ok(encoded) => Ok(json!({ "body": encoded })),
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to write UTF-8: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("Failed to write UTF-8")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to extract CSV: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("Failed to extract CSV")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not fetch templates from {}: {:?}", id, e);
|
||||||
|
|
||||||
|
json_err!("Failed to query templates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/api/guild/<id>/export/todos", data = "<body>")]
|
||||||
|
pub async fn import(
|
||||||
|
id: u64,
|
||||||
|
cookies: &CookieJar<'_>,
|
||||||
|
body: Json<ImportBody>,
|
||||||
|
ctx: &State<Context>,
|
||||||
|
pool: &State<Pool<MySql>>,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_authorization(cookies, ctx.inner(), id).await?;
|
||||||
|
|
||||||
|
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
|
||||||
|
|
||||||
|
match channels_res {
|
||||||
|
Ok(channels) => match BASE64_STANDARD.decode(&body.body) {
|
||||||
|
Ok(body) => {
|
||||||
|
let mut reader = csv::Reader::from_reader(body.as_slice());
|
||||||
|
|
||||||
|
let query_placeholder = "(?, (SELECT id FROM channels WHERE channel = ?), (SELECT id FROM guilds WHERE guild = ?))";
|
||||||
|
let mut query_params = vec![];
|
||||||
|
|
||||||
|
for result in reader.deserialize::<TodoCsv>() {
|
||||||
|
match result {
|
||||||
|
Ok(record) => match record.channel_id {
|
||||||
|
Some(channel_id) => {
|
||||||
|
let channel_id = channel_id.split_at(1).1;
|
||||||
|
|
||||||
|
match channel_id.parse::<u64>() {
|
||||||
|
Ok(channel_id) => {
|
||||||
|
if channels.contains_key(&ChannelId::new(channel_id)) {
|
||||||
|
query_params.push((record.value, Some(channel_id), id));
|
||||||
|
} else {
|
||||||
|
return json_err!(format!(
|
||||||
|
"Invalid channel ID {}",
|
||||||
|
channel_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
return json_err!(format!(
|
||||||
|
"Invalid channel ID {}",
|
||||||
|
channel_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
query_params.push((record.value, None, id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Couldn't deserialize CSV row: {:?}", e);
|
||||||
|
|
||||||
|
return json_err!("Deserialize error. Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let query_str = format!(
|
||||||
|
"INSERT INTO todos (value, channel_id, guild_id) VALUES {}",
|
||||||
|
vec![query_placeholder].repeat(query_params.len()).join(",")
|
||||||
|
);
|
||||||
|
let mut query = sqlx::query(&query_str);
|
||||||
|
|
||||||
|
for param in query_params {
|
||||||
|
query = query.bind(param.0).bind(param.1).bind(param.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = query.execute(pool.inner()).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => Ok(json!({})),
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Couldn't execute todo query: {:?}", e);
|
||||||
|
|
||||||
|
json_err!("An unexpected error occured.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
json_err!("Malformed base64")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Couldn't fetch channels for guild {}: {:?}", id, e);
|
||||||
|
|
||||||
|
json_err!("Couldn't fetch channels.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -593,12 +593,113 @@ pub(crate) async fn create_reminder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_channel_matches_guild(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -> bool {
|
pub(crate) async fn create_reminder_template(
|
||||||
return match ctx.cache.guild(guild_id) {
|
ctx: &Context,
|
||||||
Some(guild) => guild.channels.get(&channel_id).is_some(),
|
transaction: &mut Transaction<'_>,
|
||||||
|
guild_id: GuildId,
|
||||||
|
reminder_template: ReminderTemplate,
|
||||||
|
) -> JsonResult {
|
||||||
|
check_length!(MAX_CONTENT_LENGTH, reminder_template.content);
|
||||||
|
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder_template.embed_description);
|
||||||
|
check_length!(MAX_EMBED_TITLE_LENGTH, reminder_template.embed_title);
|
||||||
|
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder_template.embed_author);
|
||||||
|
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder_template.embed_footer);
|
||||||
|
check_length_opt!(MAX_EMBED_FIELDS, reminder_template.embed_fields);
|
||||||
|
if let Some(fields) = &reminder_template.embed_fields {
|
||||||
|
for field in &fields.0 {
|
||||||
|
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||||
|
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_length_opt!(MAX_USERNAME_LENGTH, reminder_template.username);
|
||||||
|
check_length_opt!(
|
||||||
|
MAX_URL_LENGTH,
|
||||||
|
reminder_template.embed_footer_url,
|
||||||
|
reminder_template.embed_thumbnail_url,
|
||||||
|
reminder_template.embed_author_url,
|
||||||
|
reminder_template.embed_image_url,
|
||||||
|
reminder_template.avatar
|
||||||
|
);
|
||||||
|
|
||||||
None => false,
|
// validate urls
|
||||||
|
check_url_opt!(
|
||||||
|
reminder_template.embed_footer_url,
|
||||||
|
reminder_template.embed_thumbnail_url,
|
||||||
|
reminder_template.embed_author_url,
|
||||||
|
reminder_template.embed_image_url,
|
||||||
|
reminder_template.avatar
|
||||||
|
);
|
||||||
|
|
||||||
|
let name = if reminder_template.name.is_empty() {
|
||||||
|
template_name_default()
|
||||||
|
} else {
|
||||||
|
reminder_template.name.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match sqlx::query!(
|
||||||
|
"INSERT INTO reminder_template
|
||||||
|
(guild_id,
|
||||||
|
name,
|
||||||
|
attachment,
|
||||||
|
attachment_name,
|
||||||
|
avatar,
|
||||||
|
content,
|
||||||
|
embed_author,
|
||||||
|
embed_author_url,
|
||||||
|
embed_color,
|
||||||
|
embed_description,
|
||||||
|
embed_footer,
|
||||||
|
embed_footer_url,
|
||||||
|
embed_image_url,
|
||||||
|
embed_thumbnail_url,
|
||||||
|
embed_title,
|
||||||
|
embed_fields,
|
||||||
|
interval_seconds,
|
||||||
|
interval_days,
|
||||||
|
interval_months,
|
||||||
|
tts,
|
||||||
|
username
|
||||||
|
) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
guild_id.get(),
|
||||||
|
name,
|
||||||
|
reminder_template.attachment,
|
||||||
|
reminder_template.attachment_name,
|
||||||
|
reminder_template.avatar,
|
||||||
|
reminder_template.content,
|
||||||
|
reminder_template.embed_author,
|
||||||
|
reminder_template.embed_author_url,
|
||||||
|
reminder_template.embed_color,
|
||||||
|
reminder_template.embed_description,
|
||||||
|
reminder_template.embed_footer,
|
||||||
|
reminder_template.embed_footer_url,
|
||||||
|
reminder_template.embed_image_url,
|
||||||
|
reminder_template.embed_thumbnail_url,
|
||||||
|
reminder_template.embed_title,
|
||||||
|
reminder_template.embed_fields,
|
||||||
|
reminder_template.interval_seconds,
|
||||||
|
reminder_template.interval_days,
|
||||||
|
reminder_template.interval_months,
|
||||||
|
reminder_template.tts,
|
||||||
|
reminder_template.username,
|
||||||
|
)
|
||||||
|
.fetch_all(transaction.executor())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(json!({})),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not create template for {}: {:?}", guild_id.get(), e);
|
||||||
|
|
||||||
|
json_err!("Could not create template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_channel_matches_guild(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -> bool {
|
||||||
|
match ctx.cache.guild(guild_id) {
|
||||||
|
Some(guild) => guild.channels.get(&channel_id).is_some(),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_database_channel(
|
async fn create_database_channel(
|
||||||
|
Loading…
Reference in New Issue
Block a user