feat: Introduce AI code hinting, enforce single-tenant organization model, and add a Code Lab block component.

This commit is contained in:
2026-03-09 17:24:15 -03:00
parent b9c17ce67b
commit bde5be22e7
26 changed files with 822 additions and 1378 deletions
+16 -17
View File
@@ -2,7 +2,7 @@
use axum::{
Json,
extract::{Path, State},
extract::State,
http::StatusCode,
};
use common::models::Organization;
@@ -11,7 +11,7 @@ use serde_json::json;
use sqlx::PgPool;
use uuid::Uuid;
use super::handlers::log_action;
use super::handlers::{log_action, Org};
#[derive(Deserialize, Serialize)]
pub struct BrandingPayload {
@@ -35,8 +35,8 @@ pub struct BrandingResponse {
// Upload organization logo
pub async fn upload_organization_logo(
claims: common::auth::Claims,
Org(org_ctx): Org,
State(pool): State<PgPool>,
Path(org_id): Path<Uuid>,
mut multipart: axum::extract::Multipart,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
// Only admins can upload logos
@@ -46,7 +46,7 @@ pub async fn upload_organization_logo(
// Verify organization exists and user has access
let _ = sqlx::query_as::<_, Organization>("SELECT * FROM organizations WHERE id = $1")
.bind(org_id)
.bind(org_ctx.id)
.fetch_one(&pool)
.await
.map_err(|_| (StatusCode::NOT_FOUND, "Organization not found".into()))?;
@@ -98,7 +98,7 @@ pub async fn upload_organization_logo(
})?;
// Generate unique filename
let unique_filename = format!("{}_{}.{}", org_id, uuid::Uuid::new_v4(), ext);
let unique_filename = format!("{}_{}.{}", org_ctx.id, uuid::Uuid::new_v4(), ext);
let filepath = format!("uploads/org-logos/{}", unique_filename);
// Save file
@@ -113,7 +113,7 @@ pub async fn upload_organization_logo(
let logo_url = format!("/{}", filepath);
sqlx::query("UPDATE organizations SET logo_url = $1 WHERE id = $2")
.bind(&logo_url)
.bind(org_id)
.bind(org_ctx.id)
.execute(&pool)
.await
.map_err(|e| {
@@ -129,7 +129,7 @@ pub async fn upload_organization_logo(
claims.sub,
"UPDATE_LOGO",
"Organization",
org_id,
org_ctx.id,
json!({"logo_url": &logo_url}),
)
.await;
@@ -147,8 +147,8 @@ pub async fn upload_organization_logo(
// Upload organization favicon
pub async fn upload_organization_favicon(
claims: common::auth::Claims,
Org(org_ctx): Org,
State(pool): State<PgPool>,
Path(org_id): Path<Uuid>,
mut multipart: axum::extract::Multipart,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
// Only admins can upload favicons
@@ -158,7 +158,7 @@ pub async fn upload_organization_favicon(
// Verify organization exists and user has access
let _ = sqlx::query_as::<_, Organization>("SELECT * FROM organizations WHERE id = $1")
.bind(org_id)
.bind(org_ctx.id)
.fetch_one(&pool)
.await
.map_err(|_| (StatusCode::NOT_FOUND, "Organization not found".into()))?;
@@ -210,7 +210,7 @@ pub async fn upload_organization_favicon(
})?;
// Generate unique filename
let unique_filename = format!("{}_{}.{}", org_id, uuid::Uuid::new_v4(), ext);
let unique_filename = format!("{}_{}.{}", org_ctx.id, uuid::Uuid::new_v4(), ext);
let filepath = format!("uploads/org-favicons/{}", unique_filename);
// Save file
@@ -225,7 +225,7 @@ pub async fn upload_organization_favicon(
let favicon_url = format!("/{}", filepath);
sqlx::query("UPDATE organizations SET favicon_url = $1 WHERE id = $2")
.bind(&favicon_url)
.bind(org_id)
.bind(org_ctx.id)
.execute(&pool)
.await
.map_err(|e| {
@@ -241,7 +241,7 @@ pub async fn upload_organization_favicon(
claims.sub,
"UPDATE_FAVICON",
"Organization",
org_id,
org_ctx.id,
json!({"favicon_url": &favicon_url}),
)
.await;
@@ -259,8 +259,8 @@ pub async fn upload_organization_favicon(
// Update organization branding colors
pub async fn update_organization_branding(
claims: common::auth::Claims,
Org(org_ctx): Org,
State(pool): State<PgPool>,
Path(org_id): Path<Uuid>,
Json(payload): Json<BrandingPayload>,
) -> Result<Json<Organization>, (StatusCode, String)> {
// Only admins can update branding
@@ -310,7 +310,7 @@ pub async fn update_organization_branding(
.bind(&payload.secondary_color)
.bind(&payload.platform_name)
.bind(&payload.logo_variant)
.bind(org_id)
.bind(org_ctx.id)
.fetch_one(&pool)
.await
.map_err(|e| {
@@ -326,7 +326,7 @@ pub async fn update_organization_branding(
claims.sub,
"UPDATE_BRANDING",
"Organization",
org_id,
org_ctx.id,
json!(payload),
)
.await;
@@ -337,10 +337,9 @@ pub async fn update_organization_branding(
// Get organization branding (public endpoint)
pub async fn get_organization_branding(
State(pool): State<PgPool>,
Path(org_id): Path<Uuid>,
) -> Result<Json<BrandingResponse>, StatusCode> {
let org = sqlx::query_as::<_, Organization>("SELECT * FROM organizations WHERE id = $1")
.bind(org_id)
.bind(Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap())
.fetch_one(&pool)
.await
.map_err(|_| StatusCode::NOT_FOUND)?;