set user intro
parent
a1b3bbb999
commit
c9a91d3d36
|
|
@ -116,10 +116,10 @@ where
|
||||||
self.wrapped_service.add_intro_to_guild(req).await
|
self.wrapped_service.add_intro_to_guild(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_intro_to_user(
|
async fn set_user_intro(
|
||||||
&self,
|
&self,
|
||||||
req: models::guild::AddIntroToUserRequest,
|
req: models::guild::AddIntroToUserRequest,
|
||||||
) -> Result<(), models::guild::AddIntroToUserError> {
|
) -> 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 {
|
impl std::fmt::Display for IntroId {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
|
|
@ -271,10 +283,10 @@ pub enum IntroRequestData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AddIntroToUserRequest {
|
pub struct AddIntroToUserRequest {
|
||||||
user: UserName,
|
pub user: UserName,
|
||||||
guild_id: GuildId,
|
pub guild_id: GuildId,
|
||||||
channel_name: ChannelName,
|
pub channel_name: ChannelName,
|
||||||
intro_id: IntroId,
|
pub intro_id: IntroId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ pub trait IntroToolService: Send + Sync + Clone + 'static {
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
) -> impl Future<Output = Result<User, GetUserError>> + Send;
|
) -> 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_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
|
||||||
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
||||||
async fn create_channel(
|
async fn create_channel(
|
||||||
|
|
@ -48,11 +53,6 @@ pub trait IntroToolService: Send + Sync + Clone + 'static {
|
||||||
&self,
|
&self,
|
||||||
req: AddIntroToGuildRequest,
|
req: AddIntroToGuildRequest,
|
||||||
) -> impl Future<Output = Result<IntroId, AddIntroToGuildError>> + Send;
|
) -> 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 {
|
pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
||||||
|
|
@ -97,6 +97,11 @@ pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
) -> impl Future<Output = Result<User, GetUserError>> + Send;
|
) -> 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_guild(&self, req: CreateGuildRequest) -> Result<Guild, CreateGuildError>;
|
||||||
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError>;
|
||||||
async fn create_channel(
|
async fn create_channel(
|
||||||
|
|
@ -110,11 +115,6 @@ pub trait IntroToolRepository: Send + Sync + Clone + 'static {
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
filename: String,
|
filename: String,
|
||||||
) -> impl Future<Output = Result<IntroId, AddIntroToGuildError>> + Send;
|
) -> 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 {
|
pub trait RemoteAudioFetcher: Send + Sync + Clone + 'static {
|
||||||
|
|
|
||||||
|
|
@ -124,10 +124,10 @@ where
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_intro_to_user(
|
async fn set_user_intro(
|
||||||
&self,
|
&self,
|
||||||
req: guild::AddIntroToUserRequest,
|
req: guild::AddIntroToUserRequest,
|
||||||
) -> Result<(), guild::AddIntroToUserError> {
|
) -> 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",
|
"/v2/intros/:guild/upload",
|
||||||
post(handlers::upload_guild_intro),
|
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("/guild/:guild_id/setup", get(routes::guild_setup))
|
||||||
// .route(
|
// .route(
|
||||||
|
|
@ -133,10 +137,6 @@ where
|
||||||
// )
|
// )
|
||||||
// .route("/v2/auth", get(routes::v2_auth))
|
// .route("/v2/auth", get(routes::v2_auth))
|
||||||
// .route(
|
// .route(
|
||||||
// "/v2/intros/add/:guild_id/:channel",
|
|
||||||
// post(routes::v2_add_intro_to_user),
|
|
||||||
// )
|
|
||||||
// .route(
|
|
||||||
// "/v2/intros/remove/:guild_id/:channel",
|
// "/v2/intros/remove/:guild_id/:channel",
|
||||||
// post(routes::v2_remove_intro_from_user),
|
// post(routes::v2_remove_intro_from_user),
|
||||||
// )
|
// )
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,20 @@ use std::collections::HashMap;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Multipart, Path, Query, State},
|
extract::{Multipart, Path, Query, State},
|
||||||
http::{HeaderMap, HeaderValue},
|
http::{HeaderMap, HeaderValue},
|
||||||
|
response::Html,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::intro_tool::{
|
domain::intro_tool::{
|
||||||
models::guild::{AddIntroToGuildRequest, GuildId, IntroRequestData, User},
|
models::guild::{
|
||||||
|
AddIntroToGuildRequest, AddIntroToUserRequest, ChannelName, GuildId, IntroRequestData,
|
||||||
|
User, UserName,
|
||||||
|
},
|
||||||
ports::IntroToolService,
|
ports::IntroToolService,
|
||||||
},
|
},
|
||||||
|
htmx::Build,
|
||||||
inbound::{
|
inbound::{
|
||||||
http::ApiState,
|
http::{page, ApiState},
|
||||||
response::{ApiError, ErrorAsRedirect},
|
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>(
|
pub(super) async fn add_guild_intro<S: IntroToolService>(
|
||||||
State(state): State<ApiState<S>>,
|
State(state): State<ApiState<S>>,
|
||||||
Path(guild_id): Path<u64>,
|
Path(guild_id): Path<u64>,
|
||||||
|
|
@ -165,3 +195,60 @@ pub(super) async fn upload_guild_intro<S: IntroToolService>(
|
||||||
|
|
||||||
Ok(headers)
|
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 serde::Serialize;
|
||||||
|
|
||||||
use crate::domain::intro_tool::models::guild::{
|
use crate::domain::intro_tool::models::guild::{
|
||||||
AddIntroToGuildError, GetChannelError, GetGuildError, GetIntroError,
|
AddIntroToGuildError, AddIntroToUserError, GetChannelError, GetGuildError, GetIntroError,
|
||||||
|
GetUserError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) trait ErrorAsRedirect<T>: Sized {
|
pub(super) trait ErrorAsRedirect<T>: Sized {
|
||||||
|
|
@ -126,6 +127,8 @@ impl ApiError {
|
||||||
|
|
||||||
impl IntoResponse for ApiError {
|
impl IntoResponse for ApiError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
tracing::error!(err = ?self, "error");
|
||||||
|
|
||||||
(self.status_code(), Json(self)).into_response()
|
(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 {
|
impl From<AddIntroToGuildError> for ApiError {
|
||||||
fn from(value: AddIntroToGuildError) -> Self {
|
fn from(value: AddIntroToGuildError) -> Self {
|
||||||
match value {
|
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)
|
Ok(intro_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_intro_to_user(
|
async fn set_user_intro(
|
||||||
&self,
|
&self,
|
||||||
req: AddIntroToUserRequest,
|
req: AddIntroToUserRequest,
|
||||||
) -> Result<(), guild::AddIntroToUserError> {
|
) -> 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();
|
dotenv::dotenv().ok();
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
tracing::info!("tracing initialized");
|
||||||
|
|
||||||
let secrets = auth::DiscordSecret {
|
let secrets = auth::DiscordSecret {
|
||||||
client_id: env::var("DISCORD_CLIENT_ID").expect("expected DISCORD_CLIENT_ID env var"),
|
client_id: env::var("DISCORD_CLIENT_ID").expect("expected DISCORD_CLIENT_ID env var"),
|
||||||
client_secret: env::var("DISCORD_CLIENT_SECRET")
|
client_secret: env::var("DISCORD_CLIENT_SECRET")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue