add: anonymous questions
This commit is contained in:
parent
2266afde01
commit
3db7f2699c
34 changed files with 473 additions and 98 deletions
|
@ -41,10 +41,11 @@ version = "1.0.0"
|
|||
"auth:action.login" = "Login"
|
||||
"auth:action.register" = "Register"
|
||||
"auth:action.logout" = "Logout"
|
||||
"auto:action.follow" = "Follow"
|
||||
"auto:action.unfollow" = "Unfollow"
|
||||
"auto:action.block" = "Block"
|
||||
"auto:action.unblock" = "Unblock"
|
||||
"auth:action.follow" = "Follow"
|
||||
"auth:action.unfollow" = "Unfollow"
|
||||
"auth:action.block" = "Block"
|
||||
"auth:action.ip_block" = "IP block"
|
||||
"auth:action.unblock" = "Unblock"
|
||||
"auth:link.my_profile" = "My profile"
|
||||
"auth:link.settings" = "Settings"
|
||||
"auth:label.followers" = "Followers"
|
||||
|
@ -55,8 +56,8 @@ version = "1.0.0"
|
|||
"auth:label.before_you_view" = "Before you view"
|
||||
"auth:label.private_profile" = "Private profile"
|
||||
"auth:label.private_profile_message" = "This profile is private, meaning you can only view it if they follow you."
|
||||
"auto:action.request_to_follow" = "Request to follow"
|
||||
"auto:action.cancel_follow_request" = "Cancel follow request"
|
||||
"auth:action.request_to_follow" = "Request to follow"
|
||||
"auth:action.cancel_follow_request" = "Cancel follow request"
|
||||
|
||||
"communities:action.create" = "Create"
|
||||
"communities:action.select" = "Select"
|
||||
|
|
|
@ -110,7 +110,8 @@
|
|||
|
||||
<div class="flex gap-2">
|
||||
<button class="primary">{{ text "requests:label.answer" }}</button>
|
||||
<button class="red quaternary" onclick="trigger('me::remove_question', ['{{ question[0].id }}'])">{{ text "general:action.delete" }}</button>
|
||||
<button type="button" class="red quaternary" onclick="trigger('me::remove_question', ['{{ question[0].id }}'])">{{ text "general:action.delete" }}</button>
|
||||
<button type="button" class="red quaternary" onclick="trigger('me::ip_block_question', ['{{ question[0].id }}'])">{{ text "auth:action.ip_block" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
atto_tag="user.follow"
|
||||
>
|
||||
{{ icon "user-plus" }}
|
||||
<span>{{ text "auto:action.follow" }}</span>
|
||||
<span>{{ text "auth:action.follow" }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
@ -165,7 +165,7 @@
|
|||
atto_tag="user.unfollow"
|
||||
>
|
||||
{{ icon "user-minus" }}
|
||||
<span>{{ text "auto:action.unfollow" }}</span>
|
||||
<span>{{ text "auth:action.unfollow" }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
@ -173,7 +173,7 @@
|
|||
class="quaternary red"
|
||||
>
|
||||
{{ icon "shield" }}
|
||||
<span>{{ text "auto:action.block" }}</span>
|
||||
<span>{{ text "auth:action.block" }}</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
|
@ -181,7 +181,7 @@
|
|||
class="quaternary red"
|
||||
>
|
||||
{{ icon "shield-off" }}
|
||||
<span>{{ text "auto:action.unblock" }}</span>
|
||||
<span>{{ text "auth:action.unblock" }}</span>
|
||||
</button>
|
||||
{% endif %} {% if is_helper %}
|
||||
<a
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "profile/base.html" %} {% block content %} {% if
|
||||
profile.settings.enable_questions and user %}
|
||||
profile.settings.enable_questions and (user or
|
||||
profile.settings.allow_anonymous_questions) %}
|
||||
<div style="display: contents">
|
||||
{{ components::create_question_form(receiver=profile.id,
|
||||
header=profile.settings.motivational_header) }}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
atto_tag="user.follow_request"
|
||||
>
|
||||
{{ icon "user-plus" }}
|
||||
<span>{{ text "auto:action.request_to_follow" }}</span>
|
||||
<span>{{ text "auth:action.request_to_follow" }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
@ -34,7 +34,7 @@
|
|||
atto_tag="user.cancel_request"
|
||||
>
|
||||
{{ icon "user-minus" }}
|
||||
<span>{{ text "auto:action.cancel_follow_request" }}</span>
|
||||
<span>{{ text "auth:action.cancel_follow_request" }}</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
|
@ -43,7 +43,7 @@
|
|||
atto_tag="user.unfollow"
|
||||
>
|
||||
{{ icon "user-minus" }}
|
||||
<span>{{ text "auto:action.unfollow" }}</span>
|
||||
<span>{{ text "auth:action.unfollow" }}</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -748,20 +748,6 @@
|
|||
settings.warning,
|
||||
"textarea",
|
||||
],
|
||||
[[], "Questions", "title"],
|
||||
[
|
||||
[
|
||||
"enable_questions",
|
||||
"Allow users to ask you questions",
|
||||
],
|
||||
"{{ profile.settings.enable_questions }}",
|
||||
"checkbox",
|
||||
],
|
||||
[
|
||||
["motivational_header", "Motivational header"],
|
||||
settings.motivational_header,
|
||||
"input",
|
||||
],
|
||||
],
|
||||
settings,
|
||||
);
|
||||
|
@ -791,6 +777,28 @@
|
|||
"{{ profile.settings.private_last_seen }}",
|
||||
"checkbox",
|
||||
],
|
||||
[[], "Questions", "title"],
|
||||
[
|
||||
[
|
||||
"enable_questions",
|
||||
"Allow users to ask you questions",
|
||||
],
|
||||
"{{ profile.settings.enable_questions }}",
|
||||
"checkbox",
|
||||
],
|
||||
[
|
||||
[
|
||||
"allow_anonymous_questions",
|
||||
"Allow anonymous questions",
|
||||
],
|
||||
"{{ profile.settings.allow_anonymous_questions }}",
|
||||
"checkbox",
|
||||
],
|
||||
[
|
||||
["motivational_header", "Motivational header"],
|
||||
settings.motivational_header,
|
||||
"input",
|
||||
],
|
||||
],
|
||||
settings,
|
||||
);
|
||||
|
|
|
@ -214,6 +214,27 @@
|
|||
});
|
||||
});
|
||||
|
||||
self.define("ip_block_question", async (_, id) => {
|
||||
if (
|
||||
!(await trigger("atto::confirm", [
|
||||
"Are you sure you want to do this?",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/questions/${id}/block_ip`, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// token switcher
|
||||
self.define(
|
||||
"set_login_account_tokens",
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
};
|
||||
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use tetratto_core::model::auth::{FollowResult, Notification, UserBlock, UserFollow};
|
||||
use tetratto_core::model::auth::{FollowResult, IpBlock, Notification, UserBlock, UserFollow};
|
||||
|
||||
/// Toggle following on the given user.
|
||||
pub async fn follow_request(
|
||||
|
@ -197,3 +197,38 @@ pub async fn block_request(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle IP blocking on the given IP.
|
||||
pub async fn ip_block_request(
|
||||
jar: CookieJar,
|
||||
Path(ip): Path<String>,
|
||||
Extension(data): Extension<State>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
if let Ok(ipblock) = data.get_ipblock_by_initiator_receiver(user.id, &ip).await {
|
||||
// delete
|
||||
match data.delete_ipblock(ipblock.id, user).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "IP unblocked".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
} else {
|
||||
// create
|
||||
match data.create_ipblock(IpBlock::new(user.id, ip)).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "IP blocked".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,49 @@
|
|||
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::{HeaderMap, HeaderValue},
|
||||
response::IntoResponse,
|
||||
Extension, Json,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use tetratto_core::model::{communities::Question, ApiReturn, Error};
|
||||
use tetratto_core::model::{auth::IpBlock, communities::Question, ApiReturn, Error};
|
||||
use crate::{get_user_from_token, routes::api::v1::CreateQuestion, State};
|
||||
|
||||
pub async fn create_request(
|
||||
jar: CookieJar,
|
||||
headers: HeaderMap,
|
||||
Extension(data): Extension<State>,
|
||||
Json(req): Json<CreateQuestion>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
let user = get_user_from_token!(jar, data);
|
||||
|
||||
if req.is_global && user.is_none() {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
// get real ip
|
||||
let real_ip = headers
|
||||
.get(data.0.security.real_ip_header.to_owned())
|
||||
.unwrap_or(&HeaderValue::from_static(""))
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
// check for ip ban
|
||||
if data.get_ipban_by_ip(&real_ip).await.is_ok() {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
// ...
|
||||
let mut props = Question::new(
|
||||
user.id,
|
||||
if let Some(ref ua) = user { ua.id } else { 0 },
|
||||
match req.receiver.parse::<usize>() {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
},
|
||||
req.content,
|
||||
req.is_global,
|
||||
real_ip,
|
||||
);
|
||||
|
||||
if !req.community.is_empty() {
|
||||
|
@ -63,3 +85,43 @@ pub async fn delete_request(
|
|||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ip_block_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
// get question
|
||||
let question = match data.get_question_by_id(id).await {
|
||||
Ok(q) => q,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
// check for an existing ip block
|
||||
if data
|
||||
.get_ipblock_by_initiator_receiver(user.id, &question.ip)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
// create ip block
|
||||
match data
|
||||
.create_ipblock(IpBlock::new(user.id, question.ip))
|
||||
.await
|
||||
{
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "IP blocked".to_string(),
|
||||
payload: (),
|
||||
}),
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,10 @@ pub fn routes() -> Router {
|
|||
"/questions/{id}",
|
||||
delete(communities::questions::delete_request),
|
||||
)
|
||||
.route(
|
||||
"/questions/{id}/block_ip",
|
||||
post(communities::questions::ip_block_request),
|
||||
)
|
||||
// auth
|
||||
// global
|
||||
.route("/auth/register", post(auth::register_request))
|
||||
|
@ -177,6 +181,7 @@ pub fn routes() -> Router {
|
|||
"/auth/user/find_by_ip/{ip}",
|
||||
get(auth::profile::redirect_from_ip),
|
||||
)
|
||||
.route("/auth/ip/{ip}/block", post(auth::social::ip_block_request))
|
||||
// warnings
|
||||
.route("/warnings/{id}", post(auth::user_warnings::create_request))
|
||||
.route(
|
||||
|
|
|
@ -20,6 +20,10 @@ pub fn routes(config: &Config) -> Router {
|
|||
get_service(tower_http::services::ServeDir::new(&config.dirs.assets)),
|
||||
)
|
||||
.route("/public/favicon.svg", get(assets::favicon_request))
|
||||
.route_service(
|
||||
"/robots.txt",
|
||||
tower_http::services::ServeFile::new(format!("{}/robots.txt", config.dirs.assets)),
|
||||
)
|
||||
// api
|
||||
.nest("/api/v1", api::v1::routes())
|
||||
// pages
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue