fix: update details handling in video activities to use JSON strings

This commit is contained in:
swve 2025-04-23 17:47:26 +02:00
parent 31b5104dd5
commit 260bd60c7a
4 changed files with 108 additions and 99 deletions

View file

@ -1,4 +1,4 @@
from typing import List, Optional
from typing import List
from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.db.courses.activities import ActivityCreate, ActivityRead, ActivityUpdate
from src.db.users import PublicUser
@ -113,7 +113,7 @@ async def api_create_video_activity(
request: Request,
name: str = Form(),
chapter_id: str = Form(),
details: Optional[dict] = Form(default=None),
details: str = Form(default="{}"),
current_user: PublicUser = Depends(get_current_user),
video_file: UploadFile | None = None,
db_session=Depends(get_db_session),

View file

@ -1,4 +1,5 @@
from typing import Literal, Optional
from typing import Literal
import json
from src.db.courses.courses import Course
from src.db.organizations import Organization
@ -31,7 +32,7 @@ async def create_video_activity(
current_user: PublicUser,
db_session: Session,
video_file: UploadFile | None = None,
details: Optional[dict] = None,
details: str = "{}",
):
# RBAC check
await rbac_check(request, "activity_x", current_user, "create", db_session)
@ -40,6 +41,9 @@ async def create_video_activity(
statement = select(Chapter).where(Chapter.id == chapter_id)
chapter = db_session.exec(statement).first()
# convert details to dict
details = json.loads(details)
if not chapter:
raise HTTPException(
status_code=404,
@ -146,7 +150,7 @@ class ExternalVideo(BaseModel):
uri: str
type: Literal["youtube", "vimeo"]
chapter_id: str
details: Optional[dict] = None
details: str = "{}"
class ExternalVideoInDB(BaseModel):
@ -184,6 +188,9 @@ async def create_external_video_activity(
# generate activity_uuid
activity_uuid = str(f"activity_{uuid4()}")
# convert details to dict
details = json.loads(data.details)
activity_object = Activity(
name=data.name,
activity_type=ActivityTypeEnum.TYPE_VIDEO,
@ -197,7 +204,7 @@ async def create_external_video_activity(
"type": data.type,
"activity_uuid": activity_uuid,
},
details=data.details,
details=details,
version=1,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),

View file

@ -1,11 +1,10 @@
import FormLayout, {
ButtonBlack,
Flex,
FormField,
FormLabel,
FormMessage,
Input,
} from '@components/Objects/StyledElements/Form/Form'
import {
Button,
} from "@components/ui/button"
import {
Input
} from "@components/ui/input"
import { Label } from "@components/ui/label"
import React, { useState } from 'react'
import * as Form from '@radix-ui/react-form'
import BarLoader from 'react-spinners/BarLoader'
@ -100,10 +99,10 @@ function VideoModal({
<div className="space-y-4 mt-4 p-4 bg-gray-50 rounded-lg">
<h3 className="font-medium text-gray-900 mb-3">Video Settings</h3>
<div className="grid grid-cols-2 gap-4">
<FormField name="start-time">
<FormLabel>Start Time (seconds)</FormLabel>
<Form.Control asChild>
<div>
<Label htmlFor="start-time">Start Time (seconds)</Label>
<Input
id="start-time"
type="number"
min="0"
value={videoDetails.startTime}
@ -113,13 +112,12 @@ function VideoModal({
})}
placeholder="0"
/>
</Form.Control>
</FormField>
</div>
<FormField name="end-time">
<FormLabel>End Time (seconds, optional)</FormLabel>
<Form.Control asChild>
<div>
<Label htmlFor="end-time">End Time (seconds, optional)</Label>
<Input
id="end-time"
type="number"
min={videoDetails.startTime + 1}
value={videoDetails.endTime || ''}
@ -129,8 +127,7 @@ function VideoModal({
})}
placeholder="Leave empty for full duration"
/>
</Form.Control>
</FormField>
</div>
</div>
<div className="flex items-center space-x-6 mt-4">
@ -164,24 +161,18 @@ function VideoModal({
)
return (
<FormLayout onSubmit={handleSubmit}>
<FormField name="video-activity-name">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Activity Name</FormLabel>
<FormMessage match="valueMissing">
Please provide a name for your video activity
</FormMessage>
</Flex>
<Form.Control asChild>
<Form.Root onSubmit={handleSubmit}>
<div>
<Label htmlFor="video-activity-name">Activity Name</Label>
<Input
id="video-activity-name"
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
required
placeholder="Enter activity name..."
/>
</Form.Control>
</FormField>
</div>
<div className="mt-4 rounded-lg border border-gray-200">
<div className="grid grid-cols-2 gap-0">
@ -214,10 +205,11 @@ function VideoModal({
<div className="p-6">
{selectedView === 'file' && (
<div className="space-y-4">
<FormField name="video-activity-file">
<FormLabel>Video File</FormLabel>
<div>
<Label htmlFor="video-activity-file">Video File</Label>
<div className="mt-2">
<input
id="video-activity-file"
type="file"
accept={SUPPORTED_FILES}
onChange={handleVideoChange}
@ -225,34 +217,36 @@ function VideoModal({
className="w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-black file:text-white hover:file:bg-gray-800"
/>
</div>
</FormField>
</div>
<VideoSettingsForm />
</div>
)}
{selectedView === 'youtube' && (
<div className="space-y-4">
<FormField name="youtube-url">
<FormLabel>YouTube URL</FormLabel>
<Form.Control asChild>
<div>
<Label htmlFor="youtube-url">YouTube URL</Label>
<Input
id="youtube-url"
value={youtubeUrl}
onChange={(e) => setYoutubeUrl(e.target.value)}
type="text"
required
placeholder="https://youtube.com/watch?v=..."
/>
</Form.Control>
</FormField>
</div>
<VideoSettingsForm />
</div>
)}
</div>
</div>
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
<Form.Submit asChild>
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
<div className="flex justify-end mt-6">
<Button
type="submit"
disabled={isSubmitting}
className="bg-black text-white hover:bg-black/90"
>
{isSubmitting ? (
<BarLoader
cssOverride={{ borderRadius: 60 }}
@ -262,10 +256,9 @@ function VideoModal({
) : (
'Create Activity'
)}
</ButtonBlack>
</Form.Submit>
</Flex>
</FormLayout>
</Button>
</div>
</Form.Root>
)
}

View file

@ -75,14 +75,23 @@ export async function createExternalVideoActivity(
data.chapter_id = chapter_id
data.activity_id = activity.id
// Add video details if provided
data.details = {
startTime: data.startTime || 0,
endTime: data.endTime || null,
autoplay: data.autoplay || false,
muted: data.muted || false
// Add video details with null checking
const defaultDetails = {
startTime: 0,
endTime: null,
autoplay: false,
muted: false
}
const videoDetails = data.details ? {
startTime: data.details.startTime ?? defaultDetails.startTime,
endTime: data.details.endTime ?? defaultDetails.endTime,
autoplay: data.details.autoplay ?? defaultDetails.autoplay,
muted: data.details.muted ?? defaultDetails.muted
} : defaultDetails
data.details = JSON.stringify(videoDetails)
const result = await fetch(
`${getAPIUrl()}activities/external_video`,
RequestBodyWithAuthHeader('POST', data, null, access_token)