user info + max size
This commit is contained in:
parent
52723b200a
commit
961e14bd43
@ -1,6 +1,7 @@
|
|||||||
from ninja import NinjaAPI
|
from ninja import NinjaAPI
|
||||||
from ninja.security import django_auth
|
from ninja.security import django_auth
|
||||||
from submissions.api import router as submissions_router
|
from submissions.api import router as submissions_router
|
||||||
|
from submissions.schemas import UserInfoOut
|
||||||
|
|
||||||
# Create the main API instance
|
# Create the main API instance
|
||||||
api = NinjaAPI(
|
api = NinjaAPI(
|
||||||
@ -39,3 +40,29 @@ def api_info(request):
|
|||||||
"Admin validation tools",
|
"Admin validation tools",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# User info endpoint
|
||||||
|
@api.get("/user", response=UserInfoOut)
|
||||||
|
def get_user_info(request):
|
||||||
|
"""Get current user information"""
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
if user.is_authenticated:
|
||||||
|
return {
|
||||||
|
"id": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"first_name": user.first_name,
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"email": user.email,
|
||||||
|
"is_authenticated": True,
|
||||||
|
"is_staff": user.is_staff,
|
||||||
|
"is_superuser": user.is_superuser,
|
||||||
|
"cas_groups": getattr(user, 'cas_groups', [])
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"is_authenticated": False,
|
||||||
|
"is_staff": False,
|
||||||
|
"is_superuser": False,
|
||||||
|
}
|
||||||
|
|||||||
@ -133,24 +133,24 @@ AUTH_USER_MODEL = "accounts.CustomUser"
|
|||||||
CAS_SERVER_URL = "https://polylan.ch/cas/"
|
CAS_SERVER_URL = "https://polylan.ch/cas/"
|
||||||
|
|
||||||
# Steam API Configuration
|
# Steam API Configuration
|
||||||
STEAM_API_KEY = os.environ.get('STEAM_API_KEY', None) # Set via environment variable
|
STEAM_API_KEY = os.environ.get("STEAM_API_KEY", None) # Set via environment variable
|
||||||
|
|
||||||
# File Upload Settings
|
# File Upload Settings
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / 'media'
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
# File Upload Limits
|
# File Upload Limits
|
||||||
FILE_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
|
FILE_UPLOAD_MAX_MEMORY_SIZE = 256 * 1024 * 1024 # 256MB
|
||||||
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 256 * 1024 * 1024 # 256MB
|
||||||
|
|
||||||
# Allowed file types for submissions
|
# Allowed file types for submissions
|
||||||
ALLOWED_SUBMISSION_TYPES = [
|
ALLOWED_SUBMISSION_TYPES = [
|
||||||
'image/jpeg',
|
"image/jpeg",
|
||||||
'image/jpg',
|
"image/jpg",
|
||||||
'image/png',
|
"image/png",
|
||||||
'image/gif',
|
"image/gif",
|
||||||
'video/mp4',
|
"video/mp4",
|
||||||
'video/webm'
|
"video/webm",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Authentication backends
|
# Authentication backends
|
||||||
|
|||||||
@ -2,13 +2,15 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import PuzzleCard from './components/PuzzleCard.vue'
|
import PuzzleCard from './components/PuzzleCard.vue'
|
||||||
import SubmissionForm from './components/SubmissionForm.vue'
|
import SubmissionForm from './components/SubmissionForm.vue'
|
||||||
import { puzzleHelpers, submissionHelpers, errorHelpers } from './services/apiService'
|
import AdminPanel from './components/AdminPanel.vue'
|
||||||
import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse } from './types'
|
import { puzzleHelpers, submissionHelpers, errorHelpers, apiService } from './services/apiService'
|
||||||
|
import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse, UserInfo } from './types'
|
||||||
|
|
||||||
// API data
|
// API data
|
||||||
const collections = ref<SteamCollection[]>([])
|
const collections = ref<SteamCollection[]>([])
|
||||||
const puzzles = ref<SteamCollectionItem[]>([])
|
const puzzles = ref<SteamCollectionItem[]>([])
|
||||||
const submissions = ref<Submission[]>([])
|
const submissions = ref<Submission[]>([])
|
||||||
|
const userInfo = ref<UserInfo | null>(null)
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const showSubmissionModal = ref(false)
|
const showSubmissionModal = ref(false)
|
||||||
const error = ref<string>('')
|
const error = ref<string>('')
|
||||||
@ -92,15 +94,22 @@ const mockPuzzles: SteamCollectionItem[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const isSuperuser = computed(() => {
|
||||||
|
return userInfo.value?.is_superuser || false
|
||||||
|
})
|
||||||
|
|
||||||
// Computed property to get responses grouped by puzzle
|
// Computed property to get responses grouped by puzzle
|
||||||
const responsesByPuzzle = computed(() => {
|
const responsesByPuzzle = computed(() => {
|
||||||
const grouped: Record<number, PuzzleResponse[]> = {}
|
const grouped: Record<number, PuzzleResponse[]> = {}
|
||||||
submissions.value.forEach(submission => {
|
submissions.value.forEach(submission => {
|
||||||
submission.responses.forEach(response => {
|
submission.responses.forEach(response => {
|
||||||
if (!grouped[response.puzzle_id]) {
|
// Handle both number and object types for puzzle field
|
||||||
grouped[response.puzzle_id] = []
|
const puzzleId = typeof response.puzzle === 'number' ? response.puzzle : response.puzzle.id
|
||||||
|
if (!grouped[puzzleId]) {
|
||||||
|
grouped[puzzleId] = []
|
||||||
}
|
}
|
||||||
grouped[response.puzzle_id].push(response)
|
grouped[puzzleId].push(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return grouped
|
return grouped
|
||||||
@ -111,9 +120,23 @@ onMounted(async () => {
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
|
console.log('Starting data load...')
|
||||||
|
|
||||||
|
// Load user info
|
||||||
|
console.log('Loading user info...')
|
||||||
|
const userResponse = await apiService.getUserInfo()
|
||||||
|
if (userResponse.data) {
|
||||||
|
userInfo.value = userResponse.data
|
||||||
|
console.log('User info loaded:', userResponse.data)
|
||||||
|
} else if (userResponse.error) {
|
||||||
|
console.warn('User info error:', userResponse.error)
|
||||||
|
}
|
||||||
|
|
||||||
// Load puzzles from API
|
// Load puzzles from API
|
||||||
|
console.log('Loading puzzles...')
|
||||||
const loadedPuzzles = await puzzleHelpers.loadPuzzles()
|
const loadedPuzzles = await puzzleHelpers.loadPuzzles()
|
||||||
puzzles.value = loadedPuzzles
|
puzzles.value = loadedPuzzles
|
||||||
|
console.log('Puzzles loaded:', loadedPuzzles.length)
|
||||||
|
|
||||||
// Create mock collection from loaded puzzles for display
|
// Create mock collection from loaded puzzles for display
|
||||||
if (loadedPuzzles.length > 0) {
|
if (loadedPuzzles.length > 0) {
|
||||||
@ -129,17 +152,23 @@ onMounted(async () => {
|
|||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString()
|
updated_at: new Date().toISOString()
|
||||||
}]
|
}]
|
||||||
|
console.log('Collection created')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load existing submissions
|
// Load existing submissions
|
||||||
|
console.log('Loading submissions...')
|
||||||
const loadedSubmissions = await submissionHelpers.loadSubmissions()
|
const loadedSubmissions = await submissionHelpers.loadSubmissions()
|
||||||
submissions.value = loadedSubmissions
|
submissions.value = loadedSubmissions
|
||||||
|
console.log('Submissions loaded:', loadedSubmissions.length)
|
||||||
|
|
||||||
|
console.log('Data load complete!')
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = errorHelpers.getErrorMessage(err)
|
error.value = errorHelpers.getErrorMessage(err)
|
||||||
console.error('Failed to load data:', err)
|
console.error('Failed to load data:', err)
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
|
console.log('Loading state set to false')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -208,6 +237,17 @@ const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null =>
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-xl font-bold">Opus Magnum Puzzle Submitter</h1>
|
<h1 class="text-xl font-bold">Opus Magnum Puzzle Submitter</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div v-if="userInfo?.is_authenticated" class="flex items-center gap-2">
|
||||||
|
<div class="text-sm">
|
||||||
|
<span class="font-medium">{{ userInfo.username }}</span>
|
||||||
|
<span v-if="userInfo.is_superuser" class="badge badge-warning badge-xs ml-1">Admin</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sm text-base-content/70">
|
||||||
|
Not logged in
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -255,6 +295,11 @@ const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin Panel (only for superusers) -->
|
||||||
|
<div v-if="isSuperuser">
|
||||||
|
<AdminPanel />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Puzzles Grid -->
|
<!-- Puzzles Grid -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<PuzzleCard
|
<PuzzleCard
|
||||||
|
|||||||
@ -183,10 +183,22 @@ const loadData = async () => {
|
|||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
// Load stats
|
// Load stats (skip if endpoint doesn't exist)
|
||||||
const statsResponse = await apiService.getStats()
|
try {
|
||||||
if (statsResponse.data) {
|
const statsResponse = await apiService.getStats()
|
||||||
stats.value = statsResponse.data
|
if (statsResponse.data) {
|
||||||
|
stats.value = statsResponse.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Stats endpoint not available:', error)
|
||||||
|
// Set default stats
|
||||||
|
stats.value = {
|
||||||
|
total_submissions: 0,
|
||||||
|
total_responses: 0,
|
||||||
|
needs_validation: 0,
|
||||||
|
validated_submissions: 0,
|
||||||
|
validation_rate: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load responses needing validation
|
// Load responses needing validation
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-base-content/50">
|
<p class="text-xs text-base-content/50">
|
||||||
Supported formats: JPG, PNG, GIF (max 10MB each)
|
Supported formats: JPG, PNG, GIF (max 256MB each)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -238,9 +238,9 @@ const isValidFile = (file: File): boolean => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size (10MB limit)
|
// Check file size (256MB limit)
|
||||||
if (file.size > 10 * 1024 * 1024) {
|
if (file.size > 256 * 1024 * 1024) {
|
||||||
error.value = `${file.name} is too large (max 10MB)`
|
error.value = `${file.name} is too large (max 256MB)`
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
<span class="text-sm font-medium">Solutions ({{ responses.length }})</span>
|
<span class="text-sm font-medium">Solutions ({{ responses.length }})</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div>
|
||||||
<table class="table table-xs">
|
<table class="table table-xs">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import type {
|
|||||||
SteamCollectionItem,
|
SteamCollectionItem,
|
||||||
Submission,
|
Submission,
|
||||||
PuzzleResponse,
|
PuzzleResponse,
|
||||||
SubmissionFile
|
SubmissionFile,
|
||||||
|
UserInfo
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
// API Configuration
|
// API Configuration
|
||||||
@ -179,6 +180,11 @@ export class ApiService {
|
|||||||
async healthCheck(): Promise<ApiResponse<{ status: string; service: string }>> {
|
async healthCheck(): Promise<ApiResponse<{ status: string; service: string }>> {
|
||||||
return this.request<{ status: string; service: string }>('/health')
|
return this.request<{ status: string; service: string }>('/health')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User info
|
||||||
|
async getUserInfo(): Promise<ApiResponse<UserInfo>> {
|
||||||
|
return this.request<UserInfo>('/user')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton instance
|
// Singleton instance
|
||||||
|
|||||||
@ -73,3 +73,15 @@ export interface Submission {
|
|||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id?: number
|
||||||
|
username?: string
|
||||||
|
first_name?: string
|
||||||
|
last_name?: string
|
||||||
|
email?: string
|
||||||
|
is_authenticated: boolean
|
||||||
|
is_staff: boolean
|
||||||
|
is_superuser: boolean
|
||||||
|
cas_groups?: string[]
|
||||||
|
}
|
||||||
|
|||||||
@ -5,13 +5,13 @@ from django.db import transaction
|
|||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem
|
from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
SubmissionIn,
|
SubmissionIn,
|
||||||
SubmissionOut,
|
SubmissionOut,
|
||||||
SubmissionListOut,
|
|
||||||
PuzzleResponseOut,
|
PuzzleResponseOut,
|
||||||
ValidationIn,
|
ValidationIn,
|
||||||
SteamCollectionItemOut,
|
SteamCollectionItemOut,
|
||||||
@ -26,23 +26,24 @@ def list_puzzles(request):
|
|||||||
return SteamCollectionItem.objects.select_related("collection").all()
|
return SteamCollectionItem.objects.select_related("collection").all()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/submissions", response=List[SubmissionListOut])
|
@router.get("/submissions", response=List[SubmissionOut])
|
||||||
@paginate
|
@paginate
|
||||||
def list_submissions(request):
|
def list_submissions(request):
|
||||||
"""Get paginated list of submissions"""
|
"""Get paginated list of submissions"""
|
||||||
return Submission.objects.prefetch_related("responses").all()
|
return Submission.objects.prefetch_related(
|
||||||
|
"responses__files", "responses__puzzle"
|
||||||
|
).filter(user=request.user)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/submissions/{submission_id}", response=SubmissionOut)
|
@router.get("/submissions/{submission_id}", response=SubmissionOut)
|
||||||
def get_submission(request, submission_id: str):
|
def get_submission(request, submission_id: str):
|
||||||
"""Get detailed submission by ID"""
|
"""Get detailed submission by ID"""
|
||||||
try:
|
return get_object_or_404(
|
||||||
submission = Submission.objects.prefetch_related(
|
Submission.objects.prefetch_related(
|
||||||
"responses__files", "responses__puzzle"
|
"responses__files", "responses__puzzle"
|
||||||
).get(id=submission_id)
|
).filter(user=request.user),
|
||||||
return submission
|
id=submission_id,
|
||||||
except Submission.DoesNotExist:
|
)
|
||||||
raise Http404("Submission not found")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/submissions", response=SubmissionOut)
|
@router.post("/submissions", response=SubmissionOut)
|
||||||
@ -104,9 +105,9 @@ def create_submission(
|
|||||||
"detail": f"Invalid file type: {uploaded_file.content_type}"
|
"detail": f"Invalid file type: {uploaded_file.content_type}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate file size (10MB limit)
|
# Validate file size (256MB limit)
|
||||||
if uploaded_file.size > 10 * 1024 * 1024:
|
if uploaded_file.size > 256 * 1024 * 1024:
|
||||||
return 400, {"detail": "File too large (max 10MB)"}
|
return 400, {"detail": "File too large (max 256MB)"}
|
||||||
|
|
||||||
# Create submission file
|
# Create submission file
|
||||||
submission_file = SubmissionFile.objects.create(
|
submission_file = SubmissionFile.objects.create(
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionI
|
|||||||
# Input Schemas
|
# Input Schemas
|
||||||
class SubmissionFileIn(Schema):
|
class SubmissionFileIn(Schema):
|
||||||
"""Schema for file upload data"""
|
"""Schema for file upload data"""
|
||||||
|
|
||||||
original_filename: str
|
original_filename: str
|
||||||
content_type: str
|
content_type: str
|
||||||
ocr_data: Optional[dict] = None
|
ocr_data: Optional[dict] = None
|
||||||
@ -17,6 +18,7 @@ class SubmissionFileIn(Schema):
|
|||||||
|
|
||||||
class PuzzleResponseIn(Schema):
|
class PuzzleResponseIn(Schema):
|
||||||
"""Schema for creating a puzzle response"""
|
"""Schema for creating a puzzle response"""
|
||||||
|
|
||||||
puzzle_id: int
|
puzzle_id: int
|
||||||
puzzle_name: str
|
puzzle_name: str
|
||||||
cost: Optional[str] = None
|
cost: Optional[str] = None
|
||||||
@ -28,6 +30,7 @@ class PuzzleResponseIn(Schema):
|
|||||||
|
|
||||||
class SubmissionIn(Schema):
|
class SubmissionIn(Schema):
|
||||||
"""Schema for creating a submission"""
|
"""Schema for creating a submission"""
|
||||||
|
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
responses: List[PuzzleResponseIn]
|
responses: List[PuzzleResponseIn]
|
||||||
|
|
||||||
@ -35,18 +38,26 @@ class SubmissionIn(Schema):
|
|||||||
# Output Schemas
|
# Output Schemas
|
||||||
class SubmissionFileOut(ModelSchema):
|
class SubmissionFileOut(ModelSchema):
|
||||||
"""Schema for submission file output"""
|
"""Schema for submission file output"""
|
||||||
|
|
||||||
file_url: Optional[str]
|
file_url: Optional[str]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SubmissionFile
|
model = SubmissionFile
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'original_filename', 'file_size', 'content_type',
|
"id",
|
||||||
'ocr_processed', 'ocr_raw_data', 'ocr_error', 'created_at'
|
"original_filename",
|
||||||
|
"file_size",
|
||||||
|
"content_type",
|
||||||
|
"ocr_processed",
|
||||||
|
"ocr_raw_data",
|
||||||
|
"ocr_error",
|
||||||
|
"created_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PuzzleResponseOut(ModelSchema):
|
class PuzzleResponseOut(ModelSchema):
|
||||||
"""Schema for puzzle response output"""
|
"""Schema for puzzle response output"""
|
||||||
|
|
||||||
files: List[SubmissionFileOut]
|
files: List[SubmissionFileOut]
|
||||||
final_cost: Optional[str]
|
final_cost: Optional[str]
|
||||||
final_cycles: Optional[str]
|
final_cycles: Optional[str]
|
||||||
@ -55,15 +66,25 @@ class PuzzleResponseOut(ModelSchema):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PuzzleResponse
|
model = PuzzleResponse
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'puzzle', 'puzzle_name', 'cost', 'cycles', 'area',
|
"id",
|
||||||
'needs_manual_validation', 'ocr_confidence_score',
|
"puzzle",
|
||||||
'validated_cost', 'validated_cycles', 'validated_area',
|
"puzzle_name",
|
||||||
'created_at', 'updated_at'
|
"cost",
|
||||||
|
"cycles",
|
||||||
|
"area",
|
||||||
|
"needs_manual_validation",
|
||||||
|
"ocr_confidence_score",
|
||||||
|
"validated_cost",
|
||||||
|
"validated_cycles",
|
||||||
|
"validated_area",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SubmissionOut(ModelSchema):
|
class SubmissionOut(ModelSchema):
|
||||||
"""Schema for submission output"""
|
"""Schema for submission output"""
|
||||||
|
|
||||||
responses: List[PuzzleResponseOut]
|
responses: List[PuzzleResponseOut]
|
||||||
total_responses: int
|
total_responses: int
|
||||||
needs_validation: bool
|
needs_validation: bool
|
||||||
@ -71,15 +92,22 @@ class SubmissionOut(ModelSchema):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Submission
|
model = Submission
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user', 'notes', 'is_validated', 'validated_by',
|
"id",
|
||||||
'validated_at', 'created_at', 'updated_at'
|
"user",
|
||||||
|
"notes",
|
||||||
|
"is_validated",
|
||||||
|
"validated_by",
|
||||||
|
"validated_at",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SubmissionListOut(Schema):
|
class SubmissionListOut(Schema):
|
||||||
"""Schema for submission list output"""
|
"""Schema for submission list output"""
|
||||||
|
|
||||||
id: UUID
|
id: UUID
|
||||||
user: Optional[int]
|
# user: int
|
||||||
notes: Optional[str]
|
notes: Optional[str]
|
||||||
total_responses: int
|
total_responses: int
|
||||||
needs_validation: bool
|
needs_validation: bool
|
||||||
@ -91,6 +119,7 @@ class SubmissionListOut(Schema):
|
|||||||
# Validation Schemas
|
# Validation Schemas
|
||||||
class ValidationIn(Schema):
|
class ValidationIn(Schema):
|
||||||
"""Schema for manual validation input"""
|
"""Schema for manual validation input"""
|
||||||
|
|
||||||
validated_cost: Optional[str] = None
|
validated_cost: Optional[str] = None
|
||||||
validated_cycles: Optional[str] = None
|
validated_cycles: Optional[str] = None
|
||||||
validated_area: Optional[str] = None
|
validated_area: Optional[str] = None
|
||||||
@ -99,24 +128,49 @@ class ValidationIn(Schema):
|
|||||||
# Collection Schemas
|
# Collection Schemas
|
||||||
class SteamCollectionItemOut(ModelSchema):
|
class SteamCollectionItemOut(ModelSchema):
|
||||||
"""Schema for Steam collection item output"""
|
"""Schema for Steam collection item output"""
|
||||||
|
|
||||||
steam_url: str
|
steam_url: str
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SteamCollectionItem
|
model = SteamCollectionItem
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'steam_item_id', 'title', 'author_name', 'description',
|
"id",
|
||||||
'tags', 'order_index', 'created_at', 'updated_at'
|
"steam_item_id",
|
||||||
|
"title",
|
||||||
|
"author_name",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"order_index",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Error Schemas
|
# Error Schemas
|
||||||
class ErrorOut(Schema):
|
class ErrorOut(Schema):
|
||||||
"""Schema for error responses"""
|
"""Schema for error responses"""
|
||||||
|
|
||||||
detail: str
|
detail: str
|
||||||
code: Optional[str] = None
|
code: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class ValidationErrorOut(Schema):
|
class ValidationErrorOut(Schema):
|
||||||
"""Schema for validation error responses"""
|
"""Schema for validation error responses"""
|
||||||
|
|
||||||
detail: str
|
detail: str
|
||||||
errors: dict
|
errors: dict
|
||||||
|
|
||||||
|
|
||||||
|
# User Schemas
|
||||||
|
class UserInfoOut(Schema):
|
||||||
|
"""Schema for user information output"""
|
||||||
|
|
||||||
|
id: Optional[int] = None
|
||||||
|
username: Optional[str] = None
|
||||||
|
first_name: Optional[str] = None
|
||||||
|
last_name: Optional[str] = None
|
||||||
|
email: Optional[str] = None
|
||||||
|
is_authenticated: bool
|
||||||
|
is_staff: bool
|
||||||
|
is_superuser: bool
|
||||||
|
cas_groups: Optional[List[str]] = None
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user