Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ EMAIL_APP_NAME=OpenJornada
DEBUG=True
API_HOST=0.0.0.0
API_PORT=8000

# SMS Configuration (LabsMobile)
# SMS_ENABLED=false
# SMS_PROVIDER=labsmobile
# SMS_LABSMOBILE_API_TOKEN= # Base64(username:api_key) for HTTP Basic auth. for example: echo -n "tu_usuario@email.com:tu_api_key" | base64
# SMS_SENDER_ID=OpenJornada
# SMS_UNLIMITED_BALANCE=0
31 changes: 31 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# OpenJornada API - Unit Tests
# Ejecuta los tests unitarios en cada push y pull request.
# Los tests no requieren MongoDB ni servicios externos.

name: Tests

on:
push:
branches: ["**"]
pull_request:
branches: [main, develop]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run unit tests
run: python -m pytest tests/unit/ --noconftest -v
5 changes: 4 additions & 1 deletion api/auth/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
"manage_backups",
"view_reports",
"export_reports",
"manage_inspection"
"manage_inspection",
"manage_sms_config",
"view_sms_logs",
"view_sms_dashboard"
],
"inspector": [
"view_reports",
Expand Down
9 changes: 9 additions & 0 deletions api/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ async def init_db():
partialFilterExpression={"status": "pending"}
)

# Create indexes for SmsLogs
await db.SmsLogs.create_index("worker_id")
await db.SmsLogs.create_index("company_id")
await db.SmsLogs.create_index("time_record_entry_id")
await db.SmsLogs.create_index("status")
await db.SmsLogs.create_index("created_at")
await db.SmsLogs.create_index([("company_id", 1), ("created_at", 1)])
await db.SmsLogs.create_index([("worker_id", 1), ("time_record_entry_id", 1), ("reminder_number", 1)])

except Exception as e:
print(f"Error initializing database: {e}")

Expand Down
38 changes: 22 additions & 16 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
Expand All @@ -7,7 +9,9 @@

from .database import init_db, init_default_settings
from .routers import workers, time_records, auth, incidents, settings, companies, pause_types, change_requests, gdpr, backups, reports
from .routers import sms
from .services.scheduler_service import scheduler_service
from .services.sms_service import sms_service

load_dotenv()

Expand All @@ -17,14 +21,27 @@
format='%(levelname)s: %(message)s'
)


@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
await init_default_settings()
await sms_service.initialize()
await scheduler_service.start()
yield
scheduler_service.stop()
await sms_service.close()


app = FastAPI(
title="Time Tracking API",
description="API for tracking workers' time entries",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
root_path=os.getenv("ROOT_PATH", "")
root_path=os.getenv("ROOT_PATH", ""),
lifespan=lifespan
)

# CORS
Expand All @@ -48,18 +65,7 @@
app.include_router(backups.router, prefix="/api", tags=["Backups"])
app.include_router(reports.router, prefix="/api", tags=["Reports & Inspection"])
app.include_router(gdpr.router, tags=["GDPR"])


@app.on_event("startup")
async def startup():
await init_db()
await init_default_settings()
await scheduler_service.start()


@app.on_event("shutdown")
async def shutdown():
scheduler_service.stop()
app.include_router(sms.router, prefix="/api", tags=["SMS"])


@app.get("/", tags=["Health"])
Expand All @@ -68,7 +74,7 @@ async def health_check():


if __name__ == "__main__":
uvicorn.run("app.main:app",
host=os.getenv("API_HOST", "0.0.0.0"),
port=int(os.getenv("API_PORT", 8000)),
uvicorn.run("app.main:app",
host=os.getenv("API_HOST", "0.0.0.0"),
port=int(os.getenv("API_PORT", 8000)),
reload=os.getenv("DEBUG", "False").lower() == "true")
4 changes: 4 additions & 0 deletions api/models/companies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
from typing import Optional
from datetime import datetime

from .sms import SmsCompanyConfig


class CompanyBase(BaseModel):
name: str = Field(..., min_length=1, max_length=200)

Expand All @@ -27,3 +30,4 @@ class CompanyResponse(CompanyBase):
updated_at: Optional[datetime] = None
deleted_at: Optional[datetime] = None
deleted_by: Optional[str] = None
sms_config: Optional[SmsCompanyConfig] = None
5 changes: 5 additions & 0 deletions api/models/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, Literal

from .sms import SmsProviderConfigInput, SmsProviderConfigStored, SmsProviderConfigResponse


# ============================================================================
# Backup Configuration Models
Expand Down Expand Up @@ -107,13 +109,16 @@ class SettingsBase(BaseModel):
class SettingsUpdate(BaseModel):
contact_email: Optional[EmailStr] = None
backup_config: Optional[BackupConfigInput] = None
sms_provider_config: Optional[SmsProviderConfigInput] = None


class SettingsInDB(SettingsBase):
id: str # MongoDB _id converted to string
backup_config: Optional[BackupConfigStored] = None
sms_provider_config: Optional[SmsProviderConfigStored] = None


class SettingsResponse(SettingsBase):
id: str
backup_config: Optional[BackupConfigResponse] = None
sms_provider_config: Optional[SmsProviderConfigResponse] = None
Loading
Loading