add: don't allow poll creator to vote
add: unfollow user when you block them add: force other user to unfollow you by blocking them add: leave receiver communities when you block them
This commit is contained in:
parent
5a330b7a18
commit
460e87e90e
11 changed files with 165 additions and 17 deletions
|
@ -56,6 +56,14 @@ fn check_banned(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_script_tags(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
|
||||||
|
Ok(value
|
||||||
|
.as_str()
|
||||||
|
.unwrap()
|
||||||
|
.replace("</script>", "</script>")
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
|
@ -86,6 +94,7 @@ async fn main() {
|
||||||
tera.register_filter("has_supporter", check_supporter);
|
tera.register_filter("has_supporter", check_supporter);
|
||||||
tera.register_filter("has_staff_badge", check_staff_badge);
|
tera.register_filter("has_staff_badge", check_staff_badge);
|
||||||
tera.register_filter("has_banned", check_banned);
|
tera.register_filter("has_banned", check_banned);
|
||||||
|
tera.register_filter("remove_script_tags", remove_script_tags);
|
||||||
|
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
|
||||||
|
|
|
@ -1343,6 +1343,7 @@
|
||||||
window.POLL_OPTION_B = \"\";
|
window.POLL_OPTION_B = \"\";
|
||||||
window.POLL_OPTION_C = \"\";
|
window.POLL_OPTION_C = \"\";
|
||||||
window.POLL_OPTION_D = \"\";
|
window.POLL_OPTION_D = \"\";
|
||||||
|
window.POLL_EXPIRES = null;
|
||||||
|
|
||||||
window.get_poll_data = () => {
|
window.get_poll_data = () => {
|
||||||
if (!POLL_OPTION_A && !POLL_OPTION_B) {
|
if (!POLL_OPTION_A && !POLL_OPTION_B) {
|
||||||
|
@ -1353,11 +1354,16 @@
|
||||||
return [false, \"At least 2 options are required for a poll\"];
|
return [false, \"At least 2 options are required for a poll\"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (POLL_EXPIRES < 0) {
|
||||||
|
return [false, \"Polls cannot time travel\"];
|
||||||
|
}
|
||||||
|
|
||||||
return [true, {
|
return [true, {
|
||||||
option_a: POLL_OPTION_A,
|
option_a: POLL_OPTION_A,
|
||||||
option_b: POLL_OPTION_B,
|
option_b: POLL_OPTION_B,
|
||||||
option_c: POLL_OPTION_C,
|
option_c: POLL_OPTION_C,
|
||||||
option_d: POLL_OPTION_D
|
option_d: POLL_OPTION_D,
|
||||||
|
expires: POLL_EXPIRES,
|
||||||
}];
|
}];
|
||||||
}"))
|
}"))
|
||||||
|
|
||||||
|
@ -1395,7 +1401,12 @@
|
||||||
(div
|
(div
|
||||||
("class" "card flex flex-col gap-2")
|
("class" "card flex flex-col gap-2")
|
||||||
(b (text "Option D"))
|
(b (text "Option D"))
|
||||||
(input ("type" "text") ("placeholder" "option D") ("onchange" "window.POLL_OPTION_D = event.target.value"))))
|
(input ("type" "text") ("placeholder" "option D") ("onchange" "window.POLL_OPTION_D = event.target.value")))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "card flex flex-col gap-2")
|
||||||
|
(b (text "Expires"))
|
||||||
|
(input ("type" "date") ("onchange" "window.POLL_EXPIRES = event.target.valueAsDate.getTime() - new Date().getTime()"))))
|
||||||
(hr)
|
(hr)
|
||||||
(div
|
(div
|
||||||
("class" "flex justify-between")
|
("class" "flex justify-between")
|
||||||
|
@ -1414,9 +1425,13 @@
|
||||||
("class" "card tertiary w-full flex flex-col gap-2")
|
("class" "card tertiary w-full flex flex-col gap-2")
|
||||||
(text "{% set total = poll[0].votes_a + poll[0].votes_b + poll[0].votes_c + poll[0].votes_d %}")
|
(text "{% set total = poll[0].votes_a + poll[0].votes_b + poll[0].votes_c + poll[0].votes_d %}")
|
||||||
|
|
||||||
(text "{% if poll[1] -%}")
|
(text "{% if poll[1] or poll[2] or user and user.id == poll[0].owner -%}")
|
||||||
; already voted, show results
|
; already voted, show results
|
||||||
|
(text "{% if poll[1] %}")
|
||||||
(span ("class" "fade") (text "You've already voted!"))
|
(span ("class" "fade") (text "You've already voted!"))
|
||||||
|
(text "{% elif poll[2] %}")
|
||||||
|
(span ("class" "fade") (text "Poll ended!"))
|
||||||
|
(text "{% endif %}")
|
||||||
|
|
||||||
; option a
|
; option a
|
||||||
(div
|
(div
|
||||||
|
@ -1484,5 +1499,12 @@
|
||||||
; show expiration date + totals
|
; show expiration date + totals
|
||||||
(div
|
(div
|
||||||
("class" "flex w-full flex-wrap gap-2")
|
("class" "flex w-full flex-wrap gap-2")
|
||||||
(span ("class" "notification chip") (text "{{ total }} votes"))))
|
(span ("class" "notification chip") (text "{{ total }} votes"))
|
||||||
|
(span
|
||||||
|
("class" "notification chip")
|
||||||
|
(text "Expires in ")
|
||||||
|
(span
|
||||||
|
("class" "poll_date")
|
||||||
|
("data-created" "{{ poll[0].created }}")
|
||||||
|
("data-expires" "{{ poll[0].expires }}")))))
|
||||||
(text "{%- endmacro %}")
|
(text "{%- endmacro %}")
|
||||||
|
|
|
@ -855,7 +855,7 @@
|
||||||
(script
|
(script
|
||||||
("type" "application/json")
|
("type" "application/json")
|
||||||
("id" "settings_json")
|
("id" "settings_json")
|
||||||
(text "{{ profile.settings|json_encode()|safe }}"))
|
(text "{{ profile.settings|json_encode()|remove_script_tags|safe }}"))
|
||||||
(script
|
(script
|
||||||
(text "setTimeout(() => {
|
(text "setTimeout(() => {
|
||||||
const ui = ns(\"ui\");
|
const ui = ns(\"ui\");
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
atto.disconnect_observers();
|
atto.disconnect_observers();
|
||||||
atto.remove_false_options();
|
atto.remove_false_options();
|
||||||
atto.clean_date_codes();
|
atto.clean_date_codes();
|
||||||
|
atto.clean_poll_date_codes();
|
||||||
atto.link_filter();
|
atto.link_filter();
|
||||||
|
|
||||||
atto[\"hooks::scroll\"](document.body, document.documentElement);
|
atto[\"hooks::scroll\"](document.body, document.documentElement);
|
||||||
|
|
|
@ -91,10 +91,11 @@ media_theme_pref();
|
||||||
|
|
||||||
self.define("rel_date", (_, date) => {
|
self.define("rel_date", (_, date) => {
|
||||||
// stolen and slightly modified because js dates suck
|
// stolen and slightly modified because js dates suck
|
||||||
const diff = (new Date().getTime() - date.getTime()) / 1000;
|
const diff = Math.abs((new Date().getTime() - date.getTime()) / 1000);
|
||||||
const day_diff = Math.floor(diff / 86400);
|
const day_diff = Math.floor(diff / 86400);
|
||||||
|
|
||||||
if (Number.isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
|
if (Number.isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
|
||||||
|
console.log(diff);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +163,48 @@ media_theme_pref();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.define("clean_poll_date_codes", ({ $ }) => {
|
||||||
|
for (const element of Array.from(
|
||||||
|
document.querySelectorAll(".poll_date"),
|
||||||
|
)) {
|
||||||
|
const created = Number.parseInt(
|
||||||
|
element.getAttribute("data-created"),
|
||||||
|
);
|
||||||
|
const expires = Number.parseInt(
|
||||||
|
element.getAttribute("data-expires"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const then = new Date(created + expires);
|
||||||
|
|
||||||
|
if (Number.isNaN(element.innerText)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.setAttribute("title", then.toLocaleString());
|
||||||
|
|
||||||
|
console.log($.rel_date(then));
|
||||||
|
const pretty =
|
||||||
|
$.rel_date(then)
|
||||||
|
.replaceAll(" minutes ago", "m")
|
||||||
|
.replaceAll(" minute ago", "m")
|
||||||
|
.replaceAll(" hours ago", "h")
|
||||||
|
.replaceAll(" hour ago", "h")
|
||||||
|
.replaceAll(" days ago", "d")
|
||||||
|
.replaceAll(" day ago", "d")
|
||||||
|
.replaceAll(" weeks ago", "w")
|
||||||
|
.replaceAll(" week ago", "w")
|
||||||
|
.replaceAll(" months ago", "m")
|
||||||
|
.replaceAll(" month ago", "m")
|
||||||
|
.replaceAll(" years ago", "y")
|
||||||
|
.replaceAll(" year ago", "y") || "";
|
||||||
|
|
||||||
|
element.innerText =
|
||||||
|
pretty === undefined ? then.toLocaleDateString() : pretty;
|
||||||
|
|
||||||
|
element.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.define("copy_text", ({ $ }, text) => {
|
self.define("copy_text", ({ $ }, text) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
$.toast("success", "Copied!");
|
$.toast("success", "Copied!");
|
||||||
|
|
|
@ -70,7 +70,16 @@ pub async fn create_request(
|
||||||
let poll_id = if let Some(p) = req.poll {
|
let poll_id = if let Some(p) = req.poll {
|
||||||
match data
|
match data
|
||||||
.create_poll(Poll::new(
|
.create_poll(Poll::new(
|
||||||
user.id, 86400000, p.option_a, p.option_b, p.option_c, p.option_d,
|
user.id,
|
||||||
|
if let Some(expires) = p.expires {
|
||||||
|
expires
|
||||||
|
} else {
|
||||||
|
86400000
|
||||||
|
},
|
||||||
|
p.option_a,
|
||||||
|
p.option_b,
|
||||||
|
p.option_c,
|
||||||
|
p.option_d,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
|
@ -429,6 +429,8 @@ pub struct CreatePostPoll {
|
||||||
pub option_b: String,
|
pub option_b: String,
|
||||||
pub option_c: String,
|
pub option_c: String,
|
||||||
pub option_d: String,
|
pub option_d: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub expires: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -292,6 +292,32 @@ impl DataManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete a membership given its `id`
|
||||||
|
pub async fn delete_membership_force(&self, id: usize) -> Result<()> {
|
||||||
|
let y = self.get_membership_by_id(id).await?;
|
||||||
|
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(
|
||||||
|
&conn,
|
||||||
|
"DELETE FROM memberships WHERE id = $1",
|
||||||
|
&[&(id as i64)]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!("atto.membership:{}", id)).await;
|
||||||
|
|
||||||
|
self.decr_community_member_count(y.community).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Update a membership's role given its `id`
|
/// Update a membership's role given its `id`
|
||||||
pub async fn update_membership_role(
|
pub async fn update_membership_role(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -237,11 +237,14 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the poll of the given post (if some).
|
/// Get the poll of the given post (if some).
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `Result<Option<(poll, voted, expired)>>`
|
||||||
pub async fn get_post_poll(
|
pub async fn get_post_poll(
|
||||||
&self,
|
&self,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
user: &Option<User>,
|
user: &Option<User>,
|
||||||
) -> Result<Option<(Poll, bool)>> {
|
) -> Result<Option<(Poll, bool, bool)>> {
|
||||||
let user = if let Some(ua) = user {
|
let user = if let Some(ua) = user {
|
||||||
ua
|
ua
|
||||||
} else {
|
} else {
|
||||||
|
@ -250,12 +253,16 @@ impl DataManager {
|
||||||
|
|
||||||
if post.poll_id != 0 {
|
if post.poll_id != 0 {
|
||||||
Ok(Some(match self.get_poll_by_id(post.poll_id).await {
|
Ok(Some(match self.get_poll_by_id(post.poll_id).await {
|
||||||
Ok(p) => (
|
Ok(p) => {
|
||||||
|
let expired = (unix_epoch_timestamp() as usize) - p.created > p.expires;
|
||||||
|
(
|
||||||
p,
|
p,
|
||||||
self.get_pollvote_by_owner_poll(user.id, post.poll_id)
|
self.get_pollvote_by_owner_poll(user.id, post.poll_id)
|
||||||
.await
|
.await
|
||||||
.is_ok(),
|
.is_ok(),
|
||||||
),
|
expired,
|
||||||
|
)
|
||||||
|
}
|
||||||
Err(_) => return Err(Error::MiscError("Invalid poll ID attached".to_string())),
|
Err(_) => return Err(Error::MiscError("Invalid poll ID attached".to_string())),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,7 +282,7 @@ impl DataManager {
|
||||||
User,
|
User,
|
||||||
Option<(User, Post)>,
|
Option<(User, Post)>,
|
||||||
Option<(Question, User)>,
|
Option<(Question, User)>,
|
||||||
Option<(Poll, bool)>,
|
Option<(Poll, bool, bool)>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
@ -374,7 +381,7 @@ impl DataManager {
|
||||||
Community,
|
Community,
|
||||||
Option<(User, Post)>,
|
Option<(User, Post)>,
|
||||||
Option<(Question, User)>,
|
Option<(Question, User)>,
|
||||||
Option<(Poll, bool)>,
|
Option<(Poll, bool, bool)>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl DataManager {
|
||||||
Community,
|
Community,
|
||||||
Option<(User, Post)>,
|
Option<(User, Post)>,
|
||||||
Option<(Question, User)>,
|
Option<(Question, User)>,
|
||||||
Option<(Poll, bool)>,
|
Option<(Poll, bool, bool)>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
let stack = self.get_stack_by_id(id).await?;
|
let stack = self.get_stack_by_id(id).await?;
|
||||||
|
|
|
@ -177,6 +177,10 @@ impl DataManager {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `data` - a mock [`UserBlock`] object to insert
|
/// * `data` - a mock [`UserBlock`] object to insert
|
||||||
pub async fn create_userblock(&self, data: UserBlock) -> Result<()> {
|
pub async fn create_userblock(&self, data: UserBlock) -> Result<()> {
|
||||||
|
let initiator = self.get_user_by_id(data.initiator).await?;
|
||||||
|
let receiver = self.get_user_by_id(data.receiver).await?;
|
||||||
|
|
||||||
|
// ...
|
||||||
let conn = match self.connect().await {
|
let conn = match self.connect().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
@ -197,6 +201,31 @@ impl DataManager {
|
||||||
return Err(Error::DatabaseError(e.to_string()));
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove initiator from receiver's communities
|
||||||
|
for community in self.get_communities_by_owner(data.receiver).await? {
|
||||||
|
if let Ok(membership) = self
|
||||||
|
.get_membership_by_owner_community_no_void(data.initiator, community.id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.delete_membership_force(membership.id).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfollow/remove follower
|
||||||
|
if let Ok(f) = self
|
||||||
|
.get_userfollow_by_initiator_receiver(data.initiator, data.receiver)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.delete_userfollow(f.id, &initiator, false).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(f) = self
|
||||||
|
.get_userfollow_by_receiver_initiator(data.initiator, data.receiver)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.delete_userfollow(f.id, &receiver, false).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// return
|
// return
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue