cargo fmt
This commit is contained in:
parent
6286b73b4b
commit
79705d03a4
19 changed files with 178 additions and 115 deletions
|
@ -9,4 +9,4 @@ pub mod user {
|
||||||
pub const USER_AVATAR_COOKIE: &str = "user_avatar";
|
pub const USER_AVATAR_COOKIE: &str = "user_avatar";
|
||||||
}
|
}
|
||||||
pub const LANG_COOKIE: &str = "lang";
|
pub const LANG_COOKIE: &str = "lang";
|
||||||
pub const WELCOMED_COOKIE: &str = "welcomed";
|
pub const WELCOMED_COOKIE: &str = "welcomed";
|
||||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -1,14 +1,17 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
use poise::serenity_prelude::Http;
|
use poise::serenity_prelude::Http;
|
||||||
use rocket::{fs::{relative, FileServer}, Rocket, Ignite};
|
use rocket::{
|
||||||
|
fs::{relative, FileServer},
|
||||||
|
Ignite, Rocket,
|
||||||
|
};
|
||||||
use rocket_dyn_templates::{tera, Template};
|
use rocket_dyn_templates::{tera, Template};
|
||||||
use std::{collections::HashMap, env};
|
use std::{collections::HashMap, env};
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
use models::{Settings, Database};
|
use models::{Database, Settings};
|
||||||
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
@ -52,13 +55,14 @@ async fn main() {
|
||||||
}
|
}
|
||||||
let http = http();
|
let http = http();
|
||||||
load_database()
|
load_database()
|
||||||
.load_legacy(&http).await
|
.load_legacy(&http)
|
||||||
|
.await
|
||||||
.expect("Failed to load legacy submissions");
|
.expect("Failed to load legacy submissions");
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
rocket().await.expect("Failed to launch rocket");
|
rocket().await.expect("Failed to launch rocket");
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +90,16 @@ async fn rocket() -> Result<Rocket<Ignite>, rocket::Error> {
|
||||||
.manage(load_database())
|
.manage(load_database())
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![get_challenge, get_guilds, get_user, login, post_login, success, logout, testing],
|
routes![
|
||||||
|
get_challenge,
|
||||||
|
get_guilds,
|
||||||
|
get_user,
|
||||||
|
login,
|
||||||
|
post_login,
|
||||||
|
success,
|
||||||
|
logout,
|
||||||
|
testing
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.mount("/css", FileServer::from(relative!("styles/css")))
|
.mount("/css", FileServer::from(relative!("styles/css")))
|
||||||
.mount("/", FileServer::from(relative!("assets")).rank(2))
|
.mount("/", FileServer::from(relative!("assets")).rank(2))
|
||||||
|
@ -108,12 +121,12 @@ async fn rocket() -> Result<Rocket<Ignite>, rocket::Error> {
|
||||||
);
|
);
|
||||||
engines.tera.register_filter(
|
engines.tera.register_filter(
|
||||||
"furigana",
|
"furigana",
|
||||||
move |value: &Value, args: &HashMap<String, Value>| {
|
move |value: &Value, args: &HashMap<String, Value>| furigana_filter(value, args),
|
||||||
furigana_filter(value, args)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.ignite().await
|
.ignite()
|
||||||
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.launch().await
|
.launch()
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl Challenge {
|
||||||
.expect("Couldn't find challenge file");
|
.expect("Couldn't find challenge file");
|
||||||
let root = parse_document(
|
let root = parse_document(
|
||||||
&arena,
|
&arena,
|
||||||
&(challenge_text
|
&(challenge_text
|
||||||
// comrak can't find frontmatter if there's only frontmatter and no newline at end
|
// comrak can't find frontmatter if there's only frontmatter and no newline at end
|
||||||
// TODO: Open issue in comrak
|
// TODO: Open issue in comrak
|
||||||
+ "\n"),
|
+ "\n"),
|
||||||
|
@ -53,8 +53,9 @@ impl Challenge {
|
||||||
let mut html = vec![];
|
let mut html = vec![];
|
||||||
format_html(root, &ComrakOptions::default(), &mut html)
|
format_html(root, &ComrakOptions::default(), &mut html)
|
||||||
.expect("Failed to format HTML");
|
.expect("Failed to format HTML");
|
||||||
challenge.text = Some(furigana_to_html(&gh_emoji::Replacer::new()
|
challenge.text = Some(furigana_to_html(
|
||||||
.replace_all(&String::from_utf8(html).unwrap())));
|
&gh_emoji::Replacer::new().replace_all(&String::from_utf8(html).unwrap()),
|
||||||
|
));
|
||||||
challenge
|
challenge
|
||||||
} else {
|
} else {
|
||||||
panic!("No frontmatter!")
|
panic!("No frontmatter!")
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::{fs::File, io::Read, collections::HashMap, path::Path};
|
use std::{collections::HashMap, fs::File, io::Read, path::Path};
|
||||||
|
|
||||||
use poise::serenity_prelude::{SerenityError, Http};
|
|
||||||
use r2d2::{Pool, PooledConnection};
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
use r2d2_sqlite::rusqlite::{self, params};
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
|
use poise::serenity_prelude::{Http, SerenityError};
|
||||||
|
use r2d2::{Pool, PooledConnection};
|
||||||
|
use r2d2_sqlite::rusqlite::{self, params};
|
||||||
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
|
||||||
use crate::{utils::get_challenge_number, models::User};
|
use crate::{models::User, utils::get_challenge_number};
|
||||||
|
|
||||||
use super::{LegacySubmission, Submission};
|
use super::{LegacySubmission, Submission};
|
||||||
|
|
||||||
|
@ -38,9 +38,7 @@ impl Database {
|
||||||
Path::new(DATABASE_FILENAME).exists()
|
Path::new(DATABASE_FILENAME).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(testing: bool) -> Result<Self> {
|
||||||
testing: bool,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let connection_manager = if testing {
|
let connection_manager = if testing {
|
||||||
SqliteConnectionManager::memory()
|
SqliteConnectionManager::memory()
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,7 +75,8 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_submitted(&self, user_id: u64) -> Result<bool> {
|
pub fn has_submitted(&self, user_id: u64) -> Result<bool> {
|
||||||
Ok(self.conn()?
|
Ok(self
|
||||||
|
.conn()?
|
||||||
.prepare("SELECT 1 FROM User WHERE id = ?1 LIMIT 1")?
|
.prepare("SELECT 1 FROM User WHERE id = ?1 LIMIT 1")?
|
||||||
.query_row(params![user_id], |_row| Ok(()))
|
.query_row(params![user_id], |_row| Ok(()))
|
||||||
.is_ok())
|
.is_ok())
|
||||||
|
@ -103,38 +102,47 @@ impl Database {
|
||||||
// their username/discriminator since their previous submission
|
// their username/discriminator since their previous submission
|
||||||
match archived_users.get(&id) {
|
match archived_users.get(&id) {
|
||||||
Some(User { deleted, .. }) => {
|
Some(User { deleted, .. }) => {
|
||||||
archived_users.insert(id, User {
|
archived_users.insert(
|
||||||
id,
|
id,
|
||||||
avatar: None,
|
User {
|
||||||
deleted: *deleted,
|
|
||||||
..User::from_username(&legacy.username)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
None => match User::fetch(http, id).await {
|
|
||||||
Ok(User { deleted: true, .. }) => {
|
|
||||||
archived_users.insert(id, User {
|
|
||||||
id,
|
id,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
deleted: true,
|
deleted: *deleted,
|
||||||
..User::from_username(&legacy.username)
|
..User::from_username(&legacy.username)
|
||||||
});
|
},
|
||||||
},
|
);
|
||||||
|
}
|
||||||
|
None => match User::fetch(http, id).await {
|
||||||
|
Ok(User { deleted: true, .. }) => {
|
||||||
|
archived_users.insert(
|
||||||
|
id,
|
||||||
|
User {
|
||||||
|
id,
|
||||||
|
avatar: None,
|
||||||
|
deleted: true,
|
||||||
|
..User::from_username(&legacy.username)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO User(id, name, discriminator, avatar, deleted) VALUES (?1, ?2, ?3, ?4, ?5)",
|
"INSERT INTO User(id, name, discriminator, avatar, deleted) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
params![user.id, user.name, user.discriminator, user.avatar, user.deleted]
|
params![user.id, user.name, user.discriminator, user.avatar, user.deleted]
|
||||||
).map_err(DatabaseError::Rusqlite)?;
|
).map_err(DatabaseError::Rusqlite)?;
|
||||||
},
|
}
|
||||||
Err(error) if error.to_string().eq("Unknown User") => {
|
Err(error) if error.to_string().eq("Unknown User") => {
|
||||||
// This will also be called in the case of an invalid user ID
|
// This will also be called in the case of an invalid user ID
|
||||||
println!("Failed to fetch user {id}, adding to archive");
|
println!("Failed to fetch user {id}, adding to archive");
|
||||||
archived_users.insert(id, User {
|
archived_users.insert(
|
||||||
id,
|
id,
|
||||||
avatar: None,
|
User {
|
||||||
deleted: false,
|
id,
|
||||||
..User::from_username(&legacy.username)
|
avatar: None,
|
||||||
});
|
deleted: false,
|
||||||
},
|
..User::from_username(&legacy.username)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Err(error) => return Err(LoadLegacyError::Serenity(error)),
|
Err(error) => return Err(LoadLegacyError::Serenity(error)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -160,8 +168,11 @@ impl Database {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_challenge_user_data(&self, challenge: u32) -> Result<(Vec<Submission>, HashMap<String, User>)> {
|
pub fn get_challenge_user_data(
|
||||||
|
&self,
|
||||||
|
challenge: u32,
|
||||||
|
) -> Result<(Vec<Submission>, HashMap<String, User>)> {
|
||||||
let submissions = self.get_submissions(challenge)?;
|
let submissions = self.get_submissions(challenge)?;
|
||||||
let users = self.get_users({
|
let users = self.get_users({
|
||||||
let mut user_ids = HashSet::new();
|
let mut user_ids = HashSet::new();
|
||||||
|
@ -174,7 +185,8 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_submissions(&self, challenge: u32) -> Result<Vec<Submission>> {
|
pub fn get_submissions(&self, challenge: u32) -> Result<Vec<Submission>> {
|
||||||
Ok(self.conn()?
|
Ok(self
|
||||||
|
.conn()?
|
||||||
.prepare("SELECT author_id, timestamp, image FROM Submission WHERE challenge = ?1")?
|
.prepare("SELECT author_id, timestamp, image FROM Submission WHERE challenge = ?1")?
|
||||||
.query_map(params![challenge], |row| {
|
.query_map(params![challenge], |row| {
|
||||||
Ok(Submission {
|
Ok(Submission {
|
||||||
|
@ -188,7 +200,8 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user_by_name(&self, name: &str) -> Result<Option<User>> {
|
pub fn get_user_by_name(&self, name: &str) -> Result<Option<User>> {
|
||||||
Ok(self.conn()?
|
Ok(self
|
||||||
|
.conn()?
|
||||||
.prepare("SELECT id, discriminator, avatar, deleted FROM User where name = ?1")?
|
.prepare("SELECT id, discriminator, avatar, deleted FROM User where name = ?1")?
|
||||||
.query_row(params![name], |row| {
|
.query_row(params![name], |row| {
|
||||||
Ok(Some(User {
|
Ok(Some(User {
|
||||||
|
@ -202,8 +215,10 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_submissions_by_user_name(&self, name: &str) -> Result<Vec<Submission>> {
|
pub fn get_submissions_by_user_name(&self, name: &str) -> Result<Vec<Submission>> {
|
||||||
Ok(self.conn()?
|
Ok(self
|
||||||
.prepare("
|
.conn()?
|
||||||
|
.prepare(
|
||||||
|
"
|
||||||
SELECT author_id, timestamp, image, challenge
|
SELECT author_id, timestamp, image, challenge
|
||||||
FROM Submission s
|
FROM Submission s
|
||||||
JOIN User u ON s.author_id = u.id
|
JOIN User u ON s.author_id = u.id
|
||||||
|
@ -227,16 +242,23 @@ impl Database {
|
||||||
.iter()
|
.iter()
|
||||||
// u64 must be converted to String for templates
|
// u64 must be converted to String for templates
|
||||||
.map(|id| -> Result<(String, User)> {
|
.map(|id| -> Result<(String, User)> {
|
||||||
match conn.prepare("SELECT name, discriminator, avatar, deleted FROM User WHERE id = ?1") {
|
match conn
|
||||||
Ok(mut statement) => statement.query_row(params![id], |row| {
|
.prepare("SELECT name, discriminator, avatar, deleted FROM User WHERE id = ?1")
|
||||||
Ok((id.to_string(), User {
|
{
|
||||||
id: *id,
|
Ok(mut statement) => statement
|
||||||
name: row.get(0)?,
|
.query_row(params![id], |row| {
|
||||||
discriminator: row.get(1)?,
|
Ok((
|
||||||
avatar: row.get(2)?,
|
id.to_string(),
|
||||||
deleted: row.get(3)?,
|
User {
|
||||||
}))
|
id: *id,
|
||||||
}).map_err(DatabaseError::Rusqlite),
|
name: row.get(0)?,
|
||||||
|
discriminator: row.get(1)?,
|
||||||
|
avatar: row.get(2)?,
|
||||||
|
deleted: row.get(3)?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.map_err(DatabaseError::Rusqlite),
|
||||||
Err(error) => Err(DatabaseError::Rusqlite(error)),
|
Err(error) => Err(DatabaseError::Rusqlite(error)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -255,4 +277,4 @@ impl Database {
|
||||||
// For new submissions only
|
// For new submissions only
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod user;
|
mod user;
|
||||||
pub use user::{Username, User, SessionUser};
|
pub use user::{SessionUser, User, Username};
|
||||||
|
|
||||||
mod challenge;
|
mod challenge;
|
||||||
pub use challenge::Challenge;
|
pub use challenge::Challenge;
|
||||||
|
@ -11,4 +11,4 @@ mod submission;
|
||||||
pub use submission::*;
|
pub use submission::*;
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
pub use database::{Database, DatabaseError};
|
pub use database::{Database, DatabaseError};
|
||||||
|
|
|
@ -51,11 +51,13 @@ pub struct Guild {
|
||||||
impl Guild {
|
impl Guild {
|
||||||
pub async fn load(&mut self, http: &Http) -> poise::serenity_prelude::Result<()> {
|
pub async fn load(&mut self, http: &Http) -> poise::serenity_prelude::Result<()> {
|
||||||
let server = http.get_guild(self.id).await?;
|
let server = http.get_guild(self.id).await?;
|
||||||
self.icon = Some(server.icon_url().map(|icon| if icon.contains("/a_") {
|
self.icon = Some(server.icon_url().map(|icon| {
|
||||||
// serenity only gives non-animated URL
|
if icon.contains("/a_") {
|
||||||
icon.replace("webp", "gif")
|
// serenity only gives non-animated URL
|
||||||
} else {
|
icon.replace("webp", "gif")
|
||||||
icon
|
} else {
|
||||||
|
icon
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
self.name = Some(server.name);
|
self.name = Some(server.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{Utc, TimeZone};
|
use chrono::{TimeZone, Utc};
|
||||||
use serde::Deserialize;
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::Submission;
|
use super::Submission;
|
||||||
|
|
||||||
|
@ -28,7 +28,8 @@ pub enum LegacySubmissionParseError {
|
||||||
impl LegacySubmission {
|
impl LegacySubmission {
|
||||||
pub fn parse(&self, challenge: u32) -> Result<Vec<Submission>, LegacySubmissionParseError> {
|
pub fn parse(&self, challenge: u32) -> Result<Vec<Submission>, LegacySubmissionParseError> {
|
||||||
let author_id = self.id;
|
let author_id = self.id;
|
||||||
Ok(self.images
|
Ok(self
|
||||||
|
.images
|
||||||
.iter()
|
.iter()
|
||||||
.map(|image| {
|
.map(|image| {
|
||||||
Submission {
|
Submission {
|
||||||
|
@ -37,20 +38,25 @@ impl LegacySubmission {
|
||||||
// The last number of the filename is either a discriminator or a datestamp
|
// The last number of the filename is either a discriminator or a datestamp
|
||||||
// We can split apart the filename and check if it's >9999. In that case,
|
// We can split apart the filename and check if it's >9999. In that case,
|
||||||
// it's a datestamp.
|
// it's a datestamp.
|
||||||
(|| image
|
(|| {
|
||||||
// Get filename without extension
|
image
|
||||||
.split('.').next()?
|
// Get filename without extension
|
||||||
// Get last number
|
.split('.')
|
||||||
.split('-')
|
.next()?
|
||||||
.last()?
|
// Get last number
|
||||||
.parse()
|
.split('-')
|
||||||
.ok()
|
.last()?
|
||||||
// Check if discriminator or timestamp, then convert
|
.parse()
|
||||||
.map(|number| if number > 9999 {
|
.ok()
|
||||||
Utc.timestamp_millis_opt(number).single()
|
// Check if discriminator or timestamp, then convert
|
||||||
} else {
|
.map(|number| {
|
||||||
None
|
if number > 9999 {
|
||||||
})?)()
|
Utc.timestamp_millis_opt(number).single()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
})()
|
||||||
},
|
},
|
||||||
image: image.clone(),
|
image: image.clone(),
|
||||||
challenge,
|
challenge,
|
||||||
|
@ -58,4 +64,4 @@ impl LegacySubmission {
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@ mod submission;
|
||||||
pub use submission::Submission;
|
pub use submission::Submission;
|
||||||
|
|
||||||
mod legacy_submission;
|
mod legacy_submission;
|
||||||
pub use legacy_submission::LegacySubmission;
|
pub use legacy_submission::LegacySubmission;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use chrono::{Utc, DateTime};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
// Challenge submission
|
// Challenge submission
|
||||||
|
@ -9,7 +9,7 @@ use serde::Serialize;
|
||||||
pub struct Submission {
|
pub struct Submission {
|
||||||
pub author_id: u64,
|
pub author_id: u64,
|
||||||
// Some fields might be empty for legacy submissions
|
// Some fields might be empty for legacy submissions
|
||||||
// Starting during challenge #87, submissions have a datestamp appended to their filename.
|
// Starting during challenge #87, submissions have a datestamp appended to their filename.
|
||||||
// TODO: Determine whether this datestamp is local time or UTC
|
// TODO: Determine whether this datestamp is local time or UTC
|
||||||
pub timestamp: Option<DateTime<Utc>>,
|
pub timestamp: Option<DateTime<Utc>>,
|
||||||
// Image path relative to submission folder.
|
// Image path relative to submission folder.
|
||||||
|
@ -18,4 +18,4 @@ pub struct Submission {
|
||||||
// Not necessary for challenge pages,
|
// Not necessary for challenge pages,
|
||||||
// but needed for user profile pages
|
// but needed for user profile pages
|
||||||
pub challenge: u32,
|
pub challenge: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod tests;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use poise::serenity_prelude::{self, UserId, Http};
|
use poise::serenity_prelude::{self, Http, UserId};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use rocket::http::{Cookie, CookieJar};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
|
@ -46,7 +46,9 @@ impl Username for User {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_name_deleted(name: &str) -> bool {
|
fn is_name_deleted(name: &str) -> bool {
|
||||||
Regex::new(r"Deleted User [a-f0-9]{8}").unwrap().is_match(name)
|
Regex::new(r"Deleted User [a-f0-9]{8}")
|
||||||
|
.unwrap()
|
||||||
|
.is_match(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
@ -54,10 +56,7 @@ impl User {
|
||||||
let (name, discriminator) = {
|
let (name, discriminator) = {
|
||||||
let mut iter = username.split('#');
|
let mut iter = username.split('#');
|
||||||
let name = iter.next().unwrap().to_owned();
|
let name = iter.next().unwrap().to_owned();
|
||||||
let discriminator = iter
|
let discriminator = iter.next().map(|str| str.parse().unwrap()).unwrap_or(0);
|
||||||
.next()
|
|
||||||
.map(|str| str.parse().unwrap())
|
|
||||||
.unwrap_or(0);
|
|
||||||
(name, discriminator)
|
(name, discriminator)
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
|
@ -86,12 +85,19 @@ impl User {
|
||||||
"https://cdn.discordapp.com/avatars/{}/{}.{}?size=1024",
|
"https://cdn.discordapp.com/avatars/{}/{}.{}?size=1024",
|
||||||
self.id,
|
self.id,
|
||||||
avatar,
|
avatar,
|
||||||
if avatar.starts_with("a_") { "gif" } else { "webp" }
|
if avatar.starts_with("a_") {
|
||||||
|
"gif"
|
||||||
|
} else {
|
||||||
|
"webp"
|
||||||
|
}
|
||||||
),
|
),
|
||||||
// Archived user or user with no avatar, calculate default avatar
|
// Archived user or user with no avatar, calculate default avatar
|
||||||
// https://www.reddit.com/r/discordapp/comments/au6v4e/comment/eh61dm6/
|
// https://www.reddit.com/r/discordapp/comments/au6v4e/comment/eh61dm6/
|
||||||
// https://docs.rs/serenity/0.11.5/serenity/model/user/struct.User.html#method.default_avatar_url
|
// https://docs.rs/serenity/0.11.5/serenity/model/user/struct.User.html#method.default_avatar_url
|
||||||
None => format!("https://cdn.discordapp.com/embed/avatars/{}.png", self.discriminator % 5),
|
None => format!(
|
||||||
|
"https://cdn.discordapp.com/embed/avatars/{}.png",
|
||||||
|
self.discriminator % 5
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,4 +27,4 @@ fn is_name_deleted() {
|
||||||
assert!(!is_name_deleted("Deleted User Ce34a7da")); // capital letter in hex
|
assert!(!is_name_deleted("Deleted User Ce34a7da")); // capital letter in hex
|
||||||
assert!(!is_name_deleted("Deleted User ce34a7d")); // hex too short
|
assert!(!is_name_deleted("Deleted User ce34a7d")); // hex too short
|
||||||
assert!(!is_name_deleted("Deleted User")); // no hex
|
assert!(!is_name_deleted("Deleted User")); // no hex
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use rocket_dyn_templates::{context, Template};
|
||||||
use crate::{
|
use crate::{
|
||||||
cookies::LANG_COOKIE,
|
cookies::LANG_COOKIE,
|
||||||
i18n::DEFAULT as DEFAULT_LANG,
|
i18n::DEFAULT as DEFAULT_LANG,
|
||||||
models::{Challenge, Settings, SessionUser, Database},
|
models::{Challenge, Database, SessionUser, Settings},
|
||||||
utils::AcceptLanguage,
|
utils::AcceptLanguage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use poise::serenity_prelude::Http;
|
use poise::serenity_prelude::Http;
|
||||||
use rocket::{http::CookieJar, State};
|
|
||||||
use rocket::response::stream::{Event, EventStream};
|
use rocket::response::stream::{Event, EventStream};
|
||||||
|
use rocket::{http::CookieJar, State};
|
||||||
|
|
||||||
use crate::{cookies::user::USER_ID_COOKIE, models::Settings};
|
use crate::{cookies::user::USER_ID_COOKIE, models::Settings};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use rocket::{
|
||||||
use rocket::{http::{CookieJar, Status}, State};
|
http::{CookieJar, Status},
|
||||||
|
State,
|
||||||
|
};
|
||||||
use rocket_dyn_templates::{context, Template};
|
use rocket_dyn_templates::{context, Template};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cookies::LANG_COOKIE,
|
cookies::LANG_COOKIE,
|
||||||
i18n::DEFAULT as DEFAULT_LANG,
|
i18n::DEFAULT as DEFAULT_LANG,
|
||||||
models::{Settings, SessionUser, Database, DatabaseError},
|
models::{Database, DatabaseError, SessionUser, Settings},
|
||||||
utils::AcceptLanguage,
|
utils::AcceptLanguage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,11 +23,13 @@ pub async fn get_user(
|
||||||
) -> Result<Template, Status> {
|
) -> Result<Template, Status> {
|
||||||
let profile_user = match database.get_user_by_name(&user) {
|
let profile_user = match database.get_user_by_name(&user) {
|
||||||
Ok(profile_user) => profile_user,
|
Ok(profile_user) => profile_user,
|
||||||
Err(DatabaseError::Rusqlite(rusqlite::Error::QueryReturnedNoRows)) => return Err(Status::NotFound),
|
Err(DatabaseError::Rusqlite(rusqlite::Error::QueryReturnedNoRows)) => {
|
||||||
|
return Err(Status::NotFound)
|
||||||
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("{:?}", error);
|
eprintln!("{:?}", error);
|
||||||
return Err(Status::InternalServerError);
|
return Err(Status::InternalServerError);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
Ok(Template::render(
|
Ok(Template::render(
|
||||||
"user",
|
"user",
|
||||||
|
@ -41,4 +45,4 @@ pub async fn get_user(
|
||||||
user: SessionUser::get(cookies).await.unwrap(),
|
user: SessionUser::get(cookies).await.unwrap(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ use rocket::{
|
||||||
response::Redirect,
|
response::Redirect,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{cookies::{token::TOKEN_EXPIRE_COOKIE, WELCOMED_COOKIE}, models::SessionUser};
|
use crate::{
|
||||||
|
cookies::{token::TOKEN_EXPIRE_COOKIE, WELCOMED_COOKIE},
|
||||||
|
models::SessionUser,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct Login<'r> {
|
pub struct Login<'r> {
|
||||||
|
|
|
@ -19,7 +19,8 @@ pub async fn testing(cookies: &CookieJar<'_>, http: &State<Http>) -> String {
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get logged in user data")
|
.expect("Failed to get logged in user data")
|
||||||
.expect("No logged in user")
|
.expect("No logged in user")
|
||||||
.0.id
|
.0
|
||||||
|
.id
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to fetch user in server")
|
.expect("Failed to fetch user in server")
|
||||||
|
|
|
@ -18,4 +18,4 @@ pub fn get_challenge_number() -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
max
|
max
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,19 @@ pub fn furigana_to_html(text: &str) -> String {
|
||||||
// Curly brace literals \{ need to have their backslash escaped as \\{
|
// Curly brace literals \{ need to have their backslash escaped as \\{
|
||||||
// TODO: Modify so <span lang="ja"> only wraps continuous sections of furigana
|
// TODO: Modify so <span lang="ja"> only wraps continuous sections of furigana
|
||||||
let re = Regex::new(r"\[([^\]]*)\]\{([^\\}]*)}").unwrap();
|
let re = Regex::new(r"\[([^\]]*)\]\{([^\\}]*)}").unwrap();
|
||||||
format!("<span lang=\"ja\">{}</span>", re.replace_all(text, "<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>"))
|
format!(
|
||||||
|
"<span lang=\"ja\">{}</span>",
|
||||||
|
re.replace_all(text, "<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn furigana_filter(value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
|
pub fn furigana_filter(value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
|
||||||
if value.is_null() {
|
if value.is_null() {
|
||||||
return Ok(Value::String("".to_string()));
|
return Ok(Value::String("".to_string()));
|
||||||
}
|
}
|
||||||
Ok(Value::String(furigana_to_html(value.as_str().expect("The furigana input must be a string"))))
|
Ok(Value::String(furigana_to_html(
|
||||||
|
value.as_str().expect("The furigana input must be a string"),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -36,4 +41,4 @@ mod tests {
|
||||||
</span>"
|
</span>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,4 +8,4 @@ mod challenge;
|
||||||
pub use challenge::*;
|
pub use challenge::*;
|
||||||
|
|
||||||
mod furigana;
|
mod furigana;
|
||||||
pub use furigana::*;
|
pub use furigana::*;
|
||||||
|
|
Loading…
Add table
Reference in a new issue