You've already forked AstralRinth
forked from didirus/AstralRinth
Initial affiliate codes implementation (#4382)
* Initial affiliate codes implementation * some more docs to codes * sqlx prepare * Address PR comments * Address more PR comments * fix clippy * Switch to using Json<T> for type-safe responses
This commit is contained in:
14
apps/labrinth/.sqlx/query-10200da00caaa8f1c0976b9b705bf6d74a59bb62ee5da6dccffec99f36eddac7.json
generated
Normal file
14
apps/labrinth/.sqlx/query-10200da00caaa8f1c0976b9b705bf6d74a59bb62ee5da6dccffec99f36eddac7.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM affiliate_codes WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "10200da00caaa8f1c0976b9b705bf6d74a59bb62ee5da6dccffec99f36eddac7"
|
||||
}
|
||||
40
apps/labrinth/.sqlx/query-6ef8402f0f0685acda118c024d82e31b1b235ab7c5ec00a86af4dfbe81342a58.json
generated
Normal file
40
apps/labrinth/.sqlx/query-6ef8402f0f0685acda118c024d82e31b1b235ab7c5ec00a86af4dfbe81342a58.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes WHERE affiliate = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_by",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "affiliate",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6ef8402f0f0685acda118c024d82e31b1b235ab7c5ec00a86af4dfbe81342a58"
|
||||
}
|
||||
40
apps/labrinth/.sqlx/query-81cfa59dafa8dac87a917a3ddda93d412a579ab17d9dad754e7d9e26d78e0e80.json
generated
Normal file
40
apps/labrinth/.sqlx/query-81cfa59dafa8dac87a917a3ddda93d412a579ab17d9dad754e7d9e26d78e0e80.json
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_by",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "affiliate",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "81cfa59dafa8dac87a917a3ddda93d412a579ab17d9dad754e7d9e26d78e0e80"
|
||||
}
|
||||
38
apps/labrinth/.sqlx/query-b27a1495f0a937e1bae944cfe6e46a4f702536816e5259c4aeec8b905b507473.json
generated
Normal file
38
apps/labrinth/.sqlx/query-b27a1495f0a937e1bae944cfe6e46a4f702536816e5259c4aeec8b905b507473.json
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes ORDER BY created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_by",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "affiliate",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b27a1495f0a937e1bae944cfe6e46a4f702536816e5259c4aeec8b905b507473"
|
||||
}
|
||||
22
apps/labrinth/.sqlx/query-b36457e1aa73b5517ca4f41e4144084a0aa996168d2660c13a1d4c9fc4594859.json
generated
Normal file
22
apps/labrinth/.sqlx/query-b36457e1aa73b5517ca4f41e4144084a0aa996168d2660c13a1d4c9fc4594859.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM affiliate_codes WHERE id=$1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b36457e1aa73b5517ca4f41e4144084a0aa996168d2660c13a1d4c9fc4594859"
|
||||
}
|
||||
17
apps/labrinth/.sqlx/query-d51d96a9771ce1c3a2987e0790ef25bc55122c934c73418a97ee9cd81f56b251.json
generated
Normal file
17
apps/labrinth/.sqlx/query-d51d96a9771ce1c3a2987e0790ef25bc55122c934c73418a97ee9cd81f56b251.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO affiliate_codes (id, created_at, created_by, affiliate)\n VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d51d96a9771ce1c3a2987e0790ef25bc55122c934c73418a97ee9cd81f56b251"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE affiliate_codes (
|
||||
id bigint PRIMARY KEY,
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by bigint NOT NULL REFERENCES users(id),
|
||||
affiliate bigint NOT NULL REFERENCES users(id),
|
||||
-- left nullable so we can explicitly set payouts if we need to,
|
||||
-- and use a global default if unset
|
||||
revenue_split float
|
||||
);
|
||||
117
apps/labrinth/src/database/models/affiliate_code_item.rs
Normal file
117
apps/labrinth/src/database/models/affiliate_code_item.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
|
||||
use crate::database::models::{DBAffiliateCodeId, DBUserId, DatabaseError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DBAffiliateCode {
|
||||
pub id: DBAffiliateCodeId,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub created_by: DBUserId,
|
||||
pub affiliate: DBUserId,
|
||||
}
|
||||
|
||||
impl DBAffiliateCode {
|
||||
pub async fn get_by_id(
|
||||
id: DBAffiliateCodeId,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Option<DBAffiliateCode>, DatabaseError> {
|
||||
let record = sqlx::query!(
|
||||
"SELECT id, created_at, created_by, affiliate
|
||||
FROM affiliate_codes WHERE id = $1",
|
||||
id as DBAffiliateCodeId
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(record.map(|record| DBAffiliateCode {
|
||||
id: DBAffiliateCodeId(record.id),
|
||||
created_at: record.created_at,
|
||||
created_by: DBUserId(record.created_by),
|
||||
affiliate: DBUserId(record.affiliate),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_by_affiliate(
|
||||
affiliate: DBUserId,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Vec<DBAffiliateCode>, DatabaseError> {
|
||||
let records = sqlx::query!(
|
||||
"SELECT id, created_at, created_by, affiliate
|
||||
FROM affiliate_codes WHERE affiliate = $1",
|
||||
affiliate as DBUserId
|
||||
)
|
||||
.fetch(exec)
|
||||
.map(|record| {
|
||||
let record = record?;
|
||||
Ok::<_, DatabaseError>(DBAffiliateCode {
|
||||
id: DBAffiliateCodeId(record.id),
|
||||
created_at: record.created_at,
|
||||
created_by: DBUserId(record.created_by),
|
||||
affiliate: DBUserId(record.affiliate),
|
||||
})
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
&self,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<(), DatabaseError> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO affiliate_codes (id, created_at, created_by, affiliate)
|
||||
VALUES ($1, $2, $3, $4)",
|
||||
self.id as DBAffiliateCodeId,
|
||||
self.created_at,
|
||||
self.created_by as DBUserId,
|
||||
self.affiliate as DBUserId
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(
|
||||
id: DBAffiliateCodeId,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Option<()>, DatabaseError> {
|
||||
let result = sqlx::query!(
|
||||
"DELETE FROM affiliate_codes WHERE id = $1",
|
||||
id as DBAffiliateCodeId
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() > 0 {
|
||||
Ok(Some(()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Vec<DBAffiliateCode>, DatabaseError> {
|
||||
let records = sqlx::query!(
|
||||
"SELECT id, created_at, created_by, affiliate
|
||||
FROM affiliate_codes ORDER BY created_at DESC"
|
||||
)
|
||||
.fetch(exec)
|
||||
.map(|record| {
|
||||
let record = record?;
|
||||
Ok::<_, DatabaseError>(DBAffiliateCode {
|
||||
id: DBAffiliateCodeId(record.id),
|
||||
created_at: record.created_at,
|
||||
created_by: DBUserId(record.created_by),
|
||||
affiliate: DBUserId(record.affiliate),
|
||||
})
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::DatabaseError;
|
||||
use crate::models::ids::{
|
||||
ChargeId, CollectionId, FileId, ImageId, NotificationId,
|
||||
AffiliateCodeId, ChargeId, CollectionId, FileId, ImageId, NotificationId,
|
||||
OAuthAccessTokenId, OAuthClientAuthorizationId, OAuthClientId,
|
||||
OAuthRedirectUriId, OrganizationId, PatId, PayoutId, ProductId,
|
||||
ProductPriceId, ProjectId, ReportId, SessionId, SharedInstanceId,
|
||||
@@ -263,6 +263,10 @@ db_id_interface!(
|
||||
VersionId,
|
||||
generator: generate_version_id @ "versions",
|
||||
);
|
||||
db_id_interface!(
|
||||
AffiliateCodeId,
|
||||
generator: generate_affiliate_code_id @ "affiliate_codes",
|
||||
);
|
||||
|
||||
short_id_type!(CategoryId);
|
||||
short_id_type!(GameId);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod affiliate_code_item;
|
||||
pub mod categories;
|
||||
pub mod charge_item;
|
||||
pub mod collection_item;
|
||||
@@ -34,6 +35,7 @@ pub mod users_notifications_preferences_item;
|
||||
pub mod users_redeemals;
|
||||
pub mod version_item;
|
||||
|
||||
pub use affiliate_code_item::DBAffiliateCode;
|
||||
pub use collection_item::DBCollection;
|
||||
pub use ids::*;
|
||||
pub use image_item::DBImage;
|
||||
|
||||
67
apps/labrinth/src/models/v3/affiliate_code.rs
Normal file
67
apps/labrinth/src/models/v3/affiliate_code.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use ariadne::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::ids::AffiliateCodeId;
|
||||
|
||||
/// Affiliate code used to track referral purchases.
|
||||
///
|
||||
/// See [`AffiliateCode`].
|
||||
///
|
||||
/// This struct contains information which should only be visible to admins.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AdminAffiliateCode {
|
||||
/// Affiliate code ID.
|
||||
pub id: AffiliateCodeId,
|
||||
/// When the code was created.
|
||||
pub created_at: DateTime<Utc>,
|
||||
/// User who created the code.
|
||||
pub created_by: UserId,
|
||||
/// User who refers the purchaser.
|
||||
pub affiliate: UserId,
|
||||
}
|
||||
|
||||
/// Affiliate code used to track referral purchases.
|
||||
///
|
||||
/// When a user follows a URL with [`AffiliateCode::id`] as an affiliate
|
||||
/// parameter, the code will be saved as a cookie. When the same user purchases
|
||||
/// a product with an affiliate code cookie, the purchase under that code is
|
||||
/// tracked.
|
||||
///
|
||||
/// This struct contains information which is allowed to be seen by an
|
||||
/// affiliate.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AffiliateCode {
|
||||
/// Affiliate code ID.
|
||||
pub id: AffiliateCodeId,
|
||||
/// User who refers the purchaser.
|
||||
pub affiliate: UserId,
|
||||
}
|
||||
|
||||
impl From<crate::database::models::affiliate_code_item::DBAffiliateCode>
|
||||
for AdminAffiliateCode
|
||||
{
|
||||
fn from(
|
||||
data: crate::database::models::affiliate_code_item::DBAffiliateCode,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: data.id.into(),
|
||||
created_at: data.created_at,
|
||||
created_by: data.created_by.into(),
|
||||
affiliate: data.affiliate.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::database::models::affiliate_code_item::DBAffiliateCode>
|
||||
for AffiliateCode
|
||||
{
|
||||
fn from(
|
||||
data: crate::database::models::affiliate_code_item::DBAffiliateCode,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: data.id.into(),
|
||||
affiliate: data.affiliate.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,3 +25,4 @@ base62_id!(ThreadId);
|
||||
base62_id!(ThreadMessageId);
|
||||
base62_id!(UserSubscriptionId);
|
||||
base62_id!(VersionId);
|
||||
base62_id!(AffiliateCodeId);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod affiliate_code;
|
||||
pub mod analytics;
|
||||
pub mod billing;
|
||||
pub mod collections;
|
||||
|
||||
231
apps/labrinth/src/routes/internal/affiliate.rs
Normal file
231
apps/labrinth/src/routes/internal/affiliate.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use crate::{
|
||||
auth::get_user_from_headers,
|
||||
database::{
|
||||
models::{DBAffiliateCode, DBAffiliateCodeId, DBUser, DBUserId},
|
||||
redis::RedisPool,
|
||||
},
|
||||
models::{
|
||||
ids::AffiliateCodeId,
|
||||
pats::Scopes,
|
||||
v3::affiliate_code::{AdminAffiliateCode, AffiliateCode},
|
||||
},
|
||||
queue::session::AuthQueue,
|
||||
};
|
||||
use actix_web::{
|
||||
HttpRequest, HttpResponse,
|
||||
web::{self, Json},
|
||||
};
|
||||
use ariadne::ids::UserId;
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::routes::ApiError;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("affiliate")
|
||||
.route("/code", web::get().to(code_get_all))
|
||||
.route("/code", web::put().to(code_create))
|
||||
.route("/code/{id}", web::get().to(code_get))
|
||||
.route("/code/{id}", web::delete().to(code_delete))
|
||||
.route("/self", web::get().to(self_get)),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodeGetAllResponse(Vec<AdminAffiliateCode>);
|
||||
|
||||
async fn code_get_all(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<Json<CodeGetAllResponse>, ApiError> {
|
||||
let (_, user) = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::SESSION_ACCESS,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to read all affiliate codes!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let codes = DBAffiliateCode::get_all(&**pool).await?;
|
||||
let codes = codes
|
||||
.into_iter()
|
||||
.map(AdminAffiliateCode::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(CodeGetAllResponse(codes)))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CodeCreateRequest {
|
||||
affiliate: UserId,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodeCreateResponse(AdminAffiliateCode);
|
||||
|
||||
async fn code_create(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
body: web::Json<CodeCreateRequest>,
|
||||
) -> Result<Json<CodeCreateResponse>, ApiError> {
|
||||
let (_, creator) = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::SESSION_ACCESS,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !creator.role.is_admin() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to create an affiliate code!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let creator_id = DBUserId::from(creator.id);
|
||||
let affiliate_id = DBUserId::from(body.affiliate);
|
||||
let Some(_affiliate_user) =
|
||||
DBUser::get_id(affiliate_id, &**pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"Affiliate user not found!".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let affiliate_code_id =
|
||||
crate::database::models::generate_affiliate_code_id(&mut transaction)
|
||||
.await?;
|
||||
|
||||
let code = DBAffiliateCode {
|
||||
id: affiliate_code_id,
|
||||
created_at: Utc::now(),
|
||||
created_by: creator_id,
|
||||
affiliate: affiliate_id,
|
||||
};
|
||||
code.insert(&mut *transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(Json(CodeCreateResponse(AdminAffiliateCode::from(code))))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodeGetResponse(AdminAffiliateCode);
|
||||
|
||||
async fn code_get(
|
||||
req: HttpRequest,
|
||||
path: web::Path<(AffiliateCodeId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<Json<CodeGetResponse>, ApiError> {
|
||||
let (_, user) = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::SESSION_ACCESS,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to read an affiliate code!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let (affiliate_code_id,) = path.into_inner();
|
||||
let affiliate_code_id = DBAffiliateCodeId::from(affiliate_code_id);
|
||||
|
||||
if let Some(model) =
|
||||
DBAffiliateCode::get_by_id(affiliate_code_id, &**pool).await?
|
||||
{
|
||||
let model = AdminAffiliateCode::from(model);
|
||||
Ok(Json(CodeGetResponse(model)))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
async fn code_delete(
|
||||
req: HttpRequest,
|
||||
path: web::Path<(AffiliateCodeId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (_, user) = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::SESSION_ACCESS,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to delete an affiliate code!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let (affiliate_code_id,) = path.into_inner();
|
||||
let affiliate_code_id = DBAffiliateCodeId::from(affiliate_code_id);
|
||||
|
||||
let result = DBAffiliateCode::remove(affiliate_code_id, &**pool).await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SelfGetResponse(Vec<AffiliateCode>);
|
||||
|
||||
async fn self_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<Json<SelfGetResponse>, ApiError> {
|
||||
let (_, user) = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::SESSION_ACCESS,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let codes =
|
||||
DBAffiliateCode::get_by_affiliate(DBUserId::from(user.id), &**pool)
|
||||
.await?;
|
||||
|
||||
let codes = codes
|
||||
.into_iter()
|
||||
.map(AffiliateCode::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(SelfGetResponse(codes)))
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub(crate) mod admin;
|
||||
pub mod affiliate;
|
||||
pub mod billing;
|
||||
pub mod external_notifications;
|
||||
pub mod flows;
|
||||
@@ -28,6 +29,7 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
.configure(gdpr::config)
|
||||
.configure(statuses::config)
|
||||
.configure(medal::config)
|
||||
.configure(external_notifications::config),
|
||||
.configure(external_notifications::config)
|
||||
.configure(affiliate::config),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user