set user intro
parent
a1b3bbb999
commit
c9a91d3d36
|
|
@ -116,10 +116,10 @@ where
|
|||
self.wrapped_service.add_intro_to_guild(req).await
|
||||
}
|
||||
|
||||
async fn add_intro_to_user(
|
||||
async fn set_user_intro(
|
||||
&self,
|
||||
req: models::guild::AddIntroToUserRequest,
|
||||
) -> Result<(), models::guild::AddIntroToUserError> {
|
||||
self.wrapped_service.add_intro_to_user(req).await
|
||||
self.wrapped_service.set_user_intro(req).await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,18 @@ impl std::fmt::Display for GuildId {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ChannelName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IntroId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
|
|
@ -271,10 +283,10 @@ pub enum IntroRequestData {
|
|||
}
|
||||
|
||||
pub struct AddIntroToUserRequest {
|
||||
user: UserName,
|
||||
guild_id: GuildId,
|
||||
channel_name: ChannelName,
|
||||
intro_id: IntroId,
|
||||
pub user: UserName,
|
||||
pub guild_id: GuildId,
|
||||
pub channel_name: ChannelName,
|
||||
pub intro_id: IntroId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ pub trait IntroToolService: Send + Sync + Clone + 'static {
|
|||
api_key: &str,
|
||||
) -> impl Future<Output = Result<User, GetUserError>> + Send;
|
||||
|
||||
fn set_user_intro(
|
||||
&self,
|
||||
req: AddIntroToUserRequest,
|
||||
) -> impl Future<Output = Result<(), AddIntroToUserError>> + Send;
|
||||
|
||||
async fn create_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
|
||||
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
||||
async fn create_channel(
|
||||
|
|
@ -48,11 +53,6 @@ pub trait IntroToolService: Send + Sync + Clone + 'static {
|
|||
&self,
|
||||
req: AddIntroToGuildRequest,
|
||||
) -> impl Future<Output = Result<IntroId, AddIntroToGuildError>> + Send;
|
||||
|
||||
async fn add_intro_to_user(
|
||||
&self,
|
||||
req: AddIntroToUserRequest,
|
||||
) -> Result<(), AddIntroToUserError>;
|
||||
}
|
||||
|
||||
pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
||||
|
|
@ -97,6 +97,11 @@ pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
|||
api_key: &str,
|
||||
) -> impl Future<Output = Result<User, GetUserError>> + Send;
|
||||
|
||||
fn set_user_intro(
|
||||
&self,
|
||||
req: AddIntroToUserRequest,
|
||||
) -> impl Future<Output = Result<(), AddIntroToUserError>> + Send;
|
||||
|
||||
async fn create_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
|
||||
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
||||
async fn create_channel(
|
||||
|
|
@ -110,11 +115,6 @@ pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
|||
guild_id: GuildId,
|
||||
filename: String,
|
||||
) -> impl Future<Output = Result<IntroId, AddIntroToGuildError>> + Send;
|
||||
|
||||
async fn add_intro_to_user(
|
||||
&self,
|
||||
req: AddIntroToUserRequest,
|
||||
) -> Result<(), AddIntroToUserError>;
|
||||
}
|
||||
|
||||
pub trait RemoteAudioFetcher: Send + Sync + Clone + 'static {
|
||||
|
|
|
|||
|
|
@ -124,10 +124,10 @@ where
|
|||
.await
|
||||
}
|
||||
|
||||
async fn add_intro_to_user(
|
||||
async fn set_user_intro(
|
||||
&self,
|
||||
req: guild::AddIntroToUserRequest,
|
||||
) -> Result<(), guild::AddIntroToUserError> {
|
||||
self.repo.add_intro_to_user(req).await
|
||||
self.repo.set_user_intro(req).await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ where
|
|||
"/v2/intros/:guild/upload",
|
||||
post(handlers::upload_guild_intro),
|
||||
)
|
||||
.route(
|
||||
"/v2/intros/add/:guild_id/:channel",
|
||||
post(handlers::set_user_intro),
|
||||
)
|
||||
|
||||
// .route("/guild/:guild_id/setup", get(routes::guild_setup))
|
||||
// .route(
|
||||
|
|
@ -133,10 +137,6 @@ where
|
|||
// )
|
||||
// .route("/v2/auth", get(routes::v2_auth))
|
||||
// .route(
|
||||
// "/v2/intros/add/:guild_id/:channel",
|
||||
// post(routes::v2_add_intro_to_user),
|
||||
// )
|
||||
// .route(
|
||||
// "/v2/intros/remove/:guild_id/:channel",
|
||||
// post(routes::v2_remove_intro_from_user),
|
||||
// )
|
||||
|
|
|
|||
|
|
@ -3,15 +3,20 @@ use std::collections::HashMap;
|
|||
use axum::{
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{HeaderMap, HeaderValue},
|
||||
response::Html,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
domain::intro_tool::{
|
||||
models::guild::{AddIntroToGuildRequest, GuildId, IntroRequestData, User},
|
||||
models::guild::{
|
||||
AddIntroToGuildRequest, AddIntroToUserRequest, ChannelName, GuildId, IntroRequestData,
|
||||
User, UserName,
|
||||
},
|
||||
ports::IntroToolService,
|
||||
},
|
||||
htmx::Build,
|
||||
inbound::{
|
||||
http::ApiState,
|
||||
http::{page, ApiState},
|
||||
response::{ApiError, ErrorAsRedirect},
|
||||
},
|
||||
};
|
||||
|
|
@ -102,6 +107,31 @@ impl FromApi<Multipart, GuildId> for AddIntroToGuildRequest {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromApi<Multipart, (GuildId, UserName, ChannelName)> for AddIntroToUserRequest {
|
||||
async fn from_api(
|
||||
mut value: Multipart,
|
||||
(guild_id, user, channel_name): (GuildId, UserName, ChannelName),
|
||||
) -> Result<Self, ApiError> {
|
||||
let intro_id = value
|
||||
.next_field()
|
||||
.await
|
||||
.map_err(|err| ApiError::bad_request(format!("expected intro id: {err:?}")))?
|
||||
.ok_or(ApiError::bad_request("intro id is required"))?
|
||||
.name()
|
||||
.ok_or(ApiError::bad_request("intro id is required"))?
|
||||
.parse::<i32>()
|
||||
.map_err(|err| ApiError::bad_request(format!("invalid intro id: {err:?}")))?
|
||||
.into();
|
||||
|
||||
Ok(Self {
|
||||
user,
|
||||
guild_id,
|
||||
channel_name,
|
||||
intro_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn add_guild_intro<S: IntroToolService>(
|
||||
State(state): State<ApiState<S>>,
|
||||
Path(guild_id): Path<u64>,
|
||||
|
|
@ -165,3 +195,60 @@ pub(super) async fn upload_guild_intro<S: IntroToolService>(
|
|||
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
pub(super) async fn set_user_intro<S: IntroToolService>(
|
||||
State(state): State<ApiState<S>>,
|
||||
Path((guild_id, channel)): Path<(u64, String)>,
|
||||
user: User,
|
||||
form_data: Multipart,
|
||||
) -> Result<Html<String>, ApiError> {
|
||||
let req = form_data
|
||||
.into_domain((
|
||||
guild_id.into(),
|
||||
user.name().to_string().into(),
|
||||
channel.clone().into(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let guild = state.intro_tool_service.get_guild(guild_id).await?;
|
||||
let user_guilds = state
|
||||
.intro_tool_service
|
||||
.get_user_guilds(user.name())
|
||||
.await?;
|
||||
|
||||
// does user have access to this guild
|
||||
if !user_guilds
|
||||
.iter()
|
||||
.any(|guild_ref| guild_ref.id() == guild.id())
|
||||
{
|
||||
return Err(ApiError::forbidden(
|
||||
"You do not have access to this guild".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: check if channel exists
|
||||
|
||||
state.intro_tool_service.set_user_intro(req).await?;
|
||||
let user = state.intro_tool_service.get_user(user.name()).await?;
|
||||
|
||||
let guild_intros = state
|
||||
.intro_tool_service
|
||||
.get_guild_intros(guild_id.into())
|
||||
.await?;
|
||||
let intros = user
|
||||
.intros()
|
||||
.get(&(guild.id(), channel.clone().into()))
|
||||
.map(|intros| intros.iter())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Html(
|
||||
page::channel_intro_selector(
|
||||
&state.origin,
|
||||
guild_id,
|
||||
&channel.into(),
|
||||
intros,
|
||||
guild_intros.iter(),
|
||||
)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use reqwest::StatusCode;
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::domain::intro_tool::models::guild::{
|
||||
AddIntroToGuildError, GetChannelError, GetGuildError, GetIntroError,
|
||||
AddIntroToGuildError, AddIntroToUserError, GetChannelError, GetGuildError, GetIntroError,
|
||||
GetUserError,
|
||||
};
|
||||
|
||||
pub(super) trait ErrorAsRedirect<T>: Sized {
|
||||
|
|
@ -126,6 +127,8 @@ impl ApiError {
|
|||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
tracing::error!(err = ?self, "error");
|
||||
|
||||
(self.status_code(), Json(self)).into_response()
|
||||
}
|
||||
}
|
||||
|
|
@ -153,6 +156,29 @@ impl From<GetGuildError> for ApiError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<GetUserError> for ApiError {
|
||||
fn from(value: GetUserError) -> Self {
|
||||
match value {
|
||||
GetUserError::NotFound => Self::not_found("User not found"),
|
||||
GetUserError::CouldNotFetchGuilds(get_guild_error) => {
|
||||
tracing::error!(err = ?get_guild_error, "could not fetch guilds from user");
|
||||
|
||||
Self::internal("Could not fetch guilds from user".to_string())
|
||||
}
|
||||
GetUserError::CouldNotFetchChannelIntros(get_channel_intro_error) => {
|
||||
tracing::error!(err = ?get_channel_intro_error, "could not fetch channel intros from user");
|
||||
|
||||
Self::internal("Could not fetch channel intros from user".to_string())
|
||||
}
|
||||
GetUserError::Unknown(error) => {
|
||||
tracing::error!(err = ?error, "unknown error");
|
||||
|
||||
Self::internal(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddIntroToGuildError> for ApiError {
|
||||
fn from(value: AddIntroToGuildError) -> Self {
|
||||
match value {
|
||||
|
|
@ -164,3 +190,28 @@ impl From<AddIntroToGuildError> for ApiError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddIntroToUserError> for ApiError {
|
||||
fn from(value: AddIntroToUserError) -> Self {
|
||||
match value {
|
||||
AddIntroToUserError::Unknown(error) => {
|
||||
tracing::error!(err = ?error, "unknown error");
|
||||
|
||||
Self::internal(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetIntroError> for ApiError {
|
||||
fn from(value: GetIntroError) -> Self {
|
||||
match value {
|
||||
GetIntroError::NotFound => Self::not_found("Intro not found"),
|
||||
GetIntroError::Unknown(error) => {
|
||||
tracing::error!(err = ?error, "unknown error");
|
||||
|
||||
Self::internal(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,10 +390,41 @@ impl IntroToolRepository for Sqlite {
|
|||
Ok(intro_id)
|
||||
}
|
||||
|
||||
async fn add_intro_to_user(
|
||||
async fn set_user_intro(
|
||||
&self,
|
||||
req: AddIntroToUserRequest,
|
||||
) -> Result<(), guild::AddIntroToUserError> {
|
||||
todo!()
|
||||
let conn = self.conn.lock().await;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
DELETE FROM UserIntro
|
||||
WHERE username = ?1
|
||||
AND guild_id = ?2
|
||||
AND channel_name = ?3
|
||||
",
|
||||
[
|
||||
&req.user.to_string(),
|
||||
&req.guild_id.to_string(),
|
||||
&req.channel_name.to_string(),
|
||||
],
|
||||
)
|
||||
.context("failed to delete user intros")?;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
INSERT INTO
|
||||
UserIntro (username, guild_id, channel_name, intro_id)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
[
|
||||
&req.user.to_string(),
|
||||
&req.guild_id.to_string(),
|
||||
&req.channel_name.to_string(),
|
||||
&req.intro_id.to_string(),
|
||||
],
|
||||
)
|
||||
.context("failed to insert user intro")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,6 +238,8 @@ async fn main() -> std::io::Result<()> {
|
|||
dotenv::dotenv().ok();
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
tracing::info!("tracing initialized");
|
||||
|
||||
let secrets = auth::DiscordSecret {
|
||||
client_id: env::var("DISCORD_CLIENT_ID").expect("expected DISCORD_CLIENT_ID env var"),
|
||||
client_secret: env::var("DISCORD_CLIENT_SECRET")
|
||||
|
|
|
|||
Loading…
Reference in New Issue