chore: update dependencies and improve MermaidBlock security

- Updated mermaid from version 11.13.0 to 9.1.7 for compatibility.
- Upgraded next from version 14.2.21 to ^14.2.35 for the latest features and fixes.
- Added @types/dompurify and isomorphic-dompurify for improved sanitization.
- Replaced innerHTML assignment in MermaidBlock with sanitized SVG using DOMPurify.
- Updated eslint-config-next to ^16.2.4 for better linting support.
This commit is contained in:
2026-04-28 15:15:16 -04:00
parent 567fa66428
commit 2c8bfaa20e
39 changed files with 3701 additions and 2866 deletions
Generated
+52 -51
View File
@@ -125,9 +125,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.16.2" version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
dependencies = [ dependencies = [
"aws-lc-sys", "aws-lc-sys",
"zeroize", "zeroize",
@@ -135,9 +135,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-sys" name = "aws-lc-sys"
version = "0.39.1" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
dependencies = [ dependencies = [
"cc", "cc",
"cmake", "cmake",
@@ -711,9 +711,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.1" version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@@ -723,9 +723,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]] [[package]]
name = "bytes-utils" name = "bytes-utils"
@@ -759,9 +759,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.50" version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -1107,9 +1107,9 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.5" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde_core", "serde_core",
@@ -1353,9 +1353,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.5" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]] [[package]]
name = "flate2" name = "flate2"
@@ -1521,9 +1521,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@@ -2091,9 +2091,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
@@ -2167,9 +2167,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.178" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libm" name = "libm"
@@ -2229,6 +2229,7 @@ dependencies = [
"lettre", "lettre",
"mime_guess", "mime_guess",
"rand 0.8.5", "rand 0.8.5",
"regex",
"reqwest 0.12.26", "reqwest 0.12.26",
"serde", "serde",
"serde_json", "serde_json",
@@ -2436,9 +2437,9 @@ dependencies = [
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]] [[package]]
name = "num-integer" name = "num-integer"
@@ -2478,7 +2479,7 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"chrono", "chrono",
"getrandom 0.2.16", "getrandom 0.2.17",
"http 0.2.12", "http 0.2.12",
"rand 0.8.5", "rand 0.8.5",
"reqwest 0.11.27", "reqwest 0.11.27",
@@ -2492,9 +2493,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]] [[package]]
name = "openidconnect" name = "openidconnect"
@@ -2815,9 +2816,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2839,9 +2840,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.42" version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -2905,7 +2906,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.17",
] ]
[[package]] [[package]]
@@ -3111,7 +3112,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"getrandom 0.2.16", "getrandom 0.2.17",
"libc", "libc",
"untrusted", "untrusted",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -3181,7 +3182,7 @@ dependencies = [
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki 0.103.8", "rustls-webpki 0.103.13",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -3209,9 +3210,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.13.2" version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [ dependencies = [
"zeroize", "zeroize",
] ]
@@ -3228,9 +3229,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.8" version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"ring", "ring",
@@ -3871,9 +3872,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.111" version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4012,30 +4013,30 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.44" version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
"num-conv", "num-conv",
"powerfmt", "powerfmt",
"serde", "serde_core",
"time-core", "time-core",
"time-macros", "time-macros",
] ]
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.6" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.24" version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [ dependencies = [
"num-conv", "num-conv",
"time-core", "time-core",
@@ -4308,9 +4309,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.22" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@@ -4435,9 +4436,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.1+wasi-0.2.4" version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen",
] ]
@@ -4886,9 +4887,9 @@ dependencies = [
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.46.0" version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "writeable" name = "writeable"
+7
View File
@@ -10,6 +10,10 @@ add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self' blob: https:; object-src 'none'; frame-ancestors 'self';" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self' blob: https:; object-src 'none'; frame-ancestors 'self';" always;
location /lms-api/ { location /lms-api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
rewrite ^/lms-api/(.*)$ /$1 break; rewrite ^/lms-api/(.*)$ /$1 break;
proxy_pass http://openccb-experience:3002; proxy_pass http://openccb-experience:3002;
proxy_set_header Host $host; proxy_set_header Host $host;
@@ -47,6 +51,9 @@ location /cms-api/ {
add_header Vary "Origin" always; add_header Vary "Origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always; add_header X-Content-Type-Options "nosniff" always;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
rewrite ^/cms-api/(.*)$ /$1 break; rewrite ^/cms-api/(.*)$ /$1 break;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
+39
View File
@@ -62,6 +62,9 @@ location /cms-api/ {
add_header Vary "Origin" always; add_header Vary "Origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always; add_header X-Content-Type-Options "nosniff" always;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
rewrite ^/cms-api/(.*)$ /$1 break; rewrite ^/cms-api/(.*)$ /$1 break;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
@@ -79,6 +82,10 @@ location /cms-api/ {
} }
location /lms-api/ { location /lms-api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
rewrite ^/lms-api/(.*)$ /$1 break; rewrite ^/lms-api/(.*)$ /$1 break;
proxy_pass http://openccb-experience:3002; proxy_pass http://openccb-experience:3002;
proxy_set_header Host $host; proxy_set_header Host $host;
@@ -108,6 +115,10 @@ location = /auth/login {
return 418; return 418;
} }
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -123,6 +134,10 @@ location = /auth/register {
return 418; return 418;
} }
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -143,6 +158,10 @@ location = /auth/callback {
} }
location = /auth/me { location = /auth/me {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -153,6 +172,10 @@ location = /auth/me {
} }
location /auth/sso/ { location /auth/sso/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -163,6 +186,10 @@ location /auth/sso/ {
} }
location /auth/ { location /auth/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -173,6 +200,10 @@ location /auth/ {
} }
location = /organization { location = /organization {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -183,6 +214,10 @@ location = /organization {
} }
location /organization/ { location /organization/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -193,6 +228,10 @@ location /organization/ {
} }
location = /branding { location = /branding {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
proxy_pass http://openccb-studio:3001; proxy_pass http://openccb-studio:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
+44 -36
View File
@@ -28,6 +28,7 @@ use serde_json::json;
use sqlx::PgPool; use sqlx::PgPool;
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use std::env; use std::env;
use regex;
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use uuid::Uuid; use uuid::Uuid;
@@ -605,7 +606,7 @@ pub async fn update_course(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Establecer contexto de auditoría // Establecer contexto de auditoría
sqlx::query( sqlx::query(
@@ -615,7 +616,7 @@ pub async fn update_course(
.bind(org_ctx.id.to_string()) .bind(org_ctx.id.to_string())
.execute(&mut *tx) .execute(&mut *tx)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let course = sqlx::query_as::<_, Course>( let course = sqlx::query_as::<_, Course>(
"SELECT * FROM fn_update_course($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", "SELECT * FROM fn_update_course($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
@@ -644,7 +645,7 @@ pub async fn update_course(
tx.commit() tx.commit()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(course)) Ok(Json(course))
} }
@@ -1755,7 +1756,7 @@ pub async fn get_grading_categories(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(categories)) Ok(Json(categories))
} }
@@ -1778,7 +1779,7 @@ pub async fn create_grading_category(
.bind(payload.tipo_nota_id) .bind(payload.tipo_nota_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(category)) Ok(Json(category))
} }
@@ -1815,7 +1816,7 @@ pub async fn delete_grading_category(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -2789,6 +2790,13 @@ pub async fn register(
if payload.password.len() < 8 { if payload.password.len() < 8 {
return Err((StatusCode::BAD_REQUEST, "La contraseña debe tener al menos 8 caracteres".into())); return Err((StatusCode::BAD_REQUEST, "La contraseña debe tener al menos 8 caracteres".into()));
} }
// Validar formato de email (validación básica)
let email_regex = regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Email validation error".into()))?;
if !email_regex.is_match(&payload.email) {
return Err((StatusCode::BAD_REQUEST, "Formato de email inválido".into()));
}
let password_hash = hash(payload.password, 13) let password_hash = hash(payload.password, 13)
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Hashing failed".into()))?; .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Hashing failed".into()))?;
@@ -2809,7 +2817,7 @@ pub async fn register(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let user = sqlx::query_as::<_, User>("SELECT * FROM fn_register_user($1, $2, $3, $4, $5)") let user = sqlx::query_as::<_, User>("SELECT * FROM fn_register_user($1, $2, $3, $4, $5)")
.bind(&payload.email) .bind(&payload.email)
@@ -2829,7 +2837,7 @@ pub async fn register(
tx.commit() tx.commit()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let token = create_jwt(user.id, user.organization_id, &user.role).map_err(|_| { let token = create_jwt(user.id, user.organization_id, &user.role).map_err(|_| {
( (
@@ -3041,7 +3049,7 @@ pub async fn get_course_analytics(
let analytics = res let analytics = res
.json::<CourseAnalytics>() .json::<CourseAnalytics>()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(analytics)) Ok(Json(analytics))
} }
@@ -3089,7 +3097,7 @@ pub async fn get_advanced_analytics(
let analytics = res let analytics = res
.json::<common::models::AdvancedAnalytics>() .json::<common::models::AdvancedAnalytics>()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(analytics)) Ok(Json(analytics))
} }
@@ -3120,7 +3128,7 @@ pub async fn get_lesson_heatmap(
let heatmap = res let heatmap = res
.json::<Vec<common::models::HeatmapPoint>>() .json::<Vec<common::models::HeatmapPoint>>()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(heatmap)) Ok(Json(heatmap))
} }
@@ -3164,7 +3172,7 @@ pub async fn get_audit_logs(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(logs)) Ok(Json(logs))
} }
@@ -3239,7 +3247,7 @@ pub async fn get_sso_config(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(config)) Ok(Json(config))
} }
@@ -3298,7 +3306,7 @@ pub async fn update_sso_config(
// We use fetch_all + next for slightly better error handling in this complex query // We use fetch_all + next for slightly better error handling in this complex query
let config = config let config = config
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.into_iter() .into_iter()
.next() .next()
.ok_or(( .ok_or((
@@ -3319,7 +3327,7 @@ pub async fn sso_login_init(
.bind(org_id) .bind(org_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or(( .ok_or((
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
"SSO no configurado o deshabilitado para esta organización".to_string(), "SSO no configurado o deshabilitado para esta organización".to_string(),
@@ -3351,7 +3359,7 @@ pub async fn sso_login_init(
"{}/auth/sso/callback", "{}/auth/sso/callback",
env::var("CMS_API_URL").unwrap_or_else(|_| "http://localhost:3001".to_string()) env::var("CMS_API_URL").unwrap_or_else(|_| "http://localhost:3001".to_string())
)) ))
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?, .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?,
); );
let (auth_url, csrf_token, nonce) = client let (auth_url, csrf_token, nonce) = client
@@ -3372,7 +3380,7 @@ pub async fn sso_login_init(
.bind(nonce.secret()) .bind(nonce.secret())
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(axum::response::Redirect::to(auth_url.as_str())) Ok(axum::response::Redirect::to(auth_url.as_str()))
} }
@@ -3405,7 +3413,7 @@ pub async fn sso_callback(
.bind(org_id) .bind(org_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Exchange code for token // 3. Exchange code for token
let issuer_url = IssuerUrl::new(config.issuer_url.clone()) let issuer_url = IssuerUrl::new(config.issuer_url.clone())
@@ -3413,7 +3421,7 @@ pub async fn sso_callback(
let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client) let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let client = CoreClient::from_provider_metadata( let client = CoreClient::from_provider_metadata(
provider_metadata, provider_metadata,
@@ -3425,7 +3433,7 @@ pub async fn sso_callback(
"{}/auth/sso/callback", "{}/auth/sso/callback",
env::var("CMS_API_URL").unwrap_or_else(|_| "http://localhost:3001".to_string()) env::var("CMS_API_URL").unwrap_or_else(|_| "http://localhost:3001".to_string())
)) ))
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?, .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?,
); );
let token_response = client let token_response = client
@@ -3464,7 +3472,7 @@ pub async fn sso_callback(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
"SELECT * FROM users WHERE organization_id = $1 AND lower(email) = lower($2)", "SELECT * FROM users WHERE organization_id = $1 AND lower(email) = lower($2)",
@@ -3473,7 +3481,7 @@ pub async fn sso_callback(
.bind(&email) .bind(&email)
.fetch_optional(&mut *tx) .fetch_optional(&mut *tx)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let user = match user { let user = match user {
Some(u) => u, Some(u) => u,
@@ -3491,13 +3499,13 @@ pub async fn sso_callback(
.bind("student") // Default role .bind("student") // Default role
.fetch_one(&mut *tx) .fetch_one(&mut *tx)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
} }
}; };
tx.commit() tx.commit()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 6. Generate JWT // 6. Generate JWT
let token = let token =
@@ -3849,7 +3857,7 @@ pub async fn update_user(
.fetch_one(&pool) .fetch_one(&pool)
.await .await
} }
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
log_action( log_action(
&pool, &pool,
@@ -3905,7 +3913,7 @@ pub async fn delete_user(
.execute(&pool) .execute(&pool)
.await .await
} }
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "User not found".into())); return Err((StatusCode::NOT_FOUND, "User not found".into()));
@@ -3951,7 +3959,7 @@ pub async fn get_webhooks(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(webhooks)) Ok(Json(webhooks))
} }
@@ -3979,7 +3987,7 @@ pub async fn create_webhook(
.bind(payload.secret) .bind(payload.secret)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
log_action( log_action(
&pool, &pool,
@@ -4010,7 +4018,7 @@ pub async fn delete_webhook(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Webhook not found".into())); return Err((StatusCode::NOT_FOUND, "Webhook not found".into()));
@@ -4062,7 +4070,7 @@ pub async fn export_course(
.await .await
.map_err(|e| { .map_err(|e| {
tracing::error!("Export failed: {}", e); tracing::error!("Export failed: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string())
})?; })?;
let filename = format!("course-{}.ccb", id); let filename = format!("course-{}.ccb", id);
@@ -4567,7 +4575,7 @@ pub async fn check_course_access(
.bind(user_id) .bind(user_id)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(exists) Ok(exists)
} }
@@ -4604,7 +4612,7 @@ pub async fn get_course_team(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(team)) Ok(Json(team))
} }
@@ -4633,7 +4641,7 @@ pub async fn add_team_member(
.bind(claims.sub) .bind(claims.sub)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
}; };
if !is_authorized { if !is_authorized {
@@ -4678,7 +4686,7 @@ pub async fn remove_team_member(
.bind(claims.sub) .bind(claims.sub)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
}; };
if !is_authorized && claims.sub != user_id { if !is_authorized && claims.sub != user_id {
@@ -4690,7 +4698,7 @@ pub async fn remove_team_member(
.bind(user_id) .bind(user_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
@@ -4707,7 +4715,7 @@ pub async fn create_course_preview_token(
} }
let token = create_preview_token(claims.sub, claims.org, id) let token = create_preview_token(claims.sub, claims.org, id)
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(json!({ "token": token }))) Ok(Json(json!({ "token": token })))
} }
+6 -6
View File
@@ -109,7 +109,7 @@ pub async fn retry_task(
.bind(id) .bind(id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(l) = lesson { if let Some(l) = lesson {
let pool_clone = pool.clone(); let pool_clone = pool.clone();
@@ -134,7 +134,7 @@ pub async fn retry_task(
.bind(id) .bind(id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(task) = zip_task { if let Some(task) = zip_task {
let zip_batch_id_from_metadata = task let zip_batch_id_from_metadata = task
@@ -176,7 +176,7 @@ pub async fn retry_task(
.bind(task.created_at) .bind(task.created_at)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if candidates.len() == 1 { if candidates.len() == 1 {
candidates[0].zip_batch_id candidates[0].zip_batch_id
@@ -200,7 +200,7 @@ pub async fn retry_task(
.bind(zip_batch_id) .bind(zip_batch_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if assets.is_empty() { if assets.is_empty() {
return Err(( return Err((
@@ -306,7 +306,7 @@ pub async fn cancel_task(
.bind(id) .bind(id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if lesson_result.rows_affected() > 0 { if lesson_result.rows_affected() > 0 {
return Ok(StatusCode::NO_CONTENT); return Ok(StatusCode::NO_CONTENT);
@@ -327,7 +327,7 @@ pub async fn cancel_task(
.bind(id) .bind(id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if task_result.rows_affected() > 0 { if task_result.rows_affected() > 0 {
return Ok(StatusCode::NO_CONTENT); return Ok(StatusCode::NO_CONTENT);
+12 -12
View File
@@ -408,7 +408,7 @@ pub async fn upload_asset(
data = field data = field
.bytes() .bytes()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.to_vec(); .to_vec();
} else if name == "course_id" { } else if name == "course_id" {
if let Ok(txt) = field.text().await { if let Ok(txt) = field.text().await {
@@ -447,7 +447,7 @@ pub async fn upload_asset(
// Asegurar que el directorio de subidas existe // Asegurar que el directorio de subidas existe
tokio::fs::create_dir_all("uploads") tokio::fs::create_dir_all("uploads")
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let (storage_filename, storage_path, stored_filename, stored_mimetype) = let (storage_filename, storage_path, stored_filename, stored_mimetype) =
if is_flv_media(&filename, &mimetype) { if is_flv_media(&filename, &mimetype) {
@@ -455,7 +455,7 @@ pub async fn upload_asset(
let temp_storage_path = format!("uploads/{}", temp_storage_filename); let temp_storage_path = format!("uploads/{}", temp_storage_filename);
tokio::fs::write(&temp_storage_path, data) tokio::fs::write(&temp_storage_path, data)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let final_storage_filename = format!("{}.mp4", asset_id); let final_storage_filename = format!("{}.mp4", asset_id);
let final_storage_path = format!("uploads/{}", final_storage_filename); let final_storage_path = format!("uploads/{}", final_storage_filename);
@@ -483,7 +483,7 @@ pub async fn upload_asset(
tokio::fs::write(&storage_path, data) tokio::fs::write(&storage_path, data)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
(storage_filename, storage_path, filename.clone(), mimetype.clone()) (storage_filename, storage_path, filename.clone(), mimetype.clone())
}; };
@@ -528,7 +528,7 @@ pub async fn upload_asset(
.bind(size_bytes) .bind(size_bytes)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(AssetUploadResponse { Ok(Json(AssetUploadResponse {
id: asset_id, id: asset_id,
@@ -614,7 +614,7 @@ pub async fn list_assets(
.bind(offset) .bind(offset)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(assets)) Ok(Json(assets))
} }
@@ -645,7 +645,7 @@ pub async fn list_asset_import_history(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(items)) Ok(Json(items))
} }
@@ -664,7 +664,7 @@ pub async fn delete_asset(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Activo no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Activo no encontrado".to_string()))?;
// 2. Eliminar de la base de datos // 2. Eliminar de la base de datos
@@ -672,7 +672,7 @@ pub async fn delete_asset(
.bind(id) .bind(id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Eliminar archivo físico u objeto de S3 // 3. Eliminar archivo físico u objeto de S3
let _ = delete_storage_path(&asset.storage_path).await; let _ = delete_storage_path(&asset.storage_path).await;
@@ -790,7 +790,7 @@ pub async fn ingest_asset_for_rag(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let source_kind = if asset.mimetype.starts_with("audio/") || asset.mimetype.starts_with("video/") { let source_kind = if asset.mimetype.starts_with("audio/") || asset.mimetype.starts_with("video/") {
"audio-transcription" "audio-transcription"
@@ -1435,7 +1435,7 @@ pub async fn import_assets_zip(
tokio::fs::create_dir_all("uploads") tokio::fs::create_dir_all("uploads")
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// ── Phase 2: process entries ─────────────────────────────────────────────── // ── Phase 2: process entries ───────────────────────────────────────────────
let mut imported_assets = 0usize; let mut imported_assets = 0usize;
@@ -1591,7 +1591,7 @@ pub async fn import_assets_zip(
let temp_storage_path = format!("uploads/{}", temp_storage_filename); let temp_storage_path = format!("uploads/{}", temp_storage_filename);
tokio::fs::write(&temp_storage_path, &content) tokio::fs::write(&temp_storage_path, &content)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let final_storage_filename = format!("{}.mp4", asset_id); let final_storage_filename = format!("{}.mp4", asset_id);
let final_storage_path = format!("uploads/{}", final_storage_filename); let final_storage_path = format!("uploads/{}", final_storage_filename);
@@ -74,7 +74,7 @@ pub async fn generate_question_embeddings(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let _total = questions.len(); let _total = questions.len();
let mut processed = 0; let mut processed = 0;
@@ -181,7 +181,7 @@ pub async fn regenerate_question_embedding(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()))?;
// Generar texto de la incrustación // Generar texto de la incrustación
@@ -226,7 +226,7 @@ pub async fn regenerate_question_embedding(
.bind(question_id) .bind(question_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -310,7 +310,7 @@ pub async fn semantic_search(
let results = sql_query let results = sql_query
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(results)) Ok(Json(results))
} }
@@ -348,7 +348,7 @@ pub async fn find_similar_questions(
.bind(limit) .bind(limit)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.into_iter() .into_iter()
.filter(|r| r.similarity >= threshold) .filter(|r| r.similarity >= threshold)
.collect(); .collect();
+8 -8
View File
@@ -40,7 +40,7 @@ pub async fn create_library_block(
.bind(payload.tags.as_deref()) .bind(payload.tags.as_deref())
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(block)) Ok(Json(block))
} }
@@ -98,7 +98,7 @@ pub async fn list_library_blocks(
let blocks = sql_query let blocks = sql_query
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(blocks)) Ok(Json(blocks))
} }
@@ -116,7 +116,7 @@ pub async fn get_library_block(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
match block { match block {
Some(b) => Ok(Json(b)), Some(b) => Ok(Json(b)),
@@ -137,7 +137,7 @@ pub async fn update_library_block(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if existing.is_none() { if existing.is_none() {
return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string()));
@@ -163,7 +163,7 @@ pub async fn update_library_block(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
} else { } else {
sqlx::query_as( sqlx::query_as(
r#" r#"
@@ -181,7 +181,7 @@ pub async fn update_library_block(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
}; };
Ok(Json(updated)) Ok(Json(updated))
@@ -198,7 +198,7 @@ pub async fn delete_library_block(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string()));
@@ -218,7 +218,7 @@ pub async fn increment_block_usage(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Bloque no encontrado".to_string()));
+6 -6
View File
@@ -63,7 +63,7 @@ pub async fn list_plugins(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let plugins = rows let plugins = rows
.into_iter() .into_iter()
@@ -101,7 +101,7 @@ pub async fn list_enabled_plugins(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let plugins = rows let plugins = rows
.into_iter() .into_iter()
@@ -155,7 +155,7 @@ pub async fn create_plugin(
.bind(payload.config.unwrap_or(serde_json::json!({}))) .bind(payload.config.unwrap_or(serde_json::json!({})))
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok((StatusCode::CREATED, Json(OrgPlugin { Ok((StatusCode::CREATED, Json(OrgPlugin {
id: row.get("id"), id: row.get("id"),
@@ -190,7 +190,7 @@ pub async fn update_plugin(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists { if !exists {
return Err((StatusCode::NOT_FOUND, "Plugin no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Plugin no encontrado".to_string()));
@@ -229,7 +229,7 @@ pub async fn update_plugin(
.bind(payload.enabled) .bind(payload.enabled)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(OrgPlugin { Ok(Json(OrgPlugin {
id: row.get("id"), id: row.get("id"),
@@ -262,7 +262,7 @@ pub async fn delete_plugin(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Plugin no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Plugin no encontrado".to_string()));
@@ -379,7 +379,7 @@ pub async fn create_question(
.bind(media_type.as_deref()) .bind(media_type.as_deref())
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(question)) Ok(Json(question))
} }
@@ -501,7 +501,7 @@ pub async fn list_questions(
.fetch_all(&pool) .fetch_all(&pool)
.await .await
} }
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(questions)) Ok(Json(questions))
} }
@@ -531,7 +531,7 @@ pub async fn get_question(
.await .await
.map_err(|e| match e { .map_err(|e| match e {
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()), sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()),
})?; })?;
Ok(Json(question)) Ok(Json(question))
@@ -602,7 +602,7 @@ pub async fn update_question(
.await .await
.map_err(|e| match e { .map_err(|e| match e {
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()), sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()),
})?; })?;
Ok(Json(question)) Ok(Json(question))
@@ -627,7 +627,7 @@ pub async fn delete_question(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()));
+22 -22
View File
@@ -121,7 +121,7 @@ pub async fn create_rubric(
.bind(&payload.description) .bind(&payload.description)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rubric)) Ok(Json(rubric))
} }
@@ -144,7 +144,7 @@ pub async fn list_course_rubrics(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rubrics)) Ok(Json(rubrics))
} }
@@ -167,7 +167,7 @@ pub async fn get_rubric_with_details(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?;
// Get criteria // Get criteria
@@ -182,7 +182,7 @@ pub async fn get_rubric_with_details(
.bind(rubric_id) .bind(rubric_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Get levels for each criterion // Get levels for each criterion
let mut criteria_with_levels = Vec::new(); let mut criteria_with_levels = Vec::new();
@@ -198,7 +198,7 @@ pub async fn get_rubric_with_details(
.bind(criterion.id) .bind(criterion.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
criteria_with_levels.push(CriterionWithLevels { criterion, levels }); criteria_with_levels.push(CriterionWithLevels { criterion, levels });
} }
@@ -232,7 +232,7 @@ pub async fn update_rubric(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?;
Ok(Json(rubric)) Ok(Json(rubric))
@@ -249,7 +249,7 @@ pub async fn delete_rubric(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()));
@@ -273,7 +273,7 @@ pub async fn create_criterion(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Rúbrica no encontrada".to_string()))?;
let position = payload.position.unwrap_or(0); let position = payload.position.unwrap_or(0);
@@ -292,7 +292,7 @@ pub async fn create_criterion(
.bind(position) .bind(position)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Update rubric total_points // Update rubric total_points
let _= sqlx::query( let _= sqlx::query(
@@ -306,7 +306,7 @@ pub async fn create_criterion(
.bind(rubric_id) .bind(rubric_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(criterion)) Ok(Json(criterion))
} }
@@ -338,7 +338,7 @@ pub async fn update_criterion(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?;
// Update rubric total_points if max_points changed // Update rubric total_points if max_points changed
@@ -354,7 +354,7 @@ pub async fn update_criterion(
.bind(criterion.rubric_id) .bind(criterion.rubric_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
} }
Ok(Json(criterion)) Ok(Json(criterion))
@@ -371,7 +371,7 @@ pub async fn delete_criterion(
.bind(criterion_id) .bind(criterion_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?;
let rubric_id: Uuid = criterion_row.get("rubric_id"); let rubric_id: Uuid = criterion_row.get("rubric_id");
@@ -387,7 +387,7 @@ pub async fn delete_criterion(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()));
@@ -405,7 +405,7 @@ pub async fn delete_criterion(
.bind(rubric_id) .bind(rubric_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
@@ -425,7 +425,7 @@ pub async fn create_level(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Criterio no encontrado".to_string()))?;
let position = payload.position.unwrap_or(0); let position = payload.position.unwrap_or(0);
@@ -444,7 +444,7 @@ pub async fn create_level(
.bind(position) .bind(position)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(level)) Ok(Json(level))
} }
@@ -479,7 +479,7 @@ pub async fn update_level(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Nivel no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Nivel no encontrado".to_string()))?;
Ok(Json(level)) Ok(Json(level))
@@ -505,7 +505,7 @@ pub async fn delete_level(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Nivel no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Nivel no encontrado".to_string()));
@@ -534,7 +534,7 @@ pub async fn assign_rubric_to_lesson(
.bind(rubric_id) .bind(rubric_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(lesson_rubric)) Ok(Json(lesson_rubric))
} }
@@ -550,7 +550,7 @@ pub async fn unassign_rubric_from_lesson(
.bind(rubric_id) .bind(rubric_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Vínculo lección-rúbrica no encontrado".to_string())); return Err((StatusCode::NOT_FOUND, "Vínculo lección-rúbrica no encontrado".to_string()));
@@ -578,7 +578,7 @@ pub async fn get_lesson_rubrics(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rubrics)) Ok(Json(rubrics))
} }
+2 -2
View File
@@ -109,7 +109,7 @@ pub async fn sync_sam_students(
.await .await
.map_err(|e| { .map_err(|e| {
errors.push(format!("Error al comprobar el usuario {}: {}", sam_student.email, e)); errors.push(format!("Error al comprobar el usuario {}: {}", sam_student.email, e));
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string())
}) })
.ok() .ok()
.flatten(); .flatten();
@@ -422,7 +422,7 @@ pub async fn sync_all_sam(
.await .await
.map_err(|e| { .map_err(|e| {
errors.push(format!("Error al comprobar el usuario {}: {}", sam_student.email, e)); errors.push(format!("Error al comprobar el usuario {}: {}", sam_student.email, e));
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string())
}) })
.ok() .ok()
.flatten(); .flatten();
@@ -108,7 +108,7 @@ pub async fn create_test_template(
.bind(payload.tags.as_deref()) .bind(payload.tags.as_deref())
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(template)) Ok(Json(template))
} }
@@ -198,7 +198,7 @@ pub async fn list_test_templates(
let templates = sql_query let templates = sql_query
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(templates)) Ok(Json(templates))
} }
@@ -225,7 +225,7 @@ pub async fn get_test_template(
.await .await
.map_err(|e| match e { .map_err(|e| match e {
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()), sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()),
})?; })?;
// Obtener secciones // Obtener secciones
@@ -240,7 +240,7 @@ pub async fn get_test_template(
.bind(template_id) .bind(template_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Obtener preguntas // Obtener preguntas
let questions: Vec<TestTemplateQuestion> = sqlx::query_as( let questions: Vec<TestTemplateQuestion> = sqlx::query_as(
@@ -255,7 +255,7 @@ pub async fn get_test_template(
.bind(template_id) .bind(template_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(TestTemplateWithQuestions { Ok(Json(TestTemplateWithQuestions {
template, template,
@@ -317,7 +317,7 @@ pub async fn update_test_template(
.await .await
.map_err(|e| match e { .map_err(|e| match e {
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()), sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()),
})?; })?;
Ok(Json(template)) Ok(Json(template))
@@ -341,7 +341,7 @@ pub async fn delete_test_template(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()));
@@ -367,7 +367,7 @@ pub async fn create_template_question(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists.0 { if !exists.0 {
return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()));
@@ -396,7 +396,7 @@ pub async fn create_template_question(
.bind(payload.metadata.as_ref()) .bind(payload.metadata.as_ref())
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(question)) Ok(Json(question))
} }
@@ -428,7 +428,7 @@ pub async fn delete_template_question(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists.0 { if !exists.0 {
return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()));
@@ -444,7 +444,7 @@ pub async fn delete_template_question(
.bind(template_id) .bind(template_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Pregunta no encontrada".to_string()));
@@ -470,7 +470,7 @@ pub async fn create_template_section(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists.0 { if !exists.0 {
return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()));
@@ -494,7 +494,7 @@ pub async fn create_template_section(
.bind(payload.section_data.as_ref()) .bind(payload.section_data.as_ref())
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(section)) Ok(Json(section))
} }
@@ -525,7 +525,7 @@ pub async fn delete_template_section(
.bind(template_id) .bind(template_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if result.rows_affected() == 0 { if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Sección no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Sección no encontrada".to_string()));
@@ -560,7 +560,7 @@ pub async fn apply_template_to_lesson(
.await .await
.map_err(|e| match e { .map_err(|e| match e {
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()), sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "Plantilla no encontrada".to_string()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()),
})?; })?;
// Verificar que la lección existe y pertenece a la organización // Verificar que la lección existe y pertenece a la organización
@@ -571,7 +571,7 @@ pub async fn apply_template_to_lesson(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !lesson_exists.0 { if !lesson_exists.0 {
return Err((StatusCode::NOT_FOUND, "Lección no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Lección no encontrada".to_string()));
@@ -590,7 +590,7 @@ pub async fn apply_template_to_lesson(
.bind(template_id) .bind(template_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if template_questions.is_empty() { if template_questions.is_empty() {
return Err((StatusCode::BAD_REQUEST, "La plantilla no tiene preguntas".to_string())); return Err((StatusCode::BAD_REQUEST, "La plantilla no tiene preguntas".to_string()));
@@ -647,14 +647,14 @@ pub async fn apply_template_to_lesson(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Incrementar el contador de uso de la plantilla // Incrementar el contador de uso de la plantilla
sqlx::query("SELECT increment_template_usage($1)") sqlx::query("SELECT increment_template_usage($1)")
.bind(template_id) .bind(template_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
tracing::info!( tracing::info!(
"Plantilla '{}' aplicada a la lección '{}' con {} preguntas", "Plantilla '{}' aplicada a la lección '{}' con {} preguntas",
+1
View File
@@ -35,3 +35,4 @@ hex.workspace = true
tokio-stream = "0.1" tokio-stream = "0.1"
lettre = { version = "0.11", default-features = false, features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls"] } lettre = { version = "0.11", default-features = false, features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls"] }
rand = "0.8" rand = "0.8"
regex = "1.10"
+104 -53
View File
@@ -28,6 +28,7 @@ use crate::progress_tracking::{CourseCompletionMetrics, calculate_course_complet
use serde_json::json; use serde_json::json;
use base64::Engine; use base64::Engine;
use tokio::time::{Duration, timeout}; use tokio::time::{Duration, timeout};
use regex;
// Contador simple de tokens (aproximado: 1 token ≈ 4 caracteres en inglés, ~3-5 en español) // Contador simple de tokens (aproximado: 1 token ≈ 4 caracteres en inglés, ~3-5 en español)
fn count_tokens(text: &str) -> i32 { fn count_tokens(text: &str) -> i32 {
@@ -38,6 +39,12 @@ fn count_tokens(text: &str) -> i32 {
(text.len() / 4) as i32 + 1 (text.len() / 4) as i32 + 1
} }
// Validar que OPENAI_API_KEY esté configurado cuando se use proveedor OpenAI
fn get_openai_api_key() -> Result<String, (StatusCode, String)> {
std::env::var("OPENAI_API_KEY")
.map_err(|_| (StatusCode::SERVICE_UNAVAILABLE, "OPENAI_API_KEY env var must be set".into()))
}
fn scope_rejection_message(lesson_title: &str) -> String { fn scope_rejection_message(lesson_title: &str) -> String {
format!( format!(
"Esa pregunta está fuera del tema de la lección actual \"{}\". Estoy aquí para ayudarte únicamente con esta lección. ¿Qué parte te gustaría repasar?", "Esa pregunta está fuera del tema de la lección actual \"{}\". Estoy aquí para ayudarte únicamente con esta lección. ¿Qué parte te gustaría repasar?",
@@ -147,7 +154,7 @@ pub async fn get_me(
.bind(claims.sub) .bind(claims.sub)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(user) = user { if let Some(user) = user {
return Ok(Json(UserResponse { return Ok(Json(UserResponse {
@@ -506,7 +513,7 @@ pub async fn export_course_grades(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 2. Obtener datos generales de los estudiantes // 2. Obtener datos generales de los estudiantes
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
@@ -539,7 +546,7 @@ pub async fn export_course_grades(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Obtener calificaciones detalladas por usuario/categoría // 3. Obtener calificaciones detalladas por usuario/categoría
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
@@ -564,7 +571,7 @@ pub async fn export_course_grades(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 4. Construir CSV // 4. Construir CSV
let mut csv = "Name,Email,Cohort,Progress,Overall Score".to_string(); let mut csv = "Name,Email,Cohort,Progress,Overall Score".to_string();
@@ -820,6 +827,14 @@ pub async fn register(
if payload.password.len() < 8 { if payload.password.len() < 8 {
return Err((StatusCode::BAD_REQUEST, "La contraseña debe tener al menos 8 caracteres".into())); return Err((StatusCode::BAD_REQUEST, "La contraseña debe tener al menos 8 caracteres".into()));
} }
// Validar formato de email (validación básica)
let email_regex = regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Email validation error".into()))?;
if !email_regex.is_match(&payload.email) {
return Err((StatusCode::BAD_REQUEST, "Formato de email inválido".into()));
}
let password_hash = hash(payload.password, 13).map_err(|_| { let password_hash = hash(payload.password, 13).map_err(|_| {
( (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
@@ -840,7 +855,7 @@ pub async fn register(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let organization = if let Some(org_name) = payload.organization_name { let organization = if let Some(org_name) = payload.organization_name {
sqlx::query_as::<_, Organization>( sqlx::query_as::<_, Organization>(
@@ -877,7 +892,7 @@ pub async fn register(
tx.commit() tx.commit()
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let token = create_jwt(user.id, user.organization_id, "student").map_err(|_| { let token = create_jwt(user.id, user.organization_id, "student").map_err(|_| {
( (
@@ -1857,7 +1872,7 @@ pub async fn submit_lesson_score(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let ip = headers let ip = headers
.get("x-forwarded-for") .get("x-forwarded-for")
@@ -1879,7 +1894,7 @@ pub async fn submit_lesson_score(
Some("EVENTO_DEL_SISTEMA".to_string()), Some("EVENTO_DEL_SISTEMA".to_string()),
) )
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 1. Obtener reglas de intentos de la lección // 1. Obtener reglas de intentos de la lección
let max_attempts: Option<Option<i32>> = let max_attempts: Option<Option<i32>> =
@@ -1887,7 +1902,7 @@ pub async fn submit_lesson_score(
.bind(payload.lesson_id) .bind(payload.lesson_id)
.fetch_optional(&mut *tx) .fetch_optional(&mut *tx)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if max_attempts.is_none() { if max_attempts.is_none() {
return Err((StatusCode::NOT_FOUND, "Lección no encontrada".into())); return Err((StatusCode::NOT_FOUND, "Lección no encontrada".into()));
@@ -1901,7 +1916,7 @@ pub async fn submit_lesson_score(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&mut *tx) .fetch_optional(&mut *tx)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(count) = existing_attempts { if let Some(count) = existing_attempts {
if let Some(max) = max_attempts { if let Some(max) = max_attempts {
@@ -1926,7 +1941,7 @@ pub async fn submit_lesson_score(
.bind(payload.metadata) .bind(payload.metadata)
.fetch_one(&mut *tx) .fetch_one(&mut *tx)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3.1 Sincronizar con MySQL externo si está disponible // 3.1 Sincronizar con MySQL externo si está disponible
if let Some(mysql_pool) = mysql_pool { if let Some(mysql_pool) = mysql_pool {
@@ -1991,7 +2006,7 @@ pub async fn submit_lesson_score(
tx.commit() tx.commit()
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 4. Enviar Webhooks // 4. Enviar Webhooks
let webhook_service = common::webhooks::WebhookService::new(pool.clone()); let webhook_service = common::webhooks::WebhookService::new(pool.clone());
@@ -2152,7 +2167,7 @@ pub async fn get_course_grades(
.await .await
.map_err(|e: sqlx::Error| { .map_err(|e: sqlx::Error| {
tracing::error!("Error al obtener las calificaciones del curso: {}", e); tracing::error!("Error al obtener las calificaciones del curso: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string())
})?; })?;
Ok(Json(rows)) Ok(Json(rows))
@@ -2202,7 +2217,7 @@ pub async fn get_course_analytics(
.bind(filter.cohort_id) .bind(filter.cohort_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 2. Puntaje promedio del curso (General) // 2. Puntaje promedio del curso (General)
let average_score: Option<f32> = sqlx::query_scalar( let average_score: Option<f32> = sqlx::query_scalar(
@@ -2221,7 +2236,7 @@ pub async fn get_course_analytics(
.bind(filter.cohort_id) .bind(filter.cohort_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Analítica por lección // 3. Analítica por lección
// Nota: Convertimos AVG a float4 para compatibilidad con PostgreSQL // Nota: Convertimos AVG a float4 para compatibilidad con PostgreSQL
@@ -2247,7 +2262,7 @@ pub async fn get_course_analytics(
.bind(filter.cohort_id) .bind(filter.cohort_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let lessons = rows let lessons = rows
.into_iter() .into_iter()
@@ -2290,7 +2305,7 @@ pub async fn notify_student(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
match role.as_deref() { match role.as_deref() {
Some("instructor") | Some("admin") => {} Some("instructor") | Some("admin") => {}
@@ -2305,7 +2320,7 @@ pub async fn notify_student(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !enrolled { if !enrolled {
return Err((StatusCode::NOT_FOUND, "El alumno no está inscrito en este curso".to_string())); return Err((StatusCode::NOT_FOUND, "El alumno no está inscrito en este curso".to_string()));
@@ -2322,7 +2337,7 @@ pub async fn notify_student(
.bind(&payload.link_url) .bind(&payload.link_url)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
@@ -2353,7 +2368,7 @@ pub async fn get_course_progress(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.flatten(); .flatten();
let progress_percentage = enrollment_progress let progress_percentage = enrollment_progress
@@ -2722,7 +2737,7 @@ pub async fn toggle_bookmark(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(id) = existing_id { if let Some(id) = existing_id {
// Eliminar marcador // Eliminar marcador
@@ -2730,7 +2745,7 @@ pub async fn toggle_bookmark(
.bind(id) .bind(id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} else { } else {
// Añadir marcador // Añadir marcador
@@ -2743,7 +2758,7 @@ pub async fn toggle_bookmark(
.bind(lesson_id) .bind(lesson_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::CREATED) Ok(StatusCode::CREATED)
} }
} }
@@ -2765,7 +2780,7 @@ pub async fn get_user_bookmarks(
// Wait, let's create a better filter for this. // Wait, let's create a better filter for this.
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(bookmarks)) Ok(Json(bookmarks))
} }
@@ -2797,7 +2812,7 @@ pub async fn update_user(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(UserResponse { Ok(Json(UserResponse {
id: user.id, id: user.id,
@@ -2831,7 +2846,7 @@ pub async fn get_recommendations(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 2. Obtener metadatos de la lección (títulos y etiquetas) para contexto // 2. Obtener metadatos de la lección (títulos y etiquetas) para contexto
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
@@ -2863,7 +2878,7 @@ pub async fn get_recommendations(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Preparar contexto de IA con Análisis de Habilidades // 3. Preparar contexto de IA con Análisis de Habilidades
use std::collections::HashMap; use std::collections::HashMap;
@@ -2917,6 +2932,12 @@ pub async fn get_recommendations(
// 4. Llamar a Ollama // 4. Llamar a Ollama
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
// Validar que OPENAI_API_KEY esté configurado si no estamos usando Ollama local
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
// Mantener las solicitudes de IA por debajo del tiempo de espera del proxy para que podamos devolver un JSON de respaldo en lugar de un 504. // Mantener las solicitudes de IA por debajo del tiempo de espera del proxy para que podamos devolver un JSON de respaldo en lugar de un 504.
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10)) .connect_timeout(Duration::from_secs(10))
@@ -2935,7 +2956,7 @@ pub async fn get_recommendations(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string(), "gpt-4-turbo".to_string(),
) )
}; };
@@ -3012,6 +3033,11 @@ pub async fn evaluate_audio_response(
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let (url, auth_header, model) = if provider == "local" { let (url, auth_header, model) = if provider == "local" {
let base_url = get_ai_url("OLLAMA_URL", "http://ollama:11434"); let base_url = get_ai_url("OLLAMA_URL", "http://ollama:11434");
let model = env::var("LOCAL_LLM_MODEL").unwrap_or_else(|_| "llama3.2:3b".to_string()); let model = env::var("LOCAL_LLM_MODEL").unwrap_or_else(|_| "llama3.2:3b".to_string());
@@ -3023,7 +3049,7 @@ pub async fn evaluate_audio_response(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string(), "gpt-4-turbo".to_string(),
) )
}; };
@@ -3054,7 +3080,7 @@ pub async fn evaluate_audio_response(
})) }))
.send() .send()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let ai_data: serde_json::Value = response.json().await.map_err(|e| { let ai_data: serde_json::Value = response.json().await.map_err(|e| {
( (
@@ -3126,7 +3152,7 @@ pub async fn evaluate_audio_file(
audio_data = field audio_data = field
.bytes() .bytes()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.to_vec(); .to_vec();
tracing::info!("Received audio file: {} bytes", audio_data.len()); tracing::info!("Received audio file: {} bytes", audio_data.len());
} }
@@ -3223,6 +3249,11 @@ pub async fn evaluate_audio_file(
// 2. Realizar calificación por IA // 2. Realizar calificación por IA
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let (url, auth_header, model) = if provider == "local" { let (url, auth_header, model) = if provider == "local" {
let base_url = let base_url =
env::var("LOCAL_OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string()); env::var("LOCAL_OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
@@ -3235,7 +3266,7 @@ pub async fn evaluate_audio_file(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string(), "gpt-4-turbo".to_string(),
) )
}; };
@@ -3932,6 +3963,11 @@ pub async fn get_code_hint(
} }
let provider = std::env::var("AI_PROVIDER").unwrap_or_else(|_| "local".to_string()); let provider = std::env::var("AI_PROVIDER").unwrap_or_else(|_| "local".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let (url, auth_header, model) = if provider == "local" { let (url, auth_header, model) = if provider == "local" {
@@ -3941,7 +3977,7 @@ pub async fn get_code_hint(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", std::env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4o".to_string(), "gpt-4o".to_string(),
) )
}; };
@@ -4126,6 +4162,11 @@ pub async fn chat_with_tutor(
// 2. Configurar solicitud de IA // 2. Configurar solicitud de IA
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let _client = reqwest::Client::new(); let _client = reqwest::Client::new();
// 2.1 Manejar Sesión y Memoria // 2.1 Manejar Sesión y Memoria
@@ -4286,7 +4327,7 @@ pub async fn chat_with_tutor(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string(), "gpt-4-turbo".to_string(),
) )
}; };
@@ -4579,6 +4620,11 @@ pub async fn chat_role_play(
// 6. Solicitud de IA // 6. Solicitud de IA
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let (url, auth_header, model) = if provider == "local" { let (url, auth_header, model) = if provider == "local" {
@@ -4587,7 +4633,7 @@ pub async fn chat_role_play(
(format!("{}/v1/chat/completions", base_url), "".to_string(), model) (format!("{}/v1/chat/completions", base_url), "".to_string(), model)
} else { } else {
("https://api.openai.com/v1/chat/completions".to_string(), ("https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string()) "gpt-4-turbo".to_string())
}; };
@@ -4668,7 +4714,7 @@ pub async fn get_lesson_feedback(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or(( .ok_or((
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"No se encontró calificación para esta lección".into(), "No se encontró calificación para esta lección".into(),
@@ -4717,6 +4763,11 @@ pub async fn get_lesson_feedback(
// 3. Configurar solicitud de IA // 3. Configurar solicitud de IA
let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string()); let provider = env::var("AI_PROVIDER").unwrap_or_else(|_| "openai".to_string());
let openai_api_key = if provider != "local" {
get_openai_api_key()?
} else {
String::new()
};
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let (url, auth_header, model) = if provider == "local" { let (url, auth_header, model) = if provider == "local" {
@@ -4731,7 +4782,7 @@ pub async fn get_lesson_feedback(
} else { } else {
( (
"https://api.openai.com/v1/chat/completions".to_string(), "https://api.openai.com/v1/chat/completions".to_string(),
format!("Bearer {}", env::var("OPENAI_API_KEY").unwrap_or_default()), format!("Bearer {}", openai_api_key),
"gpt-4-turbo".to_string(), "gpt-4-turbo".to_string(),
) )
}; };
@@ -5146,7 +5197,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !lesson_exists { if !lesson_exists {
return Err((StatusCode::NOT_FOUND, "Lección no encontrada".into())); return Err((StatusCode::NOT_FOUND, "Lección no encontrada".into()));
@@ -5169,7 +5220,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(payload.base_revision) .bind(payload.base_revision)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.rows_affected(); .rows_affected();
if rows_updated == 1 { if rows_updated == 1 {
@@ -5190,7 +5241,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if existing.is_none() && payload.base_revision == 0 { if existing.is_none() && payload.base_revision == 0 {
// Primer guardado // Primer guardado
@@ -5207,7 +5258,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
sqlx::query( sqlx::query(
r#" r#"
@@ -5222,7 +5273,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(user_id) .bind(user_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
return Ok(Json(UpdateCollaborativeDocResponse { return Ok(Json(UpdateCollaborativeDocResponse {
lesson_id: id, lesson_id: id,
@@ -5243,7 +5294,7 @@ pub async fn update_lesson_collaborative_doc(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let (sc, sr) = server.map(|r| (r.content, r.revision)).unwrap_or_default(); let (sc, sr) = server.map(|r| (r.content, r.revision)).unwrap_or_default();
@@ -5377,7 +5428,7 @@ pub async fn list_lesson_annotations(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rows)) Ok(Json(rows))
} }
@@ -5413,7 +5464,7 @@ pub async fn create_lesson_annotation(
.bind(payload.position_data) .bind(payload.position_data)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok((StatusCode::CREATED, Json(row))) Ok((StatusCode::CREATED, Json(row)))
} }
@@ -5460,7 +5511,7 @@ pub async fn delete_lesson_annotation(
.bind(lesson_id) .bind(lesson_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.rows_affected(); .rows_affected();
if affected == 0 { if affected == 0 {
Err((StatusCode::NOT_FOUND, "Anotación no encontrada".to_string())) Err((StatusCode::NOT_FOUND, "Anotación no encontrada".to_string()))
@@ -5484,7 +5535,7 @@ pub async fn get_my_annotations(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rows)) Ok(Json(rows))
} }
@@ -5544,7 +5595,7 @@ pub async fn assign_mentor(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
match role.as_deref() { match role.as_deref() {
Some("instructor") | Some("admin") => {} Some("instructor") | Some("admin") => {}
@@ -5569,7 +5620,7 @@ pub async fn assign_mentor(
.bind(&payload.notes) .bind(&payload.notes)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(row)) Ok(Json(row))
} }
@@ -5603,7 +5654,7 @@ pub async fn list_course_mentorships(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rows)) Ok(Json(rows))
} }
@@ -5622,7 +5673,7 @@ pub async fn delete_mentorship(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.rows_affected(); .rows_affected();
if affected == 0 { if affected == 0 {
@@ -5663,7 +5714,7 @@ pub async fn get_my_mentor(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(row)) Ok(Json(row))
} }
@@ -5699,7 +5750,7 @@ pub async fn get_my_mentees(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(rows)) Ok(Json(rows))
} }
@@ -49,7 +49,7 @@ async fn ensure_announcement_author_exists(
.bind(user_id) .bind(user_id)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if exists { if exists {
return Ok(()); return Ok(());
@@ -97,7 +97,7 @@ pub async fn list_announcements(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Adjuntar cohort_ids a cada anuncio // Adjuntar cohort_ids a cada anuncio
for a in &mut announcements { for a in &mut announcements {
@@ -107,7 +107,7 @@ pub async fn list_announcements(
.bind(a.id) .bind(a.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !cohorts.is_empty() { if !cohorts.is_empty() {
a.cohort_ids = Some(cohorts.into_iter().map(|c| c.0).collect()); a.cohort_ids = Some(cohorts.into_iter().map(|c| c.0).collect());
@@ -136,7 +136,7 @@ pub async fn create_announcement(
let mut tx = pool let mut tx = pool
.begin() .begin()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 1. Crear anuncio // 1. Crear anuncio
let mut announcement = sqlx::query_as::<_, CourseAnnouncement>( let mut announcement = sqlx::query_as::<_, CourseAnnouncement>(
@@ -152,7 +152,7 @@ pub async fn create_announcement(
.bind(payload.is_pinned.unwrap_or(false)) .bind(payload.is_pinned.unwrap_or(false))
.fetch_one(&mut *tx) .fetch_one(&mut *tx)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 2. Vincular cohortes si se proporcionan // 2. Vincular cohortes si se proporcionan
if let Some(ref cohort_ids) = payload.cohort_ids { if let Some(ref cohort_ids) = payload.cohort_ids {
@@ -164,14 +164,14 @@ pub async fn create_announcement(
.bind(cohort_id) .bind(cohort_id)
.execute(&mut *tx) .execute(&mut *tx)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
} }
announcement.cohort_ids = Some(cohort_ids.clone()); announcement.cohort_ids = Some(cohort_ids.clone());
} }
tx.commit() tx.commit()
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Obtener estudiantes objetivo para notificaciones // 3. Obtener estudiantes objetivo para notificaciones
let enrolled_students = if let Some(ref cohort_ids) = payload.cohort_ids { let enrolled_students = if let Some(ref cohort_ids) = payload.cohort_ids {
@@ -207,7 +207,7 @@ pub async fn create_announcement(
.fetch_all(&pool) .fetch_all(&pool)
.await .await
} }
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Crear notificación para cada estudiante inscrito // Crear notificación para cada estudiante inscrito
for (student_id,) in enrolled_students { for (student_id,) in enrolled_students {
@@ -276,7 +276,7 @@ pub async fn update_announcement(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(announcement)) Ok(Json(announcement))
} }
@@ -302,7 +302,7 @@ pub async fn delete_announcement(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
+8 -8
View File
@@ -20,7 +20,7 @@ pub async fn list_cohorts(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(cohorts)) Ok(Json(cohorts))
} }
@@ -43,7 +43,7 @@ pub async fn create_cohort(
.bind(payload.description) .bind(payload.description)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(cohort)) Ok(Json(cohort))
} }
@@ -63,7 +63,7 @@ pub async fn add_cohort_member(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists { if !exists {
return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string()));
@@ -81,7 +81,7 @@ pub async fn add_cohort_member(
.bind(payload.user_id) .bind(payload.user_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(member)) Ok(Json(member))
} }
@@ -100,7 +100,7 @@ pub async fn remove_cohort_member(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists { if !exists {
return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string()));
@@ -111,7 +111,7 @@ pub async fn remove_cohort_member(
.bind(user_id) .bind(user_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
@@ -130,7 +130,7 @@ pub async fn get_cohort_members(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !exists { if !exists {
return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Cohorte no encontrada".to_string()));
@@ -140,7 +140,7 @@ pub async fn get_cohort_members(
.bind(cohort_id) .bind(cohort_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(members)) Ok(Json(members))
} }
@@ -454,7 +454,7 @@ pub async fn list_threads(
let threads = sql_query let threads = sql_query
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(threads)) Ok(Json(threads))
} }
@@ -497,7 +497,7 @@ pub async fn create_thread(
.bind(&payload.content) .bind(&payload.content)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Suscribir automáticamente al autor al hilo // Suscribir automáticamente al autor al hilo
let _ = sqlx::query( let _ = sqlx::query(
@@ -654,7 +654,7 @@ fn get_thread_posts_recursive<'a>(
let mut posts = sql_query let mut posts = sql_query
.fetch_all(pool) .fetch_all(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Obtener respuestas recursivamente para cada mensaje // Obtener respuestas recursivamente para cada mensaje
for post in &mut posts { for post in &mut posts {
@@ -691,7 +691,7 @@ pub async fn pin_thread(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -721,7 +721,7 @@ pub async fn lock_thread(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -779,7 +779,7 @@ pub async fn create_post(
.bind(payload.content) .bind(payload.content)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Notificar a los suscritos al hilo (excepto autor de la respuesta) // Notificar a los suscritos al hilo (excepto autor de la respuesta)
let mut recipients = sqlx::query_as::<_, (Uuid, String, Option<String>)>( let mut recipients = sqlx::query_as::<_, (Uuid, String, Option<String>)>(
@@ -883,7 +883,7 @@ pub async fn endorse_post(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -912,7 +912,7 @@ pub async fn vote_post(
.bind(&payload.vote_type) .bind(&payload.vote_type)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Recalcular votos positivos // Recalcular votos positivos
let upvote_count: i64 = sqlx::query_scalar( let upvote_count: i64 = sqlx::query_scalar(
@@ -928,7 +928,7 @@ pub async fn vote_post(
.bind(post_id) .bind(post_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -951,7 +951,7 @@ pub async fn subscribe_thread(
.bind(claims.sub) .bind(claims.sub)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -971,7 +971,7 @@ pub async fn unsubscribe_thread(
.bind(org_ctx.id) .bind(org_ctx.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
+6 -6
View File
@@ -188,7 +188,7 @@ pub async fn forgot_password(
.bind(&email) .bind(&email)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let Some(user) = user else { let Some(user) = user else {
// Respuesta genérica — no revelar si el email existe // Respuesta genérica — no revelar si el email existe
@@ -210,7 +210,7 @@ pub async fn forgot_password(
.bind(user.id) .bind(user.id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Insertar nuevo token (expira en 1 hora) // Insertar nuevo token (expira en 1 hora)
sqlx::query( sqlx::query(
@@ -220,7 +220,7 @@ pub async fn forgot_password(
.bind(&token) .bind(&token)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Intentar enviar email (fire-and-forget en caso de error SMTP) // Intentar enviar email (fire-and-forget en caso de error SMTP)
let base_url = env::var("EXPERIENCE_URL").unwrap_or_else(|_| "https://openccb.local".to_string()); let base_url = env::var("EXPERIENCE_URL").unwrap_or_else(|_| "https://openccb.local".to_string());
@@ -283,7 +283,7 @@ pub async fn reset_password(
.bind(&token) .bind(&token)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let Some((user_id,)) = row else { let Some((user_id,)) = row else {
return Err(( return Err((
@@ -303,14 +303,14 @@ pub async fn reset_password(
.bind(user_id) .bind(user_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Marcar token como usado // Marcar token como usado
sqlx::query("UPDATE password_reset_tokens SET used_at = NOW() WHERE token = $1") sqlx::query("UPDATE password_reset_tokens SET used_at = NOW() WHERE token = $1")
.bind(&token) .bind(&token)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(MessageResponse { Ok(Json(MessageResponse {
message: "Contraseña actualizada correctamente. Ya puedes iniciar sesión.".to_string(), message: "Contraseña actualizada correctamente. Ya puedes iniciar sesión.".to_string(),
@@ -73,7 +73,7 @@ pub async fn generate_knowledge_embeddings(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let _total = entries.len(); let _total = entries.len();
let mut processed = 0; let mut processed = 0;
@@ -157,7 +157,7 @@ pub async fn regenerate_knowledge_embedding(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Entrada de la base de conocimientos no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Entrada de la base de conocimientos no encontrada".to_string()))?;
// Generar embedding // Generar embedding
@@ -180,7 +180,7 @@ pub async fn regenerate_knowledge_embedding(
.bind(entry_id) .bind(entry_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@@ -264,7 +264,7 @@ pub async fn semantic_search_knowledge(
let results = sql_query let results = sql_query
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(results)) Ok(Json(results))
} }
@@ -82,7 +82,7 @@ pub async fn list_course_lti_tools(
.bind(course_id) .bind(course_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let tools = rows let tools = rows
.into_iter() .into_iter()
@@ -139,7 +139,7 @@ pub async fn create_course_lti_tool(
.bind(payload.config.unwrap_or(serde_json::json!({}))) .bind(payload.config.unwrap_or(serde_json::json!({})))
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(( Ok((
StatusCode::CREATED, StatusCode::CREATED,
@@ -206,7 +206,7 @@ pub async fn update_course_lti_tool(
.bind(payload.config) .bind(payload.config)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?;
Ok(Json(LtiExternalTool { Ok(Json(LtiExternalTool {
@@ -236,7 +236,7 @@ pub async fn delete_course_lti_tool(
.bind(course_id) .bind(course_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if res.rows_affected() == 0 { if res.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()));
@@ -276,7 +276,7 @@ pub async fn lti_grade_passback(
.bind(tool_id) .bind(tool_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?;
let organization_id: Uuid = tool_row.get("organization_id"); let organization_id: Uuid = tool_row.get("organization_id");
@@ -323,7 +323,7 @@ pub async fn lti_grade_passback(
.bind(organization_id) .bind(organization_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !user_exists { if !user_exists {
return Err((StatusCode::UNPROCESSABLE_ENTITY, "user_id inválido para esta organización".to_string())); return Err((StatusCode::UNPROCESSABLE_ENTITY, "user_id inválido para esta organización".to_string()));
@@ -348,7 +348,7 @@ pub async fn lti_grade_passback(
.bind(organization_id) .bind(organization_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !lesson_ok { if !lesson_ok {
return Err((StatusCode::UNPROCESSABLE_ENTITY, "lesson_id no pertenece al curso".to_string())); return Err((StatusCode::UNPROCESSABLE_ENTITY, "lesson_id no pertenece al curso".to_string()));
@@ -384,7 +384,7 @@ pub async fn lti_grade_passback(
.bind(payload.metadata.clone().unwrap_or(serde_json::json!({}))) .bind(payload.metadata.clone().unwrap_or(serde_json::json!({})))
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Sincronizar con gradebook solo cuando hay lesson_id // Sincronizar con gradebook solo cuando hay lesson_id
if let Some(lesson_id) = payload.lesson_id { if let Some(lesson_id) = payload.lesson_id {
@@ -419,7 +419,7 @@ pub async fn lti_grade_passback(
.bind(metadata) .bind(metadata)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
} }
Ok(Json(LtiGradePassbackResponse { Ok(Json(LtiGradePassbackResponse {
@@ -458,7 +458,7 @@ pub async fn rotate_lti_tool_secret(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if !tool_exists { if !tool_exists {
return Err((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()));
@@ -481,7 +481,7 @@ pub async fn rotate_lti_tool_secret(
.bind(tool_id) .bind(tool_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
tracing::info!( tracing::info!(
"rotate_lti_tool_secret: rotated secret for tool {} in course {} org {}", "rotate_lti_tool_secret: rotated secret for tool {} in course {} org {}",
@@ -634,7 +634,7 @@ pub async fn lti_ags_score_passback(
.bind(tool_id) .bind(tool_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Herramienta LTI no encontrada".to_string()))?;
let client_id = config.ags_client_id.as_deref().unwrap_or(""); let client_id = config.ags_client_id.as_deref().unwrap_or("");
+2 -2
View File
@@ -20,7 +20,7 @@ pub async fn get_note(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(note)) Ok(Json(note))
} }
@@ -47,7 +47,7 @@ pub async fn save_note(
.bind(payload.content) .bind(payload.content)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(note)) Ok(Json(note))
} }
@@ -51,7 +51,7 @@ pub async fn create_payment_preference(
.bind(&course.currency) .bind(&course.currency)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 3. Llamar a la API de Mercado Pago // 3. Llamar a la API de Mercado Pago
let mp_access_token = std::env::var("MP_ACCESS_TOKEN").unwrap_or_default(); let mp_access_token = std::env::var("MP_ACCESS_TOKEN").unwrap_or_default();
@@ -125,7 +125,7 @@ pub async fn create_payment_preference(
.bind(transaction_id) .bind(transaction_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(PaymentPreferenceResponse { Ok(Json(PaymentPreferenceResponse {
preference_id, preference_id,
@@ -88,7 +88,7 @@ pub async fn get_lesson_quality_metrics(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if enrolled == 0 { if enrolled == 0 {
return Ok(Json(CourseQualityMetrics { return Ok(Json(CourseQualityMetrics {
@@ -146,7 +146,7 @@ pub async fn get_lesson_quality_metrics(
.bind(enrolled) .bind(enrolled)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let lessons = rows let lessons = rows
.into_iter() .into_iter()
@@ -211,7 +211,7 @@ pub async fn get_quiz_discrimination_index(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if rows.is_empty() { if rows.is_empty() {
return Ok(Json(CourseDiscriminationReport { return Ok(Json(CourseDiscriminationReport {
@@ -300,7 +300,7 @@ pub async fn get_curricular_suggestions(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if enrolled < 5 { if enrolled < 5 {
return Ok(Json(CurricularSuggestionsReport { return Ok(Json(CurricularSuggestionsReport {
@@ -335,7 +335,7 @@ pub async fn get_curricular_suggestions(
.bind(enrolled) .bind(enrolled)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let mut suggestions: Vec<CurricularSuggestion> = vec![]; let mut suggestions: Vec<CurricularSuggestion> = vec![];
@@ -81,7 +81,7 @@ pub async fn submit_assignment(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if let Some(_) = existing { if let Some(_) = existing {
// Actualizar entrega existente // Actualizar entrega existente
@@ -98,7 +98,7 @@ pub async fn submit_assignment(
.bind(lesson_id) .bind(lesson_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
return Ok(Json(updated)); return Ok(Json(updated));
} }
@@ -118,7 +118,7 @@ pub async fn submit_assignment(
.bind(&payload.content) .bind(&payload.content)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(submission)) Ok(Json(submission))
} }
@@ -158,7 +158,7 @@ pub async fn get_peer_review_assignment(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(submission)) Ok(Json(submission))
} }
@@ -177,7 +177,7 @@ pub async fn submit_peer_review(
.bind(payload.submission_id) .bind(payload.submission_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let submission_user_id = match submission_row { let submission_user_id = match submission_row {
Some(row) => row.get::<Uuid, _>("user_id"), Some(row) => row.get::<Uuid, _>("user_id"),
@@ -199,7 +199,7 @@ pub async fn submit_peer_review(
.bind(claims.sub) .bind(claims.sub)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if existing.is_some() { if existing.is_some() {
return Err(( return Err((
@@ -223,7 +223,7 @@ pub async fn submit_peer_review(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Recalcular nota final ponderada tras nueva revisión de par // Recalcular nota final ponderada tras nueva revisión de par
let lesson_id_for_calc: Uuid = sqlx::query_scalar( let lesson_id_for_calc: Uuid = sqlx::query_scalar(
@@ -232,7 +232,7 @@ pub async fn submit_peer_review(
.bind(payload.submission_id) .bind(payload.submission_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let _ = recalculate_final_score(&pool, payload.submission_id, lesson_id_for_calc).await; let _ = recalculate_final_score(&pool, payload.submission_id, lesson_id_for_calc).await;
@@ -258,7 +258,7 @@ pub async fn get_my_submission_feedback(
.bind(lesson_id) .bind(lesson_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(reviews)) Ok(Json(reviews))
} }
@@ -287,7 +287,7 @@ pub async fn list_lesson_submissions(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(submissions)) Ok(Json(submissions))
} }
@@ -304,7 +304,7 @@ pub async fn get_submission_reviews(
.bind(submission_id) .bind(submission_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(reviews)) Ok(Json(reviews))
} }
@@ -325,7 +325,7 @@ pub async fn get_peer_review_settings(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(settings)) Ok(Json(settings))
} }
@@ -376,7 +376,7 @@ pub async fn upsert_peer_review_settings(
.bind(auto_assign) .bind(auto_assign)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(settings)) Ok(Json(settings))
} }
@@ -400,7 +400,7 @@ pub async fn auto_assign_peer_reviews(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.unwrap_or(2); .unwrap_or(2);
// Entregar todas las submissions de esta lección // Entregar todas las submissions de esta lección
@@ -411,7 +411,7 @@ pub async fn auto_assign_peer_reviews(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let mut assignments_created: i64 = 0; let mut assignments_created: i64 = 0;
@@ -423,7 +423,7 @@ pub async fn auto_assign_peer_reviews(
.bind(sub_id) .bind(sub_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let needed = (required as i64) - existing_count; let needed = (required as i64) - existing_count;
if needed <= 0 { if needed <= 0 {
@@ -437,7 +437,7 @@ pub async fn auto_assign_peer_reviews(
.bind(sub_id) .bind(sub_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Candidatos: otros alumnos que no sean el autor y no hayan revisado ya // Candidatos: otros alumnos que no sean el autor y no hayan revisado ya
let candidates: Vec<Uuid> = submissions let candidates: Vec<Uuid> = submissions
@@ -486,7 +486,7 @@ pub async fn auto_assign_peer_reviews(
.bind(lesson_id) .bind(lesson_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(serde_json::json!({ Ok(Json(serde_json::json!({
"lesson_id": lesson_id, "lesson_id": lesson_id,
@@ -517,7 +517,7 @@ pub async fn instructor_grade_submission(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if sub_exists.is_none() { if sub_exists.is_none() {
return Err((StatusCode::NOT_FOUND, "Entrega no encontrada".to_string())); return Err((StatusCode::NOT_FOUND, "Entrega no encontrada".to_string()));
@@ -542,7 +542,7 @@ pub async fn instructor_grade_submission(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// Recalcular nota final ponderada // Recalcular nota final ponderada
recalculate_final_score(&pool, payload.submission_id, lesson_id).await?; recalculate_final_score(&pool, payload.submission_id, lesson_id).await?;
@@ -570,7 +570,7 @@ pub async fn get_my_submission(
.bind(lesson_id) .bind(lesson_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(sub)) Ok(Json(sub))
} }
@@ -594,7 +594,7 @@ async fn recalculate_final_score(
.bind(submission_id) .bind(submission_id)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.unwrap_or((70i32, 30i32, 2i32)); .unwrap_or((70i32, 30i32, 2i32));
// Promedio de revisiones de pares (no instructor) // Promedio de revisiones de pares (no instructor)
@@ -604,7 +604,7 @@ async fn recalculate_final_score(
.bind(submission_id) .bind(submission_id)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.flatten(); .flatten();
// Calificación del instructor // Calificación del instructor
@@ -614,7 +614,7 @@ async fn recalculate_final_score(
.bind(submission_id) .bind(submission_id)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.flatten(); .flatten();
// Solo calcular nota final si hay suficientes revisiones de pares O hay nota del instructor // Solo calcular nota final si hay suficientes revisiones de pares O hay nota del instructor
@@ -624,7 +624,7 @@ async fn recalculate_final_score(
.bind(submission_id) .bind(submission_id)
.fetch_one(pool) .fetch_one(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let has_enough_peers = peer_count >= required as i64; let has_enough_peers = peer_count >= required as i64;
let final_score = match (peer_avg, instructor_score, has_enough_peers) { let final_score = match (peer_avg, instructor_score, has_enough_peers) {
@@ -653,7 +653,7 @@ async fn recalculate_final_score(
.bind(submission_id) .bind(submission_id)
.execute(pool) .execute(pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// También actualizar review_count en todas las submissions del mismo lesson // También actualizar review_count en todas las submissions del mismo lesson
let _ = sqlx::query( let _ = sqlx::query(
+1 -1
View File
@@ -74,7 +74,7 @@ pub async fn track_xapi_statement(
.bind(payload.raw_statement) .bind(payload.raw_statement)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(XapiStatementResponse { Ok(Json(XapiStatementResponse {
id: statement_id, id: statement_id,
+2 -2
View File
@@ -66,7 +66,7 @@ pub async fn global_search(
.bind(limit) .bind(limit)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
for (id, title, description) in courses { for (id, title, description) in courses {
let snippet = description.map(|d| truncate(&d, 150)); let snippet = description.map(|d| truncate(&d, 150));
@@ -99,7 +99,7 @@ pub async fn global_search(
.bind(limit) .bind(limit)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
for (id, title, summary, course_id, course_title) in lessons { for (id, title, summary, course_id, course_title) in lessons {
let snippet = summary.map(|s| truncate(&s, 150)); let snippet = summary.map(|s| truncate(&s, 150));
@@ -102,7 +102,7 @@ pub async fn list_course_study_rooms(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let rooms = rows let rooms = rows
.into_iter() .into_iter()
@@ -212,7 +212,7 @@ pub async fn create_study_room(
.bind(now) .bind(now)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(( Ok((
StatusCode::CREATED, StatusCode::CREATED,
@@ -259,7 +259,7 @@ pub async fn join_study_room(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?;
if room.status == "ended" { if room.status == "ended" {
@@ -322,7 +322,7 @@ pub async fn end_study_room(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?;
// Solo el creador puede terminar la sala // Solo el creador puede terminar la sala
@@ -351,7 +351,7 @@ pub async fn end_study_room(
.bind(room_id) .bind(room_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(EndStudyRoomResponse { Ok(Json(EndStudyRoomResponse {
room_id, room_id,
@@ -373,7 +373,7 @@ pub async fn delete_study_room(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Sala no encontrada".to_string()))?;
if claims.sub != created_by { if claims.sub != created_by {
@@ -384,7 +384,7 @@ pub async fn delete_study_room(
.bind(room_id) .bind(room_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
@@ -417,7 +417,7 @@ pub async fn get_study_room_recordings(
.bind(org_ctx.id) .bind(org_ctx.id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.flatten() .flatten()
.ok_or((StatusCode::NOT_FOUND, "Sala no encontrada o sin ID BBB".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Sala no encontrada o sin ID BBB".to_string()))?;
+3 -3
View File
@@ -30,7 +30,7 @@ pub async fn get_course_meetings(
.bind(claims.org) .bind(claims.org)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(meetings)) Ok(Json(meetings))
} }
@@ -65,7 +65,7 @@ pub async fn create_meeting(
.bind(join_url) .bind(join_url)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(meeting)) Ok(Json(meeting))
} }
@@ -84,7 +84,7 @@ pub async fn delete_meeting(
.bind(claims.org) .bind(claims.org)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
+10 -10
View File
@@ -33,7 +33,7 @@ pub async fn lti_login_initiation(
.bind(&params.client_id) .bind(&params.client_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::BAD_REQUEST, "Registro LTI no encontrado".to_string()))?; .ok_or((StatusCode::BAD_REQUEST, "Registro LTI no encontrado".to_string()))?;
// 2. Generar estado y nonce // 2. Generar estado y nonce
@@ -45,7 +45,7 @@ pub async fn lti_login_initiation(
.bind(&nonce) .bind(&nonce)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
// 4. Construir URL de redirección // 4. Construir URL de redirección
let mut url = format!( let mut url = format!(
@@ -130,7 +130,7 @@ pub async fn lti_launch(
.bind(aud) .bind(aud)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Registro LTI no encontrado para emisor/audiencia".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Registro LTI no encontrado para emisor/audiencia".to_string()))?;
// 3. Validar JWT // 3. Validar JWT
@@ -143,7 +143,7 @@ pub async fn lti_launch(
.bind(&lti_claims.nonce) .bind(&lti_claims.nonce)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.rows_affected() > 0; .rows_affected() > 0;
if !nonce_exists { if !nonce_exists {
@@ -161,7 +161,7 @@ pub async fn lti_launch(
.bind(registration.organization_id) .bind(registration.organization_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
if user.is_none() { if user.is_none() {
let new_user_id = Uuid::new_v4(); let new_user_id = Uuid::new_v4();
@@ -182,7 +182,7 @@ pub async fn lti_launch(
.bind(role) .bind(role)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
user = Some(User { user = Some(User {
id: new_user_id, id: new_user_id,
@@ -228,7 +228,7 @@ pub async fn lti_launch(
.bind(&settings.data) .bind(&settings.data)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Redirect::to(&format!("{}/lti/deep-linking?token={}&dl_token={}", studio_url, token, dl_request_id))) Ok(Redirect::to(&format!("{}/lti/deep-linking?token={}&dl_token={}", studio_url, token, dl_request_id)))
} else { } else {
@@ -258,7 +258,7 @@ pub async fn lti_deep_linking_response(
.bind(dl_id) .bind(dl_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::UNAUTHORIZED, "Solicitud de DL inválida o expirada".to_string()))?; .ok_or((StatusCode::UNAUTHORIZED, "Solicitud de DL inválida o expirada".to_string()))?;
// Mapeo manual ya que no podemos usar query!/query_as! fácilmente para RETURNING sin una estructura // Mapeo manual ya que no podemos usar query!/query_as! fácilmente para RETURNING sin una estructura
@@ -274,7 +274,7 @@ pub async fn lti_deep_linking_response(
.bind(registration_id) .bind(registration_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let now = chrono::Utc::now().timestamp(); let now = chrono::Utc::now().timestamp();
let response_claims = common::models::LtiDeepLinkingResponseClaims { let response_claims = common::models::LtiDeepLinkingResponseClaims {
@@ -301,7 +301,7 @@ pub async fn lti_deep_linking_response(
&response_claims, &response_claims,
&private_key, &private_key,
) )
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(json!({ Ok(Json(json!({
"jwt": response_jwt, "jwt": response_jwt,
+5 -5
View File
@@ -18,7 +18,7 @@ pub async fn get_public_profile(
.bind(user_id) .bind(user_id)
.fetch_optional(&pool) .fetch_optional(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Usuario no encontrado".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Usuario no encontrado".to_string()))?;
let is_public: bool = user.get("is_public_profile"); let is_public: bool = user.get("is_public_profile");
@@ -44,13 +44,13 @@ pub async fn get_public_profile(
.bind(user_id) .bind(user_id)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let completed_courses: i64 = sqlx::query("SELECT COUNT(*) FROM enrollments WHERE user_id = $1 AND progress >= 100") let completed_courses: i64 = sqlx::query("SELECT COUNT(*) FROM enrollments WHERE user_id = $1 AND progress >= 100")
.bind(user_id) .bind(user_id)
.fetch_one(&pool) .fetch_one(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?
.get(0); .get(0);
Ok(Json(PublicProfile { Ok(Json(PublicProfile {
@@ -87,7 +87,7 @@ pub async fn get_my_badges(
.bind(claims.sub) .bind(claims.sub)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(Json(badges)) Ok(Json(badges))
} }
@@ -106,7 +106,7 @@ pub async fn award_badge(
.bind(payload.badge_id) .bind(payload.badge_id)
.execute(&pool) .execute(&pool)
.await .await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
Ok(StatusCode::CREATED) Ok(StatusCode::CREATED)
} }
+1 -1
View File
@@ -20,7 +20,7 @@ pub async fn get_course_dropout_risks(
} }
calculate_risks_for_course(&pool, course_id, claims.org).await calculate_risks_for_course(&pool, course_id, claims.org).await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, "Error interno del servidor".to_string()))?;
let rows = sqlx::query( let rows = sqlx::query(
r#" r#"
+1572 -1225
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -13,13 +13,15 @@
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"" "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\""
}, },
"dependencies": { "dependencies": {
"@types/dompurify": "^3.0.5",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.2.10", "framer-motion": "^11.2.10",
"isomorphic-dompurify": "^3.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.395.0", "lucide-react": "^0.395.0",
"mermaid": "^11.13.0", "mermaid": "^9.1.7",
"next": "14.2.21", "next": "^14.2.35",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -33,7 +35,7 @@
"@types/react-dom": "^18", "@types/react-dom": "^18",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.21", "eslint-config-next": "^16.2.4",
"postcss": "^8", "postcss": "^8",
"prettier": "^3.2.0", "prettier": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.5.0", "prettier-plugin-tailwindcss": "^0.5.0",
@@ -2,6 +2,7 @@
import { X, Printer, Download, Award, ShieldCheck } from "lucide-react"; import { X, Printer, Download, Award, ShieldCheck } from "lucide-react";
import { CertificateResponse } from "@/lib/api"; import { CertificateResponse } from "@/lib/api";
import DOMPurify from "isomorphic-dompurify";
interface CertificateModalProps { interface CertificateModalProps {
certificate: CertificateResponse; certificate: CertificateResponse;
@@ -49,13 +50,10 @@ export default function CertificateModal({ certificate, onClose }: CertificateMo
{/* Certificate Content Wrapper */} {/* Certificate Content Wrapper */}
<div className="flex-1 overflow-y-auto p-4 md:p-12 flex items-center justify-center bg-slate-100 dark:bg-black/40"> <div className="flex-1 overflow-y-auto p-4 md:p-12 flex items-center justify-center bg-slate-100 dark:bg-black/40">
<div className="certificate-container shadow-2xl ring-1 ring-black/5"> <div className="certificate-container shadow-2xl ring-1 ring-black/5">
{/* {/* Sanitizar HTML con DOMPurify antes de inyectar */}
We inject the HTML directly.
Note: The backend must sanitize this OR we trust our own generated template.
*/}
<div <div
className="bg-white" className="bg-white"
dangerouslySetInnerHTML={{ __html: certificate.certificate_html }} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(certificate.certificate_html) }}
/> />
</div> </div>
</div> </div>
@@ -2,6 +2,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import mermaid from "mermaid"; import mermaid from "mermaid";
import DOMPurify from "isomorphic-dompurify";
import { Block } from "@/lib/api"; import { Block } from "@/lib/api";
interface MermaidViewerProps { interface MermaidViewerProps {
@@ -29,7 +30,8 @@ export default function MermaidViewer({ block }: MermaidViewerProps) {
mermaidRef.current.innerHTML = ""; mermaidRef.current.innerHTML = "";
const { svg } = await mermaid.render(`mermaid-exp-${block.id}`, block.mermaid_code); const { svg } = await mermaid.render(`mermaid-exp-${block.id}`, block.mermaid_code);
if (mermaidRef.current) { if (mermaidRef.current) {
mermaidRef.current.innerHTML = svg; // Sanitizar SVG antes de inyectar
mermaidRef.current.innerHTML = DOMPurify.sanitize(svg);
} }
} catch (error: any) { } catch (error: any) {
console.error("Mermaid parsing error:", error); console.error("Mermaid parsing error:", error);
+1662 -1287
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -14,12 +14,14 @@
}, },
"dependencies": { "dependencies": {
"@hello-pangea/dnd": "^18.0.1", "@hello-pangea/dnd": "^18.0.1",
"@types/dompurify": "^3.0.5",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.2.10", "framer-motion": "^11.2.10",
"isomorphic-dompurify": "^3.10.0",
"lucide-react": "^0.395.0", "lucide-react": "^0.395.0",
"mermaid": "^11.13.0", "mermaid": "^9.1.7",
"next": "14.2.21", "next": "^14.2.35",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -31,7 +33,7 @@
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.21", "eslint-config-next": "^16.2.4",
"postcss": "^8", "postcss": "^8",
"prettier": "^3.2.0", "prettier": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.5.0", "prettier-plugin-tailwindcss": "^0.5.0",
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef } from "react";
import { Wand2, Loader2, Code2, Play } from "lucide-react"; import { Wand2, Loader2, Code2, Play } from "lucide-react";
import { cmsApi } from "@/lib/api"; import { cmsApi } from "@/lib/api";
import mermaid from "mermaid"; import mermaid from "mermaid";
import DOMPurify from "isomorphic-dompurify";
interface MermaidBlockProps { interface MermaidBlockProps {
id: string; id: string;
@@ -48,7 +49,8 @@ export default function MermaidBlock({
mermaidRef.current.innerHTML = ""; mermaidRef.current.innerHTML = "";
const { svg } = await mermaid.render(`mermaid-${id}`, mermaid_code); const { svg } = await mermaid.render(`mermaid-${id}`, mermaid_code);
if (mermaidRef.current) { if (mermaidRef.current) {
mermaidRef.current.innerHTML = svg; // Sanitizar SVG antes de inyectar
mermaidRef.current.innerHTML = DOMPurify.sanitize(svg);
} }
} catch (error: any) { } catch (error: any) {
console.error("Mermaid parsing error:", error); console.error("Mermaid parsing error:", error);