alterações gerais
This commit is contained in:
@ -3,4 +3,7 @@
|
||||
USE_DOCKER=yes
|
||||
IPYTHONDIR=/app/.ipython
|
||||
ELASTICSEARCH_USER=elastic
|
||||
ELASTICSEARCH_PASSWORD=Euamooelasticsearch123.
|
||||
ELASTICSEARCH_PASSWORD=RwtC6t+BLWkLj44=cp-*
|
||||
DJANGO_SECRET_KEY=tYdYl0MP5zgpMlMmjBuYHvH4Dp3JDN5q3sxWBdFejemZSr0qpI9IrvrvTm17F0aW
|
||||
DJANGO_ADMIN_URL=manage-panel/
|
||||
DJANGO_ALLOWED_HOSTS=192.168.235.234,localhost,127.0.0.1,django
|
||||
|
||||
1
backup.json
Normal file
1
backup.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,4 @@
|
||||
# FROM docker.io/python:3.12.9-slim-bookworm AS python # Linha removida, já definida abaixo
|
||||
|
||||
# Python build stage - Mantenha apenas dependências de BUILD aqui
|
||||
FROM docker.io/python:3.12.9-slim-bookworm AS python-build-stage
|
||||
|
||||
ARG BUILD_ENVIRONMENT=local
|
||||
@ -44,9 +42,10 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-por \
|
||||
ghostscript \
|
||||
openjdk-17-jdk \
|
||||
# libtesseract-dev \
|
||||
# Utilitários do devcontainer e outros
|
||||
sudo git bash-completion vim ssh \
|
||||
sudo git bash-completion vim ssh curl \
|
||||
# Limpeza
|
||||
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@ -4,7 +4,17 @@ set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
|
||||
# Aplica migrações antes de iniciar o servidor
|
||||
python manage.py migrate
|
||||
|
||||
exec python manage.py runserver_plus 0.0.0.0:8005
|
||||
# Coleta arquivos estáticos
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
python manage.py compress --force
|
||||
|
||||
# Inicia o servidor com Gunicorn
|
||||
exec gunicorn config.wsgi:application \
|
||||
--bind 0.0.0.0:8005 \
|
||||
--workers 4 \
|
||||
--timeout 120
|
||||
# python manage.py runserver_plus 0.0.0.0:8005
|
||||
|
||||
44
compose/local/nginx/nginx.conf
Normal file
44
compose/local/nginx/nginx.conf
Normal file
@ -0,0 +1,44 @@
|
||||
upstream django {
|
||||
server django:8005;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name 192.168.235.234 localhost;
|
||||
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://django;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect off;
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
alias /app/staticfiles/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
alias /app/media/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
# Bloqueia acesso a arquivos ocultos
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
# define an alias for the specific python version used in this file.
|
||||
FROM docker.io/python:3.12.9-slim-bookworm AS python
|
||||
|
||||
@ -9,77 +8,66 @@ ARG BUILD_ENVIRONMENT=production
|
||||
|
||||
# Install apt packages
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
# dependencies for building Python packages
|
||||
build-essential \
|
||||
# psycopg dependencies
|
||||
libpq-dev
|
||||
|
||||
libpq-dev \
|
||||
wait-for-it \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Requirements are installed here to ensure they will be cached.
|
||||
COPY ./requirements .
|
||||
|
||||
# Create Python Dependency and Sub-Dependency Wheels.
|
||||
RUN pip wheel --wheel-dir /usr/src/app/wheels \
|
||||
RUN pip wheel --wheel-dir /usr/src/app/wheels \
|
||||
-r ${BUILD_ENVIRONMENT}.txt
|
||||
|
||||
|
||||
# Python 'run' stage
|
||||
FROM python AS python-run-stage
|
||||
|
||||
ARG BUILD_ENVIRONMENT=production
|
||||
ARG APP_HOME=/app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV BUILD_ENV=${BUILD_ENVIRONMENT}
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
BUILD_ENV=${BUILD_ENVIRONMENT} \
|
||||
PATH="/home/django/.local/bin:${PATH}"
|
||||
|
||||
WORKDIR ${APP_HOME}
|
||||
|
||||
RUN addgroup --system django \
|
||||
&& adduser --system --ingroup django django
|
||||
|
||||
# Create system user
|
||||
RUN addgroup --system django && \
|
||||
adduser --system --ingroup django django
|
||||
|
||||
# Install required system dependencies
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
# psycopg dependencies
|
||||
libpq-dev \
|
||||
# Translations dependencies
|
||||
gettext \
|
||||
# entrypoint
|
||||
wait-for-it \
|
||||
# cleaning up unused files
|
||||
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction
|
||||
# copy python dependency wheels from python-build-stage
|
||||
COPY --from=python-build-stage /usr/src/app/wheels /wheels/
|
||||
# Copy python dependency wheels
|
||||
COPY --from=python-build-stage /usr/src/app/wheels /wheels/
|
||||
|
||||
# use wheels to install python dependencies
|
||||
# Install python dependencies
|
||||
RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \
|
||||
&& rm -rf /wheels/
|
||||
|
||||
|
||||
# Copy entrypoint and start scripts
|
||||
COPY --chown=django:django ./compose/production/django/entrypoint /entrypoint
|
||||
RUN sed -i 's/\r$//g' /entrypoint
|
||||
RUN chmod +x /entrypoint
|
||||
|
||||
RUN sed -i 's/\r$//g' /entrypoint && \
|
||||
chmod +x /entrypoint
|
||||
|
||||
COPY --chown=django:django ./compose/production/django/start /start
|
||||
RUN sed -i 's/\r$//g' /start
|
||||
RUN chmod +x /start
|
||||
RUN sed -i 's/\r$//g' /start && \
|
||||
chmod +x /start
|
||||
|
||||
|
||||
# copy application code to WORKDIR
|
||||
# Copy application code
|
||||
COPY --chown=django:django . ${APP_HOME}
|
||||
|
||||
# make django owner of the WORKDIR directory as well.
|
||||
RUN chown -R django:django ${APP_HOME}
|
||||
# Fix permissions
|
||||
RUN chown -R django:django ${APP_HOME} && \
|
||||
find ${APP_HOME} -type d -exec chmod 755 {} \; && \
|
||||
find ${APP_HOME} -type f -exec chmod 644 {} \;
|
||||
|
||||
USER django
|
||||
|
||||
RUN DATABASE_URL="" \
|
||||
DJANGO_SETTINGS_MODULE="config.settings.test" \
|
||||
python manage.py compilemessages
|
||||
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
|
||||
@ -1,29 +1,51 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
# Configurações ajustáveis via variáveis de ambiente
|
||||
PORT=${GUNICORN_PORT:-8005}
|
||||
WORKERS=${GUNICORN_WORKERS:-$(( $(nproc) * 2 + 1 ))}
|
||||
TIMEOUT=${GUNICORN_TIMEOUT:-120}
|
||||
MAX_REQUESTS=${GUNICORN_MAX_REQUESTS:-1000}
|
||||
|
||||
# Aplica migrações do banco de dados (com tratamento de erro)
|
||||
python /app/manage.py migrate --noinput || {
|
||||
echo "⚠️ Falha nas migrações do banco de dados!";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Coleta arquivos estáticos
|
||||
python /app/manage.py collectstatic --noinput
|
||||
|
||||
# Verifica e comprime arquivos (se compressor ativado)
|
||||
compress_enabled() {
|
||||
python << END
|
||||
python << END
|
||||
import sys
|
||||
|
||||
from environ import Env
|
||||
|
||||
env = Env(COMPRESS_ENABLED=(bool, True))
|
||||
if env('COMPRESS_ENABLED'):
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0 if env('COMPRESS_ENABLED') else 1)
|
||||
END
|
||||
}
|
||||
|
||||
if compress_enabled; then
|
||||
# NOTE this command will fail if django-compressor is disabled
|
||||
python /app/manage.py compress
|
||||
python /app/manage.py compress --verbosity=0 || {
|
||||
echo "⚠️ Falha ao comprimir arquivos (django-compressor pode estar desativado)";
|
||||
}
|
||||
fi
|
||||
exec /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
|
||||
|
||||
# Inicia o Gunicorn com configurações otimizadas
|
||||
exec /usr/local/bin/gunicorn config.wsgi:application \
|
||||
--bind 0.0.0.0:${PORT} \
|
||||
--workers ${WORKERS} \
|
||||
--timeout ${TIMEOUT} \
|
||||
--max-requests ${MAX_REQUESTS} \
|
||||
--worker-class sync \
|
||||
--name diarios_oficiais_alems \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
--chdir=/app
|
||||
|
||||
|
||||
39
compose/production/nginx/nginx.conf
Normal file
39
compose/production/nginx/nginx.conf
Normal file
@ -0,0 +1,39 @@
|
||||
worker_processes auto;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 180;
|
||||
gzip on;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://django:8005;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
alias /usr/share/nginx/static/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
alias /usr/share/nginx/media/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
config/asgi.py
Normal file
13
config/asgi.py
Normal file
@ -0,0 +1,13 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
|
||||
sys.path.append(str(BASE_DIR / "diarios_oficiais_alems"))
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
||||
|
||||
application = get_asgi_application()
|
||||
|
||||
@ -19,7 +19,7 @@ if READ_DOT_ENV_FILE:
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||
DEBUG = env.bool("DJANGO_DEBUG", False)
|
||||
DEBUG = env.bool("DJANGO_DEBUG", True)
|
||||
# Local time zone. Choices are
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# though not all of them may be available with every OS.
|
||||
@ -67,7 +67,7 @@ DJANGO_APPS = [
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# "django.contrib.humanize", # Handy template tags
|
||||
"django.contrib.humanize",
|
||||
"django.contrib.admin",
|
||||
"django.forms",
|
||||
]
|
||||
@ -137,9 +137,8 @@ MIDDLEWARE = [
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
@ -210,11 +209,28 @@ FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),)
|
||||
# SECURITY
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
|
||||
CSRF_COOKIE_HTTPONLY = True
|
||||
CSRF_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_HTTPONLY = False
|
||||
CSRF_COOKIE_SAMESITE = "Lax"
|
||||
SESSION_COOKIE_SECURE = False
|
||||
SESSION_COOKIE_SAMESITE = "Lax"
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
'http://192.168.235.234:8005',
|
||||
'http://192.168.235.234:80',
|
||||
'http://192.168.235.234',
|
||||
'http://localhost:8005',
|
||||
'http://localhost:80',
|
||||
'http://localhost',
|
||||
'http://127.0.0.1:8005',
|
||||
'http://127.0.0.1:80',
|
||||
'http://127.0.0.1',
|
||||
]
|
||||
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'http')
|
||||
|
||||
# EMAIL
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -229,7 +245,7 @@ EMAIL_TIMEOUT = 5
|
||||
# ADMIN
|
||||
# ------------------------------------------------------------------------------
|
||||
# Django Admin URL.
|
||||
ADMIN_URL = "admin/"
|
||||
ADMIN_URL = "manage-panel/"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
||||
ADMINS = [("""Antonio Roberto""", "antonio-roberto@example.com")]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
||||
@ -267,7 +283,7 @@ REDIS_SSL = REDIS_URL.startswith("rediss://")
|
||||
|
||||
# django-allauth
|
||||
# ------------------------------------------------------------------------------
|
||||
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
ACCOUNT_ALLOW_REGISTRATION = False
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_LOGIN_METHODS = {"username"}
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
@ -349,6 +365,7 @@ ELASTICSEARCH_INDEX_SETTINGS = {
|
||||
}
|
||||
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://192.168.235.234",
|
||||
"http://109.199.98.226:8006",
|
||||
"http://109.199.98.226:8005",
|
||||
"http://localhost:8006",
|
||||
|
||||
@ -14,7 +14,7 @@ SECRET_KEY = env(
|
||||
default="tYdYl0MP5zgpMlMmjBuYHvH4Dp3JDN5q3sxWBdFejemZSr0qpI9IrvrvTm17F0aW",
|
||||
)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "109.199.98.226"] # noqa: S104
|
||||
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "192.168.235.234"] # noqa: S104
|
||||
|
||||
# CACHES
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@ -10,7 +10,7 @@ from .base import env
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||
SECRET_KEY = env("DJANGO_SECRET_KEY")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["example.com"])
|
||||
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[])
|
||||
|
||||
# DATABASES
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -36,7 +36,7 @@ CACHES = {
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
|
||||
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
|
||||
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=False)
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
|
||||
SESSION_COOKIE_SECURE = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-name
|
||||
|
||||
@ -10,8 +10,8 @@ from .api import api
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
||||
path('', include('diarios.urls')),
|
||||
# path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
||||
path("", include('diarios.urls')),
|
||||
path(
|
||||
"about/",
|
||||
TemplateView.as_view(template_name="pages/about.html"),
|
||||
@ -23,8 +23,6 @@ urlpatterns = [
|
||||
# User management
|
||||
path("users/", include("diarios_oficiais_alems.users.urls", namespace="users")),
|
||||
# path("accounts/", include("allauth.urls")),
|
||||
# Your stuff: custom urls includes go here
|
||||
path("", include("diarios.urls")),
|
||||
# Media files
|
||||
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
|
||||
]
|
||||
|
||||
@ -128,7 +128,7 @@ class PageDiarioOficialAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "diario_link", "numero", "conteudo_resumido")
|
||||
list_display_links = ('id', 'numero')
|
||||
list_filter = ("diario__tipo", "diario__data", "layout_duas_colunas")
|
||||
search_fields = ("conteudo", "diario__numero")
|
||||
search_fields = ("diario__numero",)
|
||||
readonly_fields = (
|
||||
"diario_link",
|
||||
)
|
||||
|
||||
179
diarios/crud_service.py
Normal file
179
diarios/crud_service.py
Normal file
@ -0,0 +1,179 @@
|
||||
from typing import Optional, List
|
||||
from datetime import date
|
||||
from diarios.models import DiarioOficial, TipoDiarioOficial
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from ninja.errors import HttpError
|
||||
|
||||
|
||||
class DiarioOficialService:
|
||||
@staticmethod
|
||||
def criar_diario(
|
||||
data: date,
|
||||
numero: str,
|
||||
tipo_id: Optional[int] = None,
|
||||
arquivo=None,
|
||||
link: Optional[str] = None
|
||||
) -> DiarioOficial:
|
||||
"""
|
||||
Cria um novo diário oficial
|
||||
|
||||
Args:
|
||||
data: Data do diário
|
||||
numero: Número do diário
|
||||
tipo_id: ID do tipo de diário
|
||||
arquivo: Arquivo PDF (opcional)
|
||||
link: URL do PDF (opcional)
|
||||
|
||||
Returns:
|
||||
DiarioOficial: O diário criado
|
||||
|
||||
Raises:
|
||||
HttpError: Se ocorrer algum erro na criação
|
||||
"""
|
||||
try:
|
||||
tipo = TipoDiarioOficial.objects.get(pk=tipo_id) if tipo_id else None
|
||||
|
||||
diario = DiarioOficial(
|
||||
data=data,
|
||||
numero=numero,
|
||||
tipo=tipo,
|
||||
link=link
|
||||
)
|
||||
|
||||
if arquivo:
|
||||
diario.arquivo = arquivo
|
||||
|
||||
diario.full_clean()
|
||||
diario.save()
|
||||
return diario
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
raise HttpError(404, "Tipo de diário não encontrado")
|
||||
except Exception as e:
|
||||
raise HttpError(400, f"Erro ao criar diário: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
def obter_diario_por_id(id: int) -> DiarioOficial:
|
||||
"""
|
||||
Obtém um diário pelo ID
|
||||
|
||||
Args:
|
||||
id: ID do diário
|
||||
|
||||
Returns:
|
||||
DiarioOficial: O diário encontrado
|
||||
|
||||
Raises:
|
||||
HttpError: Se o diário não for encontrado
|
||||
"""
|
||||
try:
|
||||
return DiarioOficial.objects.get(pk=id)
|
||||
except ObjectDoesNotExist:
|
||||
raise HttpError(404, "Diário não encontrado")
|
||||
|
||||
@staticmethod
|
||||
def listar_diarios(
|
||||
tipo_id: Optional[int] = None,
|
||||
data_inicio: Optional[date] = None,
|
||||
data_fim: Optional[date] = None,
|
||||
numero: Optional[str] = None
|
||||
) -> List[DiarioOficial]:
|
||||
"""
|
||||
Lista diários com filtros opcionais
|
||||
|
||||
Args:
|
||||
tipo_id: ID do tipo de diário para filtrar
|
||||
data_inicio: Data inicial para filtrar
|
||||
data_fim: Data final para filtrar
|
||||
numero: Número do diário para filtrar
|
||||
|
||||
Returns:
|
||||
List[DiarioOficial]: Lista de diários filtrados
|
||||
"""
|
||||
queryset = DiarioOficial.objects.all().order_by('-data')
|
||||
|
||||
if tipo_id:
|
||||
queryset = queryset.filter(tipo_id=tipo_id)
|
||||
|
||||
if data_inicio and data_fim:
|
||||
queryset = queryset.filter(data__range=[data_inicio, data_fim])
|
||||
elif data_inicio:
|
||||
queryset = queryset.filter(data__gte=data_inicio)
|
||||
elif data_fim:
|
||||
queryset = queryset.filter(data__lte=data_fim)
|
||||
|
||||
if numero:
|
||||
queryset = queryset.filter(numero__icontains=numero)
|
||||
|
||||
return list(queryset)
|
||||
|
||||
@staticmethod
|
||||
def atualizar_diario(
|
||||
id: int,
|
||||
data: Optional[date] = None,
|
||||
numero: Optional[str] = None,
|
||||
tipo_id: Optional[int] = None,
|
||||
arquivo=None,
|
||||
link: Optional[str] = None
|
||||
) -> DiarioOficial:
|
||||
"""
|
||||
Atualiza um diário existente
|
||||
|
||||
Args:
|
||||
id: ID do diário a ser atualizado
|
||||
data: Nova data (opcional)
|
||||
numero: Novo número (opcional)
|
||||
tipo_id: Novo tipo (opcional)
|
||||
arquivo: Novo arquivo (opcional)
|
||||
link: Novo link (opcional)
|
||||
|
||||
Returns:
|
||||
DiarioOficial: O diário atualizado
|
||||
|
||||
Raises:
|
||||
HttpError: Se ocorrer algum erro na atualização
|
||||
"""
|
||||
try:
|
||||
diario = DiarioOficial.objects.get(pk=id)
|
||||
|
||||
if data is not None:
|
||||
diario.data = data
|
||||
|
||||
if numero is not None:
|
||||
diario.numero = numero
|
||||
|
||||
if tipo_id is not None:
|
||||
tipo = TipoDiarioOficial.objects.get(pk=tipo_id) if tipo_id else None
|
||||
diario.tipo = tipo
|
||||
|
||||
if arquivo is not None:
|
||||
diario.arquivo = arquivo
|
||||
|
||||
if link is not None:
|
||||
diario.link = link
|
||||
|
||||
diario.full_clean()
|
||||
diario.save()
|
||||
return diario
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
raise HttpError(404, "Diário ou tipo não encontrado")
|
||||
except Exception as e:
|
||||
raise HttpError(400, f"Erro ao atualizar diário: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
def deletar_diario(id: int) -> None:
|
||||
"""
|
||||
Remove um diário
|
||||
|
||||
Args:
|
||||
id: ID do diário a ser removido
|
||||
|
||||
Raises:
|
||||
HttpError: Se o diário não for encontrado
|
||||
"""
|
||||
try:
|
||||
diario = DiarioOficial.objects.get(pk=id)
|
||||
diario.delete()
|
||||
except ObjectDoesNotExist:
|
||||
raise HttpError(404, "Diário não encontrado")
|
||||
@ -3,19 +3,16 @@ from urllib.parse import urlparse
|
||||
import requests
|
||||
from babel.dates import format_date
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.core.exceptions import ValidationError
|
||||
import PyPDF2
|
||||
import fitz # PyMuPDF
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
|
||||
class TipoDiarioOficial(models.Model):
|
||||
"""Representa um tipo de Diário Oficial (e.g., Municipal, Estadual, Federal)."""
|
||||
|
||||
nome = models.CharField(max_length=100, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
"""Retorna o nome do tipo de Diário Oficial."""
|
||||
return self.nome
|
||||
|
||||
class Meta:
|
||||
@ -23,16 +20,6 @@ class TipoDiarioOficial(models.Model):
|
||||
|
||||
|
||||
class DiarioOficial(models.Model):
|
||||
"""Modelo que representa um Diário Oficial, contendo data, arquivo PDF, tipo e link.
|
||||
|
||||
Attributes:
|
||||
data (DateField): Data de publicação do Diário Oficial.
|
||||
arquivo (FileField): Arquivo PDF do Diário Oficial (opcional).
|
||||
tipo (ForeignKey): Tipo do Diário Oficial (Municipal, Estadual, etc.).
|
||||
numero (CharField): Número de identificação único do Diário.
|
||||
link (URLField): URL para o Diário Oficial (opcional).
|
||||
"""
|
||||
|
||||
data = models.DateField()
|
||||
arquivo = models.FileField(upload_to="diarios_oficiais/", blank=True, null=True)
|
||||
tipo = models.ForeignKey(
|
||||
@ -46,34 +33,30 @@ class DiarioOficial(models.Model):
|
||||
link = models.URLField(blank=True, null=True, unique=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Salva o Diário Oficial, baixa o PDF (se houver link) e extrai páginas."""
|
||||
updated = False
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if self.link and not self.arquivo:
|
||||
self._download_pdf_from_link()
|
||||
updated = True
|
||||
|
||||
if self.arquivo and not self.paginas:
|
||||
if self.arquivo and not self.paginas.exists():
|
||||
self._extract_pdf_pages()
|
||||
updated = True
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
if updated:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
"""Valida o modelo antes de salvar (chamado automaticamente no admin/form)."""
|
||||
super().clean()
|
||||
if not self.arquivo and not self.link:
|
||||
raise ValidationError("Informe um arquivo ou um link para o Diário.")
|
||||
|
||||
def _validar_link(self):
|
||||
"""Verifica se o link é um PDF válido."""
|
||||
if not self.link.lower().endswith(".pdf"):
|
||||
raise ValidationError("O link deve apontar para um arquivo PDF.")
|
||||
|
||||
def _download_pdf_from_link(self):
|
||||
"""Faz download do PDF a partir do link e salva no campo `arquivo`.
|
||||
|
||||
Raises:
|
||||
ValidationError: Se o download falhar.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(self.link)
|
||||
response.raise_for_status()
|
||||
@ -86,55 +69,73 @@ class DiarioOficial(models.Model):
|
||||
raise ValidationError(f"Não foi possível baixar o PDF: {e}")
|
||||
|
||||
def _extract_pdf_pages(self):
|
||||
"""Extrai o texto de cada página do PDF e salva no modelo `PageDiarioOficial`.
|
||||
|
||||
Raises:
|
||||
ValidationError: Se a extração falhar.
|
||||
"""
|
||||
try:
|
||||
# Salvar temporariamente o PDF para abrir com o PyMuPDF
|
||||
with self.arquivo.open("rb") as pdf_file:
|
||||
pdf = PyPDF2.PdfReader(pdf_file)
|
||||
self._process_pdf_pages(pdf)
|
||||
temp_pdf_path = f"/tmp/diario_{self.id}.pdf"
|
||||
with open(temp_pdf_path, "wb") as temp_file:
|
||||
temp_file.write(pdf_file.read())
|
||||
|
||||
# Abrir e processar com fitz
|
||||
doc = fitz.open(temp_pdf_path)
|
||||
self._process_pdf_pages(doc)
|
||||
doc.close()
|
||||
|
||||
# Remover arquivo temporário
|
||||
os.remove(temp_pdf_path)
|
||||
|
||||
except Exception as pdf_error:
|
||||
raise ValidationError(f"Não foi possível processar o PDF: {pdf_error}")
|
||||
|
||||
def _process_pdf_pages(self, pdf):
|
||||
"""Processa cada página do PDF e salva seu conteúdo.
|
||||
def _process_pdf_pages(self, doc):
|
||||
with transaction.atomic():
|
||||
self.paginas.all().delete()
|
||||
|
||||
Args:
|
||||
pdf (PdfReader): Objeto PDF carregado.
|
||||
"""
|
||||
self.paginas.all().delete()
|
||||
for i, page in enumerate(doc):
|
||||
try:
|
||||
blocks = page.get_text("blocks")
|
||||
# Ordenar os blocos por coordenadas (y, x) para manter a ordem de leitura
|
||||
blocks.sort(key=lambda b: (b[1], b[0]))
|
||||
page_text = ""
|
||||
for block in blocks:
|
||||
text = block[4].strip()
|
||||
if text:
|
||||
page_text += text + "\n"
|
||||
|
||||
for i, pagina in enumerate(pdf.pages):
|
||||
try:
|
||||
page_text = pagina.extract_text()
|
||||
if page_text and page_text.strip():
|
||||
# Crucial: Remove NULL bytes from the extracted text
|
||||
# PostgreSQL text fields cannot contain NUL (0x00) bytes
|
||||
cleaned_text = page_text.strip().replace('\x00', '')
|
||||
|
||||
if cleaned_text:
|
||||
PageDiarioOficial.objects.create(
|
||||
diario=self,
|
||||
numero=i + 1,
|
||||
conteudo=cleaned_text,
|
||||
)
|
||||
else:
|
||||
PageDiarioOficial.objects.create(
|
||||
diario=self,
|
||||
numero=i + 1,
|
||||
conteudo="[Conteúdo não extraído ou vazio]",
|
||||
)
|
||||
|
||||
except Exception as page_error:
|
||||
PageDiarioOficial.objects.create(
|
||||
diario=self,
|
||||
numero=i + 1,
|
||||
conteudo=page_text.strip(),
|
||||
conteudo=f"[Erro na extração do texto: {str(page_error)}]",
|
||||
)
|
||||
except Exception as page_error:
|
||||
PageDiarioOficial.objects.create(
|
||||
diario=self,
|
||||
numero=i + 1,
|
||||
conteudo=f"[Erro na extração do texto: {str(page_error)}]",
|
||||
)
|
||||
continue
|
||||
print(f"Erro ao processar a página {i+1} no Diario ID {self.id}: {page_error}")
|
||||
|
||||
@property
|
||||
def data_formatada(self):
|
||||
"""Retorna a data formatada em português (e.g., '1 de Janeiro de 2023')."""
|
||||
return format_date(self.data, format="long", locale="pt_BR")
|
||||
|
||||
@property
|
||||
def is_online(self):
|
||||
"""Verifica se o Diário possui um link (online)."""
|
||||
return bool(self.link)
|
||||
|
||||
def __str__(self):
|
||||
"""Representação em string do Diário Oficial."""
|
||||
tipo_nome = self.tipo.nome if self.tipo else "Sem Tipo"
|
||||
return f"Diário {tipo_nome} nº {self.numero}, {self.data_formatada}"
|
||||
|
||||
@ -144,15 +145,6 @@ class DiarioOficial(models.Model):
|
||||
|
||||
|
||||
class PageDiarioOficial(models.Model):
|
||||
"""Representa uma página de um Diário Oficial com seu conteúdo textual.
|
||||
|
||||
Attributes:
|
||||
diario (ForeignKey): Diário Oficial associado.
|
||||
layout_duas_colunas (BooleanField): Indica se a página tem duas colunas.
|
||||
numero (PositiveIntegerField): Número da página no Diário.
|
||||
conteudo (TextField): Texto extraído da página.
|
||||
"""
|
||||
|
||||
diario = models.ForeignKey(
|
||||
DiarioOficial, on_delete=models.CASCADE, related_name="paginas"
|
||||
)
|
||||
@ -165,7 +157,11 @@ class PageDiarioOficial(models.Model):
|
||||
verbose_name = "Página de Diário Oficial"
|
||||
verbose_name_plural = "Páginas de Diários Oficiais"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
if self.diario:
|
||||
self.diario.save()
|
||||
|
||||
def __str__(self):
|
||||
"""Representação em string da página (e.g., 'Página 1 do Diário 123')."""
|
||||
return f"Página {self.numero} do Diário {self.diario.numero}"
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from ninja import Schema
|
||||
from ninja import Schema, ModelSchema, UploadedFile
|
||||
from typing import List, Optional
|
||||
from .models import TipoDiarioOficial
|
||||
from datetime import date
|
||||
|
||||
|
||||
class PaginaSchema(Schema):
|
||||
@ -61,3 +63,64 @@ class SugestaoResponse(Schema):
|
||||
"""
|
||||
|
||||
sugestao: Optional[str]
|
||||
|
||||
class TipoDiarioSchema(ModelSchema):
|
||||
class Config:
|
||||
model = TipoDiarioOficial
|
||||
model_fields = ["id", "nome"]
|
||||
|
||||
class DiarioOficialIn(Schema):
|
||||
"""Schema para criação de Diário Oficial"""
|
||||
data: date
|
||||
numero: str
|
||||
tipo_id: Optional[int] = None
|
||||
link: Optional[str] = None
|
||||
arquivo: Optional[UploadedFile] = None # Adicionando o campo de arquivo
|
||||
|
||||
class Config:
|
||||
# Configuração adicional para o Swagger UI
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"data": "2023-12-01",
|
||||
"numero": "1234/2023",
|
||||
}
|
||||
}
|
||||
|
||||
class DiarioOficialOut(Schema):
|
||||
"""Schema para retorno de Diário Oficial (com detalhes completos)"""
|
||||
id: int
|
||||
data: str # Será formatado como ISO (YYYY-MM-DD)
|
||||
numero: str
|
||||
link: Optional[str]
|
||||
tipo: Optional[TipoDiarioSchema]
|
||||
total_paginas: int
|
||||
|
||||
@staticmethod
|
||||
def resolve_data(obj):
|
||||
return obj.data.isoformat()
|
||||
|
||||
@staticmethod
|
||||
def resolve_total_paginas(obj):
|
||||
return obj.paginas.count()
|
||||
|
||||
class DiarioOficialUpdate(Schema):
|
||||
"""Schema para atualização de Diário Oficial"""
|
||||
data: Optional[date] = None
|
||||
numero: Optional[str] = None
|
||||
tipo_id: Optional[int] = None
|
||||
link: Optional[str] = None
|
||||
|
||||
class DiarioListagem(Schema):
|
||||
"""Schema simplificado para listagem de Diários"""
|
||||
id: int
|
||||
data: str
|
||||
numero: str
|
||||
tipo_nome: Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def resolve_data(obj):
|
||||
return obj.data.isoformat()
|
||||
|
||||
@staticmethod
|
||||
def resolve_tipo_nome(obj):
|
||||
return obj.tipo.nome if obj.tipo else None
|
||||
@ -2,7 +2,7 @@ import re
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any, List
|
||||
from elasticsearch import Elasticsearch, AsyncElasticsearch
|
||||
from .schemas import ResultadoSchema, PaginaSchema
|
||||
from .schemas import BuscaDiariosResponseSchema, ResultadoSchema, PaginaSchema
|
||||
import unicodedata
|
||||
import asyncio
|
||||
from django.conf import settings
|
||||
@ -588,16 +588,19 @@ async def processar_paginas_encontradas(
|
||||
Returns:
|
||||
List[PaginaSchema]: Lista de páginas encontradas
|
||||
"""
|
||||
if not query:
|
||||
return []
|
||||
|
||||
paginas = []
|
||||
source = hit.get("_source", {})
|
||||
|
||||
if not query and "paginas" in source:
|
||||
for pagina in source["paginas"]:
|
||||
paginas.append(
|
||||
PaginaSchema(
|
||||
numero=pagina.get("numero", 0), conteudo=pagina.get("conteudo", "")
|
||||
)
|
||||
)
|
||||
#if not query and "paginas" in source:
|
||||
# for pagina in source["paginas"]:
|
||||
# paginas.append(
|
||||
# PaginaSchema(
|
||||
# numero=pagina.get("numero", 0), conteudo=pagina.get("conteudo", "")
|
||||
# )
|
||||
# )
|
||||
# Se temos uma query e há inner_hits, processamos todas as páginas correspondentes
|
||||
if query and "inner_hits" in hit and "paginas" in hit["inner_hits"]:
|
||||
paginas_inner_hits = hit["inner_hits"]["paginas"]["hits"]["hits"]
|
||||
@ -731,3 +734,115 @@ async def buscar_diarios_simples(
|
||||
|
||||
# Processar e retornar os resultados
|
||||
return await processar_resultados(response, query, page, page_size)
|
||||
|
||||
|
||||
async def list_diarios(
|
||||
es_client: AsyncElasticsearch,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
data_inicio: Optional[str] = None,
|
||||
data_fim: Optional[str] = None,
|
||||
tipo_diario: Optional[str] = None,
|
||||
numero_diario: Optional[str] = None,
|
||||
ordenar_por: str = "data_desc"
|
||||
) -> Dict[str, any]:
|
||||
"""
|
||||
Lista diários com paginação e filtros opcionais
|
||||
|
||||
Args:
|
||||
es_client: Cliente do Elasticsearch
|
||||
page: Número da página
|
||||
page_size: Itens por página
|
||||
data_inicio: Data inicial (YYYY-MM-DD)
|
||||
data_fim: Data final (YYYY-MM-DD)
|
||||
tipo_diario: Filtro por tipo de diário
|
||||
numero_diario: Filtro por número do diário
|
||||
ordenar_por: Campo para ordenação (data_desc, data_asc)
|
||||
|
||||
Returns:
|
||||
Dict com total de itens e lista de diários
|
||||
"""
|
||||
# Construir query de filtros
|
||||
filters = []
|
||||
|
||||
# Filtro por data
|
||||
if data_inicio or data_fim:
|
||||
date_range = {}
|
||||
if data_inicio:
|
||||
try:
|
||||
datetime.strptime(data_inicio, "%Y-%m-%d")
|
||||
date_range["gte"] = data_inicio
|
||||
except ValueError:
|
||||
pass
|
||||
if data_fim:
|
||||
try:
|
||||
datetime.strptime(data_fim, "%Y-%m-%d")
|
||||
date_range["lte"] = data_fim
|
||||
except ValueError:
|
||||
pass
|
||||
if date_range:
|
||||
filters.append({"range": {"data": date_range}})
|
||||
|
||||
# Filtro por tipo
|
||||
if tipo_diario:
|
||||
filters.append({"term": {"tipo.nome": tipo_diario}})
|
||||
|
||||
# Filtro por número
|
||||
if numero_diario:
|
||||
filters.append({"wildcard": {"numero": f"*{numero_diario}*"}})
|
||||
|
||||
# Construir query completa
|
||||
query = {
|
||||
"bool": {
|
||||
"must": [{"match_all": {}}],
|
||||
"filter": filters
|
||||
}
|
||||
}
|
||||
|
||||
# Definir ordenação
|
||||
sort = [{"data": {"order": "asc" if ordenar_por == "data_asc" else "desc"}}]
|
||||
|
||||
# Executar consulta
|
||||
try:
|
||||
response = await es_client.search(
|
||||
index="diario_oficial",
|
||||
body={
|
||||
"query": query,
|
||||
"sort": sort,
|
||||
"from": (page - 1) * page_size,
|
||||
"size": page_size,
|
||||
"_source": ["numero", "data", "tipo", "link"]
|
||||
}
|
||||
)
|
||||
|
||||
hits = response["hits"]["hits"]
|
||||
total = response["hits"]["total"]["value"]
|
||||
|
||||
# Processar resultados
|
||||
diarios = []
|
||||
for hit in hits:
|
||||
source = hit["_source"]
|
||||
diarios.append(BuscaDiariosResponseSchema(
|
||||
id=hit["_id"],
|
||||
numero=source.get("numero"),
|
||||
data=source.get("data"),
|
||||
tipo=source.get("tipo", {}).get("nome"),
|
||||
link=source.get("link")
|
||||
))
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"results": diarios
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao listar diários: {e}")
|
||||
return {
|
||||
"total": 0,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"results": []
|
||||
}
|
||||
|
||||
|
||||
@ -2,5 +2,5 @@ from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('busca/', views.index, name='index')
|
||||
path("", views.home, name='home')
|
||||
]
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
from ninja import Router
|
||||
from ninja import Router, File, Form
|
||||
from ninja.files import UploadedFile
|
||||
from typing import Optional
|
||||
from django.http import HttpRequest
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from .search_service import (
|
||||
buscar_diarios,
|
||||
sugestao_termo,
|
||||
buscar_diarios_simples,
|
||||
)
|
||||
from .schemas import BuscaDiariosResponseSchema, SugestaoResponse
|
||||
from .schemas import BuscaDiariosResponseSchema, SugestaoResponse, DiarioOficialIn, DiarioOficialOut
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
router = Router(tags=["Diários Oficiais"])
|
||||
|
||||
async def index(request):
|
||||
return render(request, 'diarios/busca.html')
|
||||
async def home(request):
|
||||
return render(request, 'diarios/index.html')
|
||||
|
||||
@router.get(
|
||||
"/sugestao",
|
||||
@ -86,3 +86,28 @@ async def busca_diarios_oficiais_simples(
|
||||
)
|
||||
return resultado
|
||||
|
||||
@router.post("/", response=DiarioOficialOut, summary="Criar novo diário oficial")
|
||||
def criar_diario(
|
||||
request: HttpRequest,
|
||||
# Usamos Form para os dados normais e File para o upload
|
||||
payload: DiarioOficialIn = Form(...),
|
||||
arquivo: UploadedFile = File(None)
|
||||
):
|
||||
"""
|
||||
Cria um novo diário oficial.
|
||||
|
||||
Observações:
|
||||
- Aceita tanto upload de arquivo PDF quanto link para o diário
|
||||
- Se ambos (arquivo e link) forem fornecidos, o arquivo terá prioridade
|
||||
- O arquivo deve ser um PDF válido
|
||||
"""
|
||||
# Prioriza o arquivo se ambos existirem
|
||||
arquivo_final = arquivo if arquivo else payload.arquivo
|
||||
|
||||
return DiarioOficialService.criar_diario(
|
||||
data=payload.data,
|
||||
numero=payload.numero,
|
||||
tipo_id=payload.tipo_id,
|
||||
arquivo=arquivo_final,
|
||||
link=payload.link
|
||||
)
|
||||
5
diarios_oficiais_alems/static/css/bootstrap-custom.min.css
vendored
Normal file
5
diarios_oficiais_alems/static/css/bootstrap-custom.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2106
diarios_oficiais_alems/static/css/bootstrap-icons.css
vendored
Normal file
2106
diarios_oficiais_alems/static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff
Normal file
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff2
Normal file
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 97 KiB |
BIN
diarios_oficiais_alems/static/images/logo.jpg
Normal file
BIN
diarios_oficiais_alems/static/images/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
diarios_oficiais_alems/static/images/risco.jpg
Normal file
BIN
diarios_oficiais_alems/static/images/risco.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
5
diarios_oficiais_alems/static/js/alpine.min.js
vendored
Normal file
5
diarios_oficiais_alems/static/js/alpine.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
diarios_oficiais_alems/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
diarios_oficiais_alems/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
diarios_oficiais_alems/static/js/config.js
Normal file
1
diarios_oficiais_alems/static/js/config.js
Normal file
@ -0,0 +1 @@
|
||||
const API_BASE_URL = "http://109.199.98.226";
|
||||
@ -64,7 +64,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!query) return null;
|
||||
|
||||
try {
|
||||
const url = new URL('http://109.199.98.226:8005/api/v1/diarios/sugestao');
|
||||
const url = new URL('http://192.168.235.234/api/v1/diarios/sugestao');
|
||||
url.searchParams.append('q', query);
|
||||
|
||||
const response = await fetch(url);
|
||||
@ -168,7 +168,7 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
this.ultimoTermoBuscado = this.searchParams.q;
|
||||
// Usando agora o endpoint busca
|
||||
const url = new URL('http://109.199.98.226:8005/api/v1/diarios/busca');
|
||||
const url = new URL('http://192.168.235.234/api/v1/diarios/busca');
|
||||
|
||||
// Adicionar parâmetros à URL
|
||||
Object.entries(this.searchParams).forEach(([key, value]) => {
|
||||
@ -90,17 +90,6 @@
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a class="nav-link" href="{% url 'account_logout' %}">{% translate "Sign Out" %}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if ACCOUNT_ALLOW_REGISTRATION %}
|
||||
<li class="nav-item">
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% translate "Sign Up" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% translate "Sign In" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sistema de Busca de Diários Oficiais</title>
|
||||
<title>Diários Oficiais</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{% static 'css/bootstrap-custom.min.css' %}" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-icons.css' %}">
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.1/dist/cdn.min.js"></script>
|
||||
<script defer src="{% static 'js/alpine.min.js' %}"></script>
|
||||
<!-- Estilos customizados -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
|
||||
<link rel="icon" href="{% static 'images/favicon.ico' %}" type="image/x-icon">
|
||||
</head>
|
||||
<body class="bg-light-gradient">
|
||||
<div class="container py-5" x-data="searchApp">
|
||||
<div class="container py-5" x-data="searchApp"> <!-- Container do Logo e Imagem Direita -->
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<!-- Logo à esquerda -->
|
||||
<div class="logo-container">
|
||||
<img src="{% static 'images/logo.jpg' %}" alt="Logo" class="img-fluid">
|
||||
</div>
|
||||
<!-- Imagem à direita -->
|
||||
<div class="risco-container">
|
||||
<img src="{% static 'images/risco.jpg' %}" alt="Risco" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-5 fw-bold text-primary mb-3">
|
||||
<i class="bi bi-search me-2"></i>Sistema de Busca de Diários Oficiais
|
||||
<i class="bi bi-search me-2"></i>Diários Oficiais
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -114,7 +129,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Adicionando a seção de "Você quis dizer" após iniciar a busca -->
|
||||
<!-- Seção Você quis dizer -->
|
||||
<template x-if="!isLoading && !error && searchResults && suggestion && shouldShowSuggestion">
|
||||
<div class="mb-3 mt-3 alert alert-info d-flex align-items-center">
|
||||
<i class="bi bi-lightbulb-fill me-2"></i>
|
||||
@ -283,8 +298,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>Sistema de Busca de Diários Oficiais</h5>
|
||||
<p class="small">Uma ferramenta avançada para pesquisa em diários oficiais.</p>
|
||||
<h5>Diários Oficiais</h5>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="small mb-0">© 2025 Todos os direitos reservados</p>
|
||||
@ -292,10 +306,88 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Botão de ajuda que abre o modal -->
|
||||
<button type="button" class="btn btn-outline-secondary position-fixed bottom-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#helpModal">
|
||||
<i class="bi bi-question-circle"></i> Ajuda
|
||||
</button>
|
||||
|
||||
<!-- Modal de Ajuda -->
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="helpModalLabel">Ajuda - Sistema de Busca de Diários Oficiais</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<h6>Atenção quanto à qualidade dos dados</h6>
|
||||
<p>Alguns documentos podem conter erros de leitura devido à baixa qualidade das imagens dos PDFs originais. Isso pode afetar a precisão da busca, especialmente no modo de <strong>busca exata</strong>.</p>
|
||||
<p>Nesses casos, recomendamos utilizar o modo <strong>qualquer termo</strong>, que é mais tolerante a pequenas falhas de reconhecimento de texto.</p>
|
||||
</div>
|
||||
|
||||
<h5>Como realizar buscas</h5>
|
||||
<hr>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Busca básica</h6>
|
||||
<p>Digite o termo que deseja buscar no campo principal e clique em "Buscar". O sistema irá localizar ocorrências desse termo nos Diários Oficiais.</p>
|
||||
<ul>
|
||||
<li><strong>Número do Diário:</strong> Se souber o número específico do diário, digite-o neste campo para filtrar os resultados.</li>
|
||||
<li><strong>Modo de Busca:</strong>
|
||||
<ul>
|
||||
<li><em>Busca exata</em> - Encontra apenas documentos que contenham exatamente o termo informado.</li>
|
||||
<li><em>Qualquer termo</em> - Encontra documentos que contenham qualquer um dos termos informados.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Ordenação dos resultados</h6>
|
||||
<p>Você pode ordenar os resultados de três formas:</p>
|
||||
<ul>
|
||||
<li><strong>Relevância:</strong> Mostra primeiro os documentos mais relevantes para sua busca.</li>
|
||||
<li><strong>Data (Decrescente):</strong> Mostra os diários mais recentes primeiro.</li>
|
||||
<li><strong>Data (Crescente):</strong> Mostra os diários mais antigos primeiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Filtros avançados</h6>
|
||||
<p>Clique em "Mostrar filtros avançados" para acessar opções adicionais:</p>
|
||||
<ul>
|
||||
<li><strong>Data inicial e Data final:</strong> Restringe a busca a diários publicados dentro do período informado.</li>
|
||||
<li><strong>Resultados por página:</strong> Define quantos resultados serão exibidos em cada página.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Resultados da busca</h6>
|
||||
<p>Nos resultados, você verá:</p>
|
||||
<ul>
|
||||
<li>Tipo e número do diário, com a data de publicação.</li>
|
||||
<li>A página com melhor correspondência aparecerá destacada.</li>
|
||||
<li>Para ver mais páginas do mesmo diário, clique no botão de expansão.</li>
|
||||
<li>Use o botão "Ver Diário Completo" para abrir o arquivo PDF do diário inteiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Sugestões de busca</h6>
|
||||
<p>Se o sistema encontrar um termo semelhante ao que você buscou, mostrará uma sugestão que você pode clicar para realizar uma nova busca com o termo sugerido.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Entendi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
|
||||
<!-- Script da aplicação -->
|
||||
<script src="script.js"></script>
|
||||
<script src="{% static 'js/config.js' %}"></script>
|
||||
<script src="{% static 'js/script.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
volumes:
|
||||
diarios_oficiais_alems_local_postgres_data: {}
|
||||
diarios_oficiais_alems_local_postgres_data_backups: {}
|
||||
esdata:
|
||||
esdata:
|
||||
|
||||
services:
|
||||
django:
|
||||
@ -12,6 +12,7 @@ services:
|
||||
container_name: diarios_oficiais_alems_local_django
|
||||
depends_on:
|
||||
- postgres
|
||||
- elasticsearch
|
||||
volumes:
|
||||
- .:/app:z
|
||||
env_file:
|
||||
@ -20,6 +21,21 @@ services:
|
||||
ports:
|
||||
- '8005:8005'
|
||||
command: /start
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: diarios_oficiais_alems_local_nginx
|
||||
volumes:
|
||||
- ./compose/local/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./staticfiles:/app/staticfiles:ro
|
||||
- ./mediafiles:/app/mediafiles:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- django
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
postgres:
|
||||
build:
|
||||
@ -37,15 +53,8 @@ services:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.3
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- xpack.security.enabled=true
|
||||
- xpack.security.enabled=false
|
||||
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
|
||||
volumes:
|
||||
- esdata:/usr/share/elasticsearch/data
|
||||
|
||||
frontend:
|
||||
image: nginx:latest
|
||||
container_name: frontend_diarios
|
||||
volumes:
|
||||
- ./frontend:/usr/share/nginx/html:ro
|
||||
ports:
|
||||
- "8006:80"
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
version: '3.8'
|
||||
|
||||
volumes:
|
||||
production_postgres_data: {}
|
||||
production_postgres_data_backups: {}
|
||||
production_traefik: {}
|
||||
production_esdata: {}
|
||||
production_django_static: {}
|
||||
production_django_media: {}
|
||||
|
||||
|
||||
services:
|
||||
django:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./compose/production/django/Dockerfile
|
||||
|
||||
image: diarios_oficiais_alems_production_django
|
||||
volumes:
|
||||
- production_django_media:/app/diarios_oficiais_alems/media
|
||||
container_name: diarios_oficiais_alems_production_django
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- elasticsearch
|
||||
volumes:
|
||||
- production_django_media:/app/diarios_oficiais_alems/media
|
||||
- production_django_static:/app/diarios_oficiais_alems/static
|
||||
env_file:
|
||||
- ./.envs/.production/.django
|
||||
- ./.envs/.production/.postgres
|
||||
ports:
|
||||
- '8005:8005'
|
||||
command: /start
|
||||
|
||||
postgres:
|
||||
@ -27,34 +33,53 @@ services:
|
||||
context: .
|
||||
dockerfile: ./compose/production/postgres/Dockerfile
|
||||
image: diarios_oficiais_alems_production_postgres
|
||||
container_name: diarios_oficiais_alems_production_postgres
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- production_postgres_data:/var/lib/postgresql/data
|
||||
- production_postgres_data_backups:/backups
|
||||
env_file:
|
||||
- ./.envs/.production/.postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U debug"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
traefik:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./compose/production/traefik/Dockerfile
|
||||
image: diarios_oficiais_alems_production_traefik
|
||||
depends_on:
|
||||
- django
|
||||
volumes:
|
||||
- production_traefik:/etc/traefik/acme
|
||||
ports:
|
||||
- '0.0.0.0:80:80'
|
||||
- '0.0.0.0:443:443'
|
||||
|
||||
redis:
|
||||
image: docker.io/redis:6
|
||||
|
||||
nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./compose/production/nginx/Dockerfile
|
||||
image: diarios_oficiais_alems_production_nginx
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.3
|
||||
container_name: diarios_oficiais_alems_production_elasticsearch
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- xpack.security.enabled=true
|
||||
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
|
||||
- bootstrap.memory_lock=true
|
||||
volumes:
|
||||
- production_esdata:/usr/share/elasticsearch/data
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
|
||||
|
||||
frontend:
|
||||
image: nginx:alpine
|
||||
container_name: diarios_oficiais_alems_production_frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- django
|
||||
volumes:
|
||||
- production_django_static:/usr/share/nginx/static:ro
|
||||
- production_django_media:/usr/share/nginx/media:ro
|
||||
- ./compose/production/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
|
||||
2914
erros_processamento.txt
Normal file
2914
erros_processamento.txt
Normal file
File diff suppressed because it is too large
Load Diff
5
frontend/custom.scss
Normal file
5
frontend/custom.scss
Normal file
@ -0,0 +1,5 @@
|
||||
$primary: #448AAD;
|
||||
$secondary: #5C944E;
|
||||
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
@ -3,23 +3,38 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sistema de Busca de Diários Oficiais</title>
|
||||
<title>Diários Oficiais</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="./css/bootstrap-custom.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="./css/bootstrap-icons.css">
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.1/dist/cdn.min.js"></script>
|
||||
<script defer src="./js/alpine.min.js"></script>
|
||||
<!-- Estilos customizados -->
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<link rel="stylesheet" href="./css/styles.css">
|
||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body class="bg-light-gradient">
|
||||
<div class="container py-5" x-data="searchApp">
|
||||
<div class="container py-5" x-data="searchApp"> <!-- Container do Logo e Imagem Direita -->
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<!-- Logo à esquerda -->
|
||||
<div class="logo-container">
|
||||
<img src="img/logo.jpg" alt="Logo" class="img-fluid">
|
||||
</div>
|
||||
|
||||
<!-- Imagem à direita -->
|
||||
<div class="risco-container">
|
||||
<img src="img/risco.jpg" alt="Risco" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-5 fw-bold text-primary mb-3">
|
||||
<i class="bi bi-search me-2"></i>Sistema de Busca de Diários Oficiais
|
||||
<i class="bi bi-search me-2"></i>Diários Oficiais
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -283,8 +298,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>Sistema de Busca de Diários Oficiais</h5>
|
||||
<p class="small">Uma ferramenta avançada para pesquisa em diários oficiais.</p>
|
||||
<h5>Diários Oficiais</h5>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="small mb-0">© 2025 Todos os direitos reservados</p>
|
||||
@ -292,12 +306,89 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Botão de ajuda que abre o modal -->
|
||||
<button type="button" class="btn btn-outline-secondary position-fixed bottom-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#helpModal">
|
||||
<i class="bi bi-question-circle"></i> Ajuda
|
||||
</button>
|
||||
|
||||
<!-- Modal de Ajuda -->
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="helpModalLabel">Ajuda - Sistema de Busca de Diários Oficiais</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<h6>Atenção quanto à qualidade dos dados</h6>
|
||||
<p>Alguns documentos podem conter erros de leitura devido à baixa qualidade das imagens dos PDFs originais. Isso pode afetar a precisão da busca, especialmente no modo de <strong>busca exata</strong>.</p>
|
||||
<p>Nesses casos, recomendamos utilizar o modo <strong>qualquer termo</strong>, que é mais tolerante a pequenas falhas de reconhecimento de texto.</p>
|
||||
</div>
|
||||
|
||||
<h5>Como realizar buscas</h5>
|
||||
<hr>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Busca básica</h6>
|
||||
<p>Digite o termo que deseja buscar no campo principal e clique em "Buscar". O sistema irá localizar ocorrências desse termo nos Diários Oficiais.</p>
|
||||
<ul>
|
||||
<li><strong>Número do Diário:</strong> Se souber o número específico do diário, digite-o neste campo para filtrar os resultados.</li>
|
||||
<li><strong>Modo de Busca:</strong>
|
||||
<ul>
|
||||
<li><em>Busca exata</em> - Encontra apenas documentos que contenham exatamente o termo informado.</li>
|
||||
<li><em>Qualquer termo</em> - Encontra documentos que contenham qualquer um dos termos informados.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Ordenação dos resultados</h6>
|
||||
<p>Você pode ordenar os resultados de três formas:</p>
|
||||
<ul>
|
||||
<li><strong>Relevância:</strong> Mostra primeiro os documentos mais relevantes para sua busca.</li>
|
||||
<li><strong>Data (Decrescente):</strong> Mostra os diários mais recentes primeiro.</li>
|
||||
<li><strong>Data (Crescente):</strong> Mostra os diários mais antigos primeiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Filtros avançados</h6>
|
||||
<p>Clique em "Mostrar filtros avançados" para acessar opções adicionais:</p>
|
||||
<ul>
|
||||
<li><strong>Data inicial e Data final:</strong> Restringe a busca a diários publicados dentro do período informado.</li>
|
||||
<li><strong>Resultados por página:</strong> Define quantos resultados serão exibidos em cada página.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Resultados da busca</h6>
|
||||
<p>Nos resultados, você verá:</p>
|
||||
<ul>
|
||||
<li>Tipo e número do diário, com a data de publicação.</li>
|
||||
<li>A página com melhor correspondência aparecerá destacada.</li>
|
||||
<li>Para ver mais páginas do mesmo diário, clique no botão de expansão.</li>
|
||||
<li>Use o botão "Ver Diário Completo" para abrir o arquivo PDF do diário inteiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Sugestões de busca</h6>
|
||||
<p>Se o sistema encontrar um termo semelhante ao que você buscou, mostrará uma sugestão que você pode clicar para realizar uma nova busca com o termo sugerido.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Entendi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="./js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Script da aplicação -->
|
||||
<script src="js/config.js"></script>
|
||||
<script src="js/script.js"></script>
|
||||
<script src="./js/config.js"></script>
|
||||
<script src="./js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
const API_BASE_URL = "http://109.199.98.226:8005";
|
||||
781
frontend/package-lock.json
generated
Normal file
781
frontend/package-lock.json
generated
Normal file
@ -0,0 +1,781 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.14.9",
|
||||
"bootstrap": "^5.3.6",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"sass": "^1.89.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.14.9",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.9.tgz",
|
||||
"integrity": "sha512-gqSOhTEyryU9FhviNqiHBHzgjkvtukq9tevew29fTj+ofZtfsYriw4zPirHHOAy9bw8QoL3WGhyk7QqCh5AYlw==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
|
||||
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
|
||||
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
|
||||
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ=="
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.89.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz",
|
||||
"integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1",
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"optional": true
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.1.5"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"alpinejs": {
|
||||
"version": "3.14.9",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.9.tgz",
|
||||
"integrity": "sha512-gqSOhTEyryU9FhviNqiHBHzgjkvtukq9tevew29fTj+ofZtfsYriw4zPirHHOAy9bw8QoL3WGhyk7QqCh5AYlw==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
|
||||
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
|
||||
"requires": {}
|
||||
},
|
||||
"bootstrap-icons": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
|
||||
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw=="
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"immutable": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
|
||||
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ=="
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"optional": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"optional": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"optional": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.89.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz",
|
||||
"integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==",
|
||||
"requires": {
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
frontend/package.json
Normal file
8
frontend/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.14.9",
|
||||
"bootstrap": "^5.3.6",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"sass": "^1.89.1"
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ django-redis==5.4.0 # https://github.com/jazzband/django-redis
|
||||
# ------------------------------------------------------------------------------
|
||||
django-elasticsearch-dsl
|
||||
PyPDF2
|
||||
PyMuPDF
|
||||
babel
|
||||
django-ninja
|
||||
remote-pdb
|
||||
|
||||
@ -34,3 +34,4 @@ pytest-django==4.10.0 # https://github.com/pytest-dev/pytest-django
|
||||
|
||||
debugpy
|
||||
black
|
||||
language-tool-python
|
||||
|
||||
Reference in New Issue
Block a user