Merge beta into release #21

Merged
didirus merged 276 commits from beta into release 2025-11-01 13:04:25 +00:00
9 changed files with 201 additions and 43 deletions
Showing only changes of commit 20281c4efc - Show all commits

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes WHERE id = $1",
"query": "SELECT id, created_at, created_by, affiliate, source_name\n FROM affiliate_codes WHERE affiliate = $1",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "affiliate",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "source_name",
"type_info": "Varchar"
}
],
"parameters": {
@@ -33,8 +38,9 @@
false,
false,
false,
false,
false
]
},
"hash": "81cfa59dafa8dac87a917a3ddda93d412a579ab17d9dad754e7d9e26d78e0e80"
"hash": "057b0fda8e0ad34fc880121b6461ddfc3c61d4a0bf95e376e24440b6a58d2844"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO affiliate_codes (id, created_at, created_by, affiliate)\n VALUES ($1, $2, $3, $4)",
"query": "INSERT INTO affiliate_codes (id, created_at, created_by, affiliate, source_name)\n VALUES ($1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
@@ -8,10 +8,11 @@
"Int8",
"Timestamptz",
"Int8",
"Int8"
"Int8",
"Varchar"
]
},
"nullable": []
},
"hash": "d51d96a9771ce1c3a2987e0790ef25bc55122c934c73418a97ee9cd81f56b251"
"hash": "0e6d18643a4a7834eb34fe519b073e290b1089d2d0cfdfdb45b5125a931d08ca"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes WHERE affiliate = $1",
"query": "SELECT id, created_at, created_by, affiliate, source_name\n FROM affiliate_codes WHERE id = $1",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "affiliate",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "source_name",
"type_info": "Varchar"
}
],
"parameters": {
@@ -33,8 +38,9 @@
false,
false,
false,
false,
false
]
},
"hash": "6ef8402f0f0685acda118c024d82e31b1b235ab7c5ec00a86af4dfbe81342a58"
"hash": "1bd7365eaeac25b1286030a900767eef3b1b6e200ab0dbb3a4274eeba95f9568"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, created_at, created_by, affiliate\n FROM affiliate_codes ORDER BY created_at DESC",
"query": "SELECT id, created_at, created_by, affiliate, source_name\n FROM affiliate_codes ORDER BY created_at DESC",
"describe": {
"columns": [
{
@@ -22,6 +22,11 @@
"ordinal": 3,
"name": "affiliate",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "source_name",
"type_info": "Varchar"
}
],
"parameters": {
@@ -31,8 +36,9 @@
false,
false,
false,
false,
false
]
},
"hash": "b27a1495f0a937e1bae944cfe6e46a4f702536816e5259c4aeec8b905b507473"
"hash": "cc95f1b143399b5ecebc91fa74820bfe8c1057c26471b17efa4213a09520a65e"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE affiliate_codes SET source_name = $1 WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "d307116366d03315a8a01d1b62c5ca81624a42d01a43e1d5adf8881f8a71d495"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE affiliate_codes
ADD COLUMN source_name VARCHAR(255) NOT NULL DEFAULT '(unnamed)';

View File

@@ -9,6 +9,7 @@ pub struct DBAffiliateCode {
pub created_at: DateTime<Utc>,
pub created_by: DBUserId,
pub affiliate: DBUserId,
pub source_name: String,
}
impl DBAffiliateCode {
@@ -17,7 +18,7 @@ impl DBAffiliateCode {
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Option<DBAffiliateCode>, DatabaseError> {
let record = sqlx::query!(
"SELECT id, created_at, created_by, affiliate
"SELECT id, created_at, created_by, affiliate, source_name
FROM affiliate_codes WHERE id = $1",
id as DBAffiliateCodeId
)
@@ -29,6 +30,7 @@ impl DBAffiliateCode {
created_at: record.created_at,
created_by: DBUserId(record.created_by),
affiliate: DBUserId(record.affiliate),
source_name: record.source_name,
}))
}
@@ -37,7 +39,7 @@ impl DBAffiliateCode {
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Vec<DBAffiliateCode>, DatabaseError> {
let records = sqlx::query!(
"SELECT id, created_at, created_by, affiliate
"SELECT id, created_at, created_by, affiliate, source_name
FROM affiliate_codes WHERE affiliate = $1",
affiliate as DBUserId
)
@@ -49,6 +51,7 @@ impl DBAffiliateCode {
created_at: record.created_at,
created_by: DBUserId(record.created_by),
affiliate: DBUserId(record.affiliate),
source_name: record.source_name,
})
})
.try_collect::<Vec<_>>()
@@ -62,12 +65,13 @@ impl DBAffiliateCode {
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)",
"INSERT INTO affiliate_codes (id, created_at, created_by, affiliate, source_name)
VALUES ($1, $2, $3, $4, $5)",
self.id as DBAffiliateCodeId,
self.created_at,
self.created_by as DBUserId,
self.affiliate as DBUserId
self.affiliate as DBUserId,
self.source_name
)
.execute(exec)
.await?;
@@ -92,11 +96,27 @@ impl DBAffiliateCode {
}
}
pub async fn update_source_name(
id: DBAffiliateCodeId,
source_name: &str,
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<bool, DatabaseError> {
let result = sqlx::query!(
"UPDATE affiliate_codes SET source_name = $1 WHERE id = $2",
source_name,
id as DBAffiliateCodeId
)
.execute(exec)
.await?;
Ok(result.rows_affected() > 0)
}
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
"SELECT id, created_at, created_by, affiliate, source_name
FROM affiliate_codes ORDER BY created_at DESC"
)
.fetch(exec)
@@ -107,6 +127,7 @@ impl DBAffiliateCode {
created_at: record.created_at,
created_by: DBUserId(record.created_by),
affiliate: DBUserId(record.affiliate),
source_name: record.source_name,
})
})
.try_collect::<Vec<_>>()

View File

@@ -17,9 +17,7 @@ bitflags::bitflags! {
const ALPHA_TESTER = 1 << 4;
const CONTRIBUTOR = 1 << 5;
const TRANSLATOR = 1 << 6;
const ALL = 0b1111111;
const NONE = 0b0;
const AFFILIATE = 1 << 7;
}
}
@@ -27,7 +25,7 @@ bitflags_serde_impl!(Badges, u64);
impl Default for Badges {
fn default() -> Badges {
Badges::NONE
Badges::empty()
}
}

View File

@@ -7,6 +7,7 @@ use crate::{
models::{
ids::AffiliateCodeId,
pats::Scopes,
users::Badges,
v3::affiliate_code::{AdminAffiliateCode, AffiliateCode},
},
queue::session::AuthQueue,
@@ -25,23 +26,25 @@ 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)),
.route("/admin", web::get().to(admin_get_all))
.route("/admin", web::put().to(admin_create))
.route("/admin/{id}", web::get().to(admin_get))
.route("/admin/{id}", web::delete().to(admin_delete))
.route("/self", web::get().to(self_get_all))
.route("/self", web::put().to(self_patch))
.route("/self/{id}", web::delete().to(self_delete)),
);
}
#[derive(Serialize)]
struct CodeGetAllResponse(Vec<AdminAffiliateCode>);
struct AdminGetAllResponse(Vec<AdminAffiliateCode>);
async fn code_get_all(
async fn admin_get_all(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<Json<CodeGetAllResponse>, ApiError> {
) -> Result<Json<AdminGetAllResponse>, ApiError> {
let (_, user) = get_user_from_headers(
&req,
&**pool,
@@ -64,24 +67,25 @@ async fn code_get_all(
.map(AdminAffiliateCode::from)
.collect::<Vec<_>>();
Ok(Json(CodeGetAllResponse(codes)))
Ok(Json(AdminGetAllResponse(codes)))
}
#[derive(Serialize, Deserialize)]
struct CodeCreateRequest {
struct AdminCreateRequest {
affiliate: UserId,
source_name: String,
}
#[derive(Serialize)]
struct CodeCreateResponse(AdminAffiliateCode);
struct AdminCreateResponse(AdminAffiliateCode);
async fn code_create(
async fn admin_create(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
body: web::Json<CodeCreateRequest>,
) -> Result<Json<CodeCreateResponse>, ApiError> {
body: web::Json<AdminCreateRequest>,
) -> Result<Json<AdminCreateResponse>, ApiError> {
let (_, creator) = get_user_from_headers(
&req,
&**pool,
@@ -119,24 +123,25 @@ async fn code_create(
created_at: Utc::now(),
created_by: creator_id,
affiliate: affiliate_id,
source_name: body.source_name.clone(),
};
code.insert(&mut *transaction).await?;
transaction.commit().await?;
Ok(Json(CodeCreateResponse(AdminAffiliateCode::from(code))))
Ok(Json(AdminCreateResponse(AdminAffiliateCode::from(code))))
}
#[derive(Serialize)]
struct CodeGetResponse(AdminAffiliateCode);
struct AdminGetResponse(AdminAffiliateCode);
async fn code_get(
async fn admin_get(
req: HttpRequest,
path: web::Path<(AffiliateCodeId,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<Json<CodeGetResponse>, ApiError> {
) -> Result<Json<AdminGetResponse>, ApiError> {
let (_, user) = get_user_from_headers(
&req,
&**pool,
@@ -159,13 +164,13 @@ async fn code_get(
DBAffiliateCode::get_by_id(affiliate_code_id, &**pool).await?
{
let model = AdminAffiliateCode::from(model);
Ok(Json(CodeGetResponse(model)))
Ok(Json(AdminGetResponse(model)))
} else {
Err(ApiError::NotFound)
}
}
async fn code_delete(
async fn admin_delete(
req: HttpRequest,
path: web::Path<(AffiliateCodeId,)>,
pool: web::Data<PgPool>,
@@ -201,14 +206,14 @@ async fn code_delete(
}
#[derive(Serialize)]
struct SelfGetResponse(Vec<AffiliateCode>);
struct SelfGetAllResponse(Vec<AffiliateCode>);
async fn self_get(
async fn self_get_all(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<Json<SelfGetResponse>, ApiError> {
) -> Result<Json<SelfGetAllResponse>, ApiError> {
let (_, user) = get_user_from_headers(
&req,
&**pool,
@@ -218,6 +223,13 @@ async fn self_get(
)
.await?;
if !user.badges.contains(Badges::AFFILIATE) {
return Err(ApiError::CustomAuthentication(
"You do not have permission to view your affiliate codes!"
.to_string(),
));
}
let codes =
DBAffiliateCode::get_by_affiliate(DBUserId::from(user.id), &**pool)
.await?;
@@ -227,5 +239,96 @@ async fn self_get(
.map(AffiliateCode::from)
.collect::<Vec<_>>();
Ok(Json(SelfGetResponse(codes)))
Ok(Json(SelfGetAllResponse(codes)))
}
#[derive(Deserialize)]
struct SelfPatchRequest {
id: AffiliateCodeId,
source_name: String,
}
async fn self_patch(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
body: web::Json<SelfPatchRequest>,
) -> Result<HttpResponse, ApiError> {
let (_, user) = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::SESSION_ACCESS,
)
.await?;
if !user.badges.contains(Badges::AFFILIATE) {
return Err(ApiError::CustomAuthentication(
"You do not have permission to update your affiliate codes!"
.to_string(),
));
}
let affiliate_code_id = DBAffiliateCodeId::from(body.id);
let existing_code = DBAffiliateCode::get_by_id(affiliate_code_id, &**pool)
.await?
.ok_or(ApiError::NotFound)?;
if existing_code.affiliate != DBUserId::from(user.id) {
return Err(ApiError::NotFound);
}
DBAffiliateCode::update_source_name(
affiliate_code_id,
&body.source_name,
&**pool,
)
.await?;
Ok(HttpResponse::NoContent().finish())
}
async fn self_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.badges.contains(Badges::AFFILIATE) {
return Err(ApiError::CustomAuthentication(
"You do not have permission to delete your affiliate codes!"
.to_string(),
));
}
let (affiliate_code_id,) = path.into_inner();
let affiliate_code_id = DBAffiliateCodeId::from(affiliate_code_id);
let code = DBAffiliateCode::get_by_id(affiliate_code_id, &**pool)
.await?
.ok_or(ApiError::NotFound)?;
if code.affiliate != DBUserId::from(user.id) {
return Err(ApiError::NotFound);
}
let result = DBAffiliateCode::remove(affiliate_code_id, &**pool).await?;
if result.is_some() {
Ok(HttpResponse::NoContent().finish())
} else {
Err(ApiError::NotFound)
}
}