from pathlib import Path from enum import Enum from typing import Optional, List, Tuple, Any import re from fastapi import Header, File, UploadFile, HTTPException, Query, Form from fastapi.responses import FileResponse from pydantic import BaseModel from .saberes import Saber, SaberesConfig from .sankofa import Sankofa from .rest import BaobaxiaAPI from .root import ( BaobaxiaError, SessionNotFound, SessionExpired ) from configparser import ConfigParser ROLE_ACERVO_EDITOR = 'acervo.editor' ROLE_ACERVO_PUBLISHER = 'acervo.publisher' class MidiaTipo(str, Enum): video = 'video' audio = 'audio' imagem = 'imagem' arquivo = 'arquivo' class MidiaStatus(str, Enum): draft = 'draft' published = 'published' class Midia(Saber): titulo: str descricao: Optional[str] = None autor: Optional[str] = None mocambo: Optional[str] = None tipo: Optional[MidiaTipo] = None status: MidiaStatus = MidiaStatus.draft tags: List[str] = [] pastas_por_tipo = { MidiaTipo.video: 'videos', MidiaTipo.audio: 'audios', MidiaTipo.imagem: 'imagens', MidiaTipo.arquivo: 'arquivos', } tipos_por_content_type = { 'application/ogg': MidiaTipo.audio, 'audio/ogg': MidiaTipo.audio, 'audio/mpeg': MidiaTipo.audio, 'image/jpeg': MidiaTipo.imagem, 'image/png': MidiaTipo.imagem, 'image/gif': MidiaTipo.imagem, 'video/ogg': MidiaTipo.video, 'video/ogv': MidiaTipo.video, 'video/avi': MidiaTipo.video, 'video/mp4': MidiaTipo.video, 'video/webm': MidiaTipo.video, 'application/pdf': MidiaTipo.arquivo, 'application/odt': MidiaTipo.arquivo, 'application/ods': MidiaTipo.arquivo, 'application/odp': MidiaTipo.arquivo, } api = BaobaxiaAPI() base_path = api.baobaxia.config.data_path / \ api.baobaxia.config.default_balaio / \ api.baobaxia.default_mucua.path acervo_path = base_path / 'acervo' if not acervo_path.exists(): acervo_path.mkdir() for tipo, pasta in pastas_por_tipo.items(): pasta_path = acervo_path / pasta if not pasta_path.exists(): pasta_path.mkdir() saberes_patterns = [] for pattern in pastas_por_tipo.values(): saberes_patterns.append('acervo/'+pattern+'/*/') api.baobaxia.discover_saberes( # balaio_slug=api.baobaxia.default_balaio, # mucua_slug=api.baobaxia.default_mucua, model=Midia, patterns=saberes_patterns, indexes_names=['tags']) api.add_saberes_api( Midia, url_path='acervo/midia', skip_put_method=True, get_summary='Retornar informações da mídia') async def post_midia(*, balaio: str, mucua: str, titulo: str = Form(...), descricao: Optional[str] = Form(None), autor: Optional[str] = Form(None), mocambo: Optional[str] = Form(None), tipo: MidiaTipo = Form(...), tags: Optional[str] = Form(None), token: str = Header(...)) -> Midia: try: mocambola = api.baobaxia.get_session_mocambola(token) if not ROLE_ACERVO_EDITOR in mocambola.roles: raise HTTPException(status_code=401, detail='Mocambola não é um editor') midia = Midia( balaio=balaio, mucua=mucua, name=titulo, path=Path('acervo') / pastas_por_tipo[tipo], titulo=titulo, descricao=descricao, autor=autor, mocambo=mocambo, tipo=tipo, tags=re.split('; |, ', tags) if tags is not None else []) return api.baobaxia.put_midia( balaio, mucua, midia, token) except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/midia', post_midia, response_model=Midia, methods=['POST'], summary='Enviar as informações de uma mídia') async def put_midia(*, balaio: str, mucua: str, path: Path, titulo: str = Form(...), descricao: Optional[str] = Form(None), autor: Optional[str] = Form(None), mocambo: Optional[str] = Form(None), tags: Optional[str] = Form(None), token: str = Header(...)) -> Midia: try: mocambola = api.baobaxia.get_session_mocambola(token) if not ROLE_ACERVO_EDITOR in mocambola.roles: raise HTTPException(status_code=401, detail='Mocambola não é um editor') midia = api.baobaxia.get_midia(balaio, mucua, path, token) if midia.status == MidiaStatus.published and not ROLE_ACERVO_PUBLISHER in mocambola.roles: raise HTTPException(status_code=401, detail='Mocambola não é um publisher') midia.titulo = titulo midia.descricao = descricao midia.autor = autor midia.mocambo = mocambo midia.tags = re.split('; |, ', tags) if tags is not None else [] return api.baobaxia.put_midia( balaio, mucua, midia, token) except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/midia/{path:path}', put_midia, response_model=Midia, methods=['PUT'], summary='Atualizar as informações de uma mídia') async def upload_midia(*, balaio: str, mucua: str, path: Path, arquivo: UploadFile = File(...), token: str = Header(...)): try: mocambola = api.baobaxia.get_session_mocambola(token) if not ROLE_ACERVO_EDITOR in mocambola.roles: raise HTTPException(status_code=401, detail='Mocambola não é um editor') saber = api.baobaxia.get_midia(balaio, mucua, path, token=token) if saber.status == MidiaStatus.published and not ROLE_ACERVO_PUBLISHER in mocambolas.roles: raise HTTPException(status_code=401, detail='Mocambola não é um publisher') if len(saber.content) == 0: saber.content.append(arquivo.filename) else: saber.content[0] = arquivo.filename with (base_path / saber.path / saber.content[0]).open( 'wb') as arquivo_saber: arquivo_saber.write(arquivo.file.read()) arquivo_saber.close() api.baobaxia.put_midia(balaio, mucua, saber, token) return {'detail': 'success'} except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/upload/{path:path}', upload_midia, response_model=dict, methods=['POST'], summary='Enviar o arquivo uma mídia já existente') async def download_midia(balaio: str, mucua: str, path: Path, token: Optional[str] = Header(None)): try: saber = api.baobaxia.get_midia(balaio, mucua, path, token=token) if len(saber.content) == 0: raise HTTPException(status_code=404, detail='Acervo não encontrado') return FileResponse(path=str(base_path / saber.path / saber.content[0])) except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/download/{path:path}', download_midia, methods=['GET'], summary='Retornar o arquivo de uma mídia') async def publish_midia(*, balaio: str, mucua: str, path: Path, token: str = Header(...)): try: mocambola = api.baobaxia.get_session_mocambola(token) if not ROLE_ACERVO_PUBLISHER in mocambola.roles: raise HTTPException(status_code=401, detail='Mocambola não é um publisher') midia = api.baobaxia.get_midia(balaio, mucua, path, token) if len(midia.content) == 0: raise HTTPException(status_code=412, detail='Nâo foi realizado upload do arquivo de mídia') midia.status = MidiaStatus.published api.baobaxia.put_midia(balaio, mucua, midia, token) return {'detail': 'success'} except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/publish/{path:path}', publish_midia, methods=['PUT'], summary='Publica uma mídia') async def find_midias(*, balaio: str, mucua: str, keywords: Optional[str] = None, hashtags: Optional[str] = Query(None), tipos: Optional[str] = Query(None), status: Optional[str] = Query(None), creator: Optional[str] = Query(None), ordem_campo: Optional[str] = None, ordem_decrescente: bool = False, pag_tamanho: int = 12, pag_atual: int = 1, token: Optional[str] = Header(None)): try: tp_list = [] if tipos is not None and len(tipos) > 0: tp_list = tipos.split(' ') kw_list = [] if keywords is not None and len(keywords) > 0: kw_list = keywords.split(' ') ht_list = [] if hashtags is not None and len(hashtags) > 0: ht_list = hashtags.split(' ') def filter_function(midia): if status is not None and midia.status != status: return False if creator is not None and midia.creator != creator: return False if len(tp_list) > 0 and midia.tipo.value not in tipos: return False match = True for kw in kw_list: if kw not in midia.titulo and kw not in midia.descricao: match = False break for ht in ht_list: if ht not in midia.tags: match = False break return match def sorted_function(midia): if ordem_campo is None: return 0 elif hasattr(midia, ordem_campo): return getattr(midia, ordem_campo) else: return 0 return api.baobaxia.find_midias( balaio, mucua, token, filter_function=filter_function, sorted_function=sorted_function, sorted_reverse=ordem_decrescente, page_size=pag_tamanho, page_index=pag_atual) except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/find', find_midias, response_model=List[Midia], methods=['GET'], summary='Busca mídias de acordo com os parâmetros fornecidos') async def get_tipos_por_content_type(): return tipos_por_content_type api.add_api_route('/{balaio}/{mucua}/acervo/tipos_por_content_type', get_tipos_por_content_type, response_model=dict, methods=['GET'], summary='Retornar os content types aceitos e ' + \ 'os tipos de mídia correspondentes para o json') class TagCounter(BaseModel): tag: str count: int async def get_top_tags(*, balaio: str, mucua: str, size: int = 10): try: api.baobaxia._check_cache(Midia, 'midia', saberes_patterns, balaio, mucua) tags = api.baobaxia.indexes[balaio][mucua]['midia']['tags'] counters = [] for tag in tags: counters.append(TagCounter( tag=tag, count=len(tags[tag]) )) return sorted(counters, key=lambda counter: counter.count, reverse=True)[:size] except (SessionNotFound, SessionExpired): raise HTTPException(status_code=401, detail="Token inválido") except Exception as e: raise HTTPException(status_code=500, detail=str(e)) api.add_api_route('/{balaio}/{mucua}/acervo/top_tags', get_top_tags, response_model=List[TagCounter], methods=['GET'], summary='Retorna as tags mais usadas') from fastapi.middleware.cors import CORSMiddleware api.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])