This commit is contained in:
FrenchGithubUser
2025-12-13 14:24:43 +01:00
6 changed files with 46 additions and 17 deletions

View File

@@ -1,12 +1,13 @@
use crate::Arcadia;
use actix_web::{
web::{Data, Json},
HttpResponse,
HttpRequest, HttpResponse,
};
use arcadia_common::error::Result;
use arcadia_storage::{
models::user_application::{UserApplication, UserCreatedUserApplication},
redis::RedisPoolInterface,
sqlx::types::ipnetwork::IpNetwork,
};
#[utoipa::path(
@@ -20,11 +21,18 @@ use arcadia_storage::{
)]
pub async fn exec<R: RedisPoolInterface + 'static>(
arc: Data<Arcadia<R>>,
req: HttpRequest,
application: Json<UserCreatedUserApplication>,
) -> Result<HttpResponse> {
let client_ip = req
.connection_info()
.realip_remote_addr()
.and_then(|ip| ip.parse::<IpNetwork>().ok())
.unwrap();
let created_application = arc
.pool
.create_user_application(&application.into_inner())
.create_user_application(&application.into_inner(), client_ip)
.await?;
Ok(HttpResponse::Created().json(created_application))

View File

@@ -451,8 +451,14 @@ async fn test_non_owner_cannot_edit_thread(pool: PgPool) {
let pool = Arc::new(ConnectionPool::with_pg_pool(pool));
// First, login as staff user (101) and create a thread owned by them
let (service, staff_user) =
create_test_app_and_login(pool.clone(), MockRedisPool::default(), 100, 100, TestUser::Staff).await;
let (service, staff_user) = create_test_app_and_login(
pool.clone(),
MockRedisPool::default(),
100,
100,
TestUser::Staff,
)
.await;
let create_body = UserCreatedForumThread {
forum_sub_category_id: 100,
@@ -475,7 +481,8 @@ async fn test_non_owner_cannot_edit_thread(pool: PgPool) {
// Now login as a different non-staff user (100) and try to edit the staff user's thread
let (service2, standard_user) =
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Standard).await;
create_test_app_and_login(pool, MockRedisPool::default(), 100, 100, TestUser::Standard)
.await;
let edit_body = EditedForumThread {
id: thread.id, // Thread owned by user 101 (staff)

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO user_applications (body, referral, email, staff_note, status)\n VALUES ($1, $2, $3, '', 'pending')\n RETURNING id, created_at, body, email, referral, staff_note,\n status as \"status: UserApplicationStatus\"\n ",
"query": "\n INSERT INTO user_applications (body, referral, email, applied_from_ip, staff_note, status)\n VALUES ($1, $2, $3, $4, '', 'pending')\n RETURNING id, created_at, body, email, referral,\n applied_from_ip as \"applied_from_ip: IpNetwork\",\n staff_note, status as \"status: UserApplicationStatus\"\n ",
"describe": {
"columns": [
{
@@ -30,11 +30,16 @@
},
{
"ordinal": 5,
"name": "applied_from_ip: IpNetwork",
"type_info": "Inet"
},
{
"ordinal": 6,
"name": "staff_note",
"type_info": "Text"
},
{
"ordinal": 6,
"ordinal": 7,
"name": "status: UserApplicationStatus",
"type_info": {
"Custom": {
@@ -54,7 +59,8 @@
"Left": [
"Text",
"Text",
"Text"
"Text",
"Inet"
]
},
"nullable": [
@@ -64,8 +70,9 @@
false,
false,
false,
false,
false
]
},
"hash": "1f6d24cb5529ad67db890fedb4e722bd7395bc3beacfd770acaac11ea389bb5f"
"hash": "c26a5cd31c8ba876b555639c3e77d3ec99a052e7f28920a8802c096f3e687c6a"
}

View File

@@ -98,6 +98,7 @@ CREATE TABLE user_applications (
body TEXT NOT NULL,
referral TEXT NOT NULL,
email TEXT NOT NULL,
applied_from_ip INET NOT NULL,
staff_note TEXT NOT NULL DEFAULT '',
status user_application_status_enum NOT NULL DEFAULT 'pending'
);

View File

@@ -1,6 +1,6 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::prelude::FromRow;
use sqlx::{prelude::FromRow, types::ipnetwork::IpNetwork};
use strum::Display;
use utoipa::ToSchema;
@@ -23,6 +23,8 @@ pub struct UserApplication {
pub body: String,
pub email: String,
pub referral: String,
#[schema(value_type = String)]
pub applied_from_ip: IpNetwork,
pub staff_note: String,
pub status: UserApplicationStatus,
}

View File

@@ -5,24 +5,28 @@ use crate::{
},
};
use arcadia_common::error::{Error, Result};
use sqlx::types::ipnetwork::IpNetwork;
use std::borrow::Borrow;
impl ConnectionPool {
pub async fn create_user_application(
&self,
application: &UserCreatedUserApplication,
from_ip: IpNetwork,
) -> Result<UserApplication> {
let created_application = sqlx::query_as!(
UserApplication,
r#"
INSERT INTO user_applications (body, referral, email, staff_note, status)
VALUES ($1, $2, $3, '', 'pending')
RETURNING id, created_at, body, email, referral, staff_note,
status as "status: UserApplicationStatus"
INSERT INTO user_applications (body, referral, email, applied_from_ip, staff_note, status)
VALUES ($1, $2, $3, $4, '', 'pending')
RETURNING id, created_at, body, email, referral,
applied_from_ip as "applied_from_ip: IpNetwork",
staff_note, status as "status: UserApplicationStatus"
"#,
application.body,
application.referral,
application.email
application.email,
from_ip
)
.fetch_one(self.borrow())
.await
@@ -39,7 +43,7 @@ impl ConnectionPool {
) -> Result<Vec<UserApplication>> {
let query = format!(
r#"
SELECT id, created_at, body, email, referral, staff_note,
SELECT id, created_at, body, email, referral, applied_from_ip, staff_note,
status::user_application_status_enum as status
FROM user_applications ua
WHERE $1 IS NULL OR ua.status = $1::user_application_status_enum
@@ -69,7 +73,7 @@ impl ConnectionPool {
UPDATE user_applications
SET status = $2::user_application_status_enum
WHERE id = $1
RETURNING id, created_at, body, email, referral, staff_note,
RETURNING id, created_at, body, email, referral, applied_from_ip, staff_note,
status::user_application_status_enum as status
"#,
)