Materials: images with captions and video support
- markdown: new directives `:::figure assets/x.png | Подпись` and `:::video assets/x.mp4 | Подпись` rendered as figure/figcaption and a video player (works with HTML escaping on); sanitizer allows video/source/ track/figure with safe attributes only - assets: video extensions (mp4/webm/ogg) allowed, separate limits — images up to 10 MB, video up to 100 MB; correct content types - upload limits raised: web/academic multipart 120 MB per file / 200 MB per request, nginx client_max_body_size 200m (covers media inside ZIP imports) - docs: new docs/06-authoring.md (markdown, images, video, assets, limits, import semantics); import message now says "медиафайлов"
This commit is contained in:
@@ -18,13 +18,19 @@ class AssetStorage(@Value("\${lms.content.assets-dir:/data/content-assets}") roo
|
||||
data class StoredAsset(val publicName: String, val storedPath: Path, val mime: String)
|
||||
|
||||
fun store(originalName: String, bytes: ByteArray): StoredAsset {
|
||||
if (bytes.size > MAX_ASSET_BYTES) {
|
||||
throw BadRequestException("Файл $originalName слишком большой (> ${MAX_ASSET_BYTES / 1024 / 1024} МБ)")
|
||||
}
|
||||
val ext = originalName.substringAfterLast('.', "").lowercase()
|
||||
val allowed = setOf("png", "jpg", "jpeg", "gif", "svg", "webp")
|
||||
if (ext !in allowed) {
|
||||
throw BadRequestException("Недопустимый тип файла: $originalName (разрешены ${allowed.joinToString(", ")})")
|
||||
val isVideo = ext in VIDEO_EXTENSIONS
|
||||
val isImage = ext in IMAGE_EXTENSIONS
|
||||
if (!isVideo && !isImage) {
|
||||
throw BadRequestException(
|
||||
"Недопустимый тип файла: $originalName " +
|
||||
"(изображения: ${IMAGE_EXTENSIONS.joinToString(", ")}; " +
|
||||
"видео: ${VIDEO_EXTENSIONS.joinToString(", ")})",
|
||||
)
|
||||
}
|
||||
val limit = if (isVideo) MAX_VIDEO_BYTES else MAX_IMAGE_BYTES
|
||||
if (bytes.size > limit) {
|
||||
throw BadRequestException("Файл $originalName слишком большой (> ${limit / 1024 / 1024} МБ)")
|
||||
}
|
||||
val publicName = "${UUID.randomUUID()}.$ext"
|
||||
val target = rootDir.resolve(publicName).normalize()
|
||||
@@ -50,10 +56,16 @@ class AssetStorage(@Value("\${lms.content.assets-dir:/data/content-assets}") roo
|
||||
"gif" -> "image/gif"
|
||||
"svg" -> "image/svg+xml"
|
||||
"webp" -> "image/webp"
|
||||
"mp4" -> "video/mp4"
|
||||
"webm" -> "video/webm"
|
||||
"ogg", "ogv" -> "video/ogg"
|
||||
else -> "application/octet-stream"
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_ASSET_BYTES = 10 * 1024 * 1024
|
||||
val IMAGE_EXTENSIONS = setOf("png", "jpg", "jpeg", "gif", "svg", "webp")
|
||||
val VIDEO_EXTENSIONS = setOf("mp4", "webm", "ogg", "ogv")
|
||||
const val MAX_IMAGE_BYTES = 10 * 1024 * 1024 // 10 МБ на картинку
|
||||
const val MAX_VIDEO_BYTES = 100 * 1024 * 1024 // 100 МБ на видео
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ server:
|
||||
spring:
|
||||
application:
|
||||
name: academic-service
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 120MB
|
||||
max-request-size: 200MB
|
||||
datasource:
|
||||
url: ${LMS_DB_URL:jdbc:postgresql://localhost:5432/lms?currentSchema=academic}
|
||||
username: ${LMS_DB_USER:lms}
|
||||
|
||||
Reference in New Issue
Block a user