feat(admin,shop-admin): content/brand management + merchant shop profile (add-content-admin-ui)
This commit is contained in:
@@ -13,11 +13,11 @@ use crate::error::ApiResult;
|
||||
use crate::models::{Shop, ShopStatus};
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::service::{self, ProfileBody, ShopProfileView};
|
||||
use super::service::{self, MerchantProfileBody, ProfileBody, ShopProfileView};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/shop/profile", get(my_shop))
|
||||
.route("/shop/profile", get(my_shop).put(update_my_profile))
|
||||
.route("/shops", get(list_shops))
|
||||
.route("/shops/{slug}", get(get_shop))
|
||||
.route("/admin/shops", get(admin_list_shops).post(create_shop))
|
||||
@@ -30,6 +30,15 @@ async fn my_shop(State(state): State<AppState>, auth: AuthUser) -> ApiResult<Jso
|
||||
Ok(Json(service::get_by_id(&state, shop_id).await?))
|
||||
}
|
||||
|
||||
async fn update_my_profile(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthUser,
|
||||
Json(body): Json<MerchantProfileBody>,
|
||||
) -> ApiResult<Json<ShopProfileView>> {
|
||||
let shop_id = auth.require_shop()?;
|
||||
Ok(Json(service::set_my_profile(&state, shop_id, body).await?))
|
||||
}
|
||||
|
||||
async fn list_shops(State(state): State<AppState>) -> ApiResult<Json<Vec<ShopProfileView>>> {
|
||||
Ok(Json(service::list_active_profiles(&state).await?))
|
||||
}
|
||||
|
||||
@@ -111,6 +111,19 @@ pub struct ProfileBody {
|
||||
pub score_speed: Option<f64>,
|
||||
}
|
||||
|
||||
/// Merchant self-service write: same fields as the admin body minus the
|
||||
/// platform-owned scores; serde ignores any score values merchants send.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MerchantProfileBody {
|
||||
pub logo: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub company: Option<String>,
|
||||
pub region: Option<String>,
|
||||
pub address: Option<Value>,
|
||||
pub notice: Option<Value>,
|
||||
pub after_sale: Option<Value>,
|
||||
}
|
||||
|
||||
fn bilingual(label: &Value, field: &str) -> ApiResult<()> {
|
||||
let ok = ["en", "zh"].iter().all(|code| {
|
||||
label
|
||||
@@ -189,3 +202,60 @@ pub async fn set_profile(
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound("shop".into()))
|
||||
}
|
||||
|
||||
/// Merchant upsert of their own shop profile. Scores are platform-owned, so
|
||||
/// this statement never touches the score columns on insert or update.
|
||||
pub async fn set_my_profile(
|
||||
state: &AppState,
|
||||
id: Uuid,
|
||||
body: MerchantProfileBody,
|
||||
) -> ApiResult<ShopProfileView> {
|
||||
for (value, field) in [
|
||||
(&body.address, "address"),
|
||||
(&body.notice, "notice"),
|
||||
(&body.after_sale, "after_sale"),
|
||||
] {
|
||||
if let Some(label) = value {
|
||||
bilingual(label, field)?;
|
||||
}
|
||||
}
|
||||
|
||||
let exists: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM shops WHERE id = $1)")
|
||||
.bind(id)
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
if !exists {
|
||||
return Err(ApiError::NotFound("shop".into()));
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO shop_profiles (shop_id, logo, banner, company, region, address, notice,
|
||||
after_sale, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now())
|
||||
ON CONFLICT (shop_id) DO UPDATE SET
|
||||
logo = EXCLUDED.logo,
|
||||
banner = EXCLUDED.banner,
|
||||
company = EXCLUDED.company,
|
||||
region = EXCLUDED.region,
|
||||
address = EXCLUDED.address,
|
||||
notice = EXCLUDED.notice,
|
||||
after_sale = EXCLUDED.after_sale,
|
||||
updated_at = now()",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(&body.logo)
|
||||
.bind(&body.banner)
|
||||
.bind(&body.company)
|
||||
.bind(&body.region)
|
||||
.bind(&body.address)
|
||||
.bind(&body.notice)
|
||||
.bind(&body.after_sale)
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
|
||||
sqlx::query_as::<_, ShopProfileView>(&format!("{SELECT_PROFILE} WHERE s.id = $1"))
|
||||
.bind(id)
|
||||
.fetch_optional(&state.db)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound("shop".into()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user