Compare commits

..

18 Commits

Author SHA1 Message Date
renovate[bot]
bcec3079d3 feat(deps): update dependency pytest to v8.3.4 2025-01-19 21:23:04 +00:00
renovate[bot]
cf033298ce feat(deps): update dependency psycopg-binary to v3.2.4 2025-01-19 17:13:09 +00:00
renovate[bot]
3738b6f0a7 feat(deps): update dependency psycopg to v3.2.4 2025-01-19 12:37:22 +00:00
renovate[bot]
b8ac7226be feat(deps): update dependency click to v8.1.8 2025-01-19 08:23:32 +00:00
dafdf34f71 feat: added automerge to renovate 2025-01-17 12:33:14 +01:00
e77fe115c6 fix: removed duplicate install 2024-11-24 17:16:23 +01:00
6ab1eafe1d fix: fixed security recommendation from codacy 2024-11-24 17:16:11 +01:00
256e2adbf7 fix: fixed a recommendation from codacy 2024-11-24 17:13:44 +01:00
7c0d0da511 fix: bumped security dependency 2024-11-24 17:13:33 +01:00
renovate[bot]
4f793585e5 feat(deps): update dependency anyio to v4.6.2.post1 (#2)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-24 16:19:31 +01:00
renovate[bot]
98df462b61 chore: Configure Renovate (#1)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Conrad <grosserconrad@gmail.com>
2024-11-24 16:05:51 +01:00
6db2b3e14e Update ci.yml 2024-11-24 16:05:40 +01:00
22eaed8a75 fix: fixed an issue with duplicate operation_id 2024-10-10 23:38:35 +02:00
creyD
4d0ecb2ee8 Adjusted files for isort & autopep 2024-10-10 21:35:18 +00:00
88e97faddb feat: added select delete 2024-10-10 23:34:45 +02:00
cefb48a4b2 feat: added environment for log entries 2024-10-10 20:32:46 +02:00
4f50f6bb7e fix: fixed issue with the uvicorn worker command 2024-10-10 17:59:38 +02:00
a43ec6abd8 breaking: release 1.0.0 2024-10-10 17:39:13 +02:00
10 changed files with 214 additions and 18 deletions

View File

@@ -5,6 +5,7 @@ on:
branches:
- dev
- master
- renovate/**
paths-ignore:
- "**/.github/**"
- "**/.gitignore"
@@ -13,6 +14,10 @@ on:
- "**/CHANGELOG.md"
- "**/docs/**"
workflow_dispatch:
pull_request:
branches:
- dev
- master
env:
REGISTRY: ghcr.io

View File

@@ -1,9 +1,15 @@
FROM python:3.12-slim
ARG VERSION=unkown
ARG VERSION=unknown
# Create a non-root user and group
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY . .
# Change ownership of the application directory
RUN chown -R appuser:appuser /app
# Python setup
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
@@ -12,13 +18,19 @@ ENV ENV=DEV
# Install dependencies
RUN pip install --no-cache-dir --upgrade -r requirements.txt
RUN pip install 'uvicorn[standard]'
# Switch to the non-root user
USER appuser
EXPOSE 9000
CMD ["uvicorn", "app.main:app", "-w", "6" , "--host", "0.0.0.0", "--port", "9000"]
CMD ["uvicorn", "app.main:app", "--workers", "6" , "--host", "0.0.0.0", "--port", "9000"]
# Install curl
RUN apt-get update && apt-get install -y curl && apt-get clean
USER root
RUN apt-get update && apt-get install -y --no-install-recommends curl && apt-get clean
# Switch back to the non-root user
USER appuser
HEALTHCHECK --interval=30s --timeout=10s --retries=5 \
CMD curl --fail http://localhost:9000/openapi.json || exit 1

View File

@@ -0,0 +1,29 @@
"""empty message
Revision ID: 21dc1dc045b8
Revises: 74c576cf9560
Create Date: 2024-10-10 20:32:12.579725
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "21dc1dc045b8"
down_revision: Union[str, None] = "74c576cf9560"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
with op.batch_alter_table("logentry", schema=None) as batch_op:
batch_op.add_column(sa.Column("environment", sa.String(length=64), nullable=True))
def downgrade() -> None:
with op.batch_alter_table("logentry", schema=None) as batch_op:
batch_op.drop_column("environment")

View File

@@ -0,0 +1,54 @@
"""empty message
Revision ID: 74c576cf9560
Revises: 95201f00f6b9
Create Date: 2024-10-10 17:38:19.834168
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "74c576cf9560"
down_revision: Union[str, None] = "95201f00f6b9"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"logentry",
sa.Column("application", sa.UUID(), nullable=False),
sa.Column(
"l_type",
sa.Enum("INFO", "WARNING", "ERROR", "CRITICAL", name="logtype"),
nullable=False,
),
sa.Column(
"t_type",
sa.Enum("CREATE", "UPDATE", "DELETE", "UNDEFINED", name="transactiontype"),
nullable=False,
),
sa.Column("message", sa.String(length=512), nullable=True),
sa.Column("author", sa.String(length=512), nullable=False),
sa.Column("object_reference", sa.String(length=512), nullable=True),
sa.Column("previous_object", sa.JSON(), nullable=True),
sa.Column("id", sa.UUID(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True
),
sa.Column("updated_at", sa.DateTime(), nullable=True),
sa.Column("created_by_id", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["application"], ["application.id"], ondelete="CASCADE", name="fk_logentry_application"
),
sa.PrimaryKeyConstraint("id", name="pk_logentry"),
)
def downgrade() -> None:
op.drop_table("logentry")

View File

@@ -23,6 +23,7 @@ class LogEntry(Base):
application = Column(
UUID(as_uuid=True), ForeignKey("application.id", ondelete="CASCADE"), nullable=False
)
environment = Column(String(64), nullable=True, default="prod")
# type of the log entry
l_type = Column(Enum(LogType), nullable=False, default=LogType.INFO)
# type of the transaction

View File

@@ -17,6 +17,8 @@ from uuid import UUID
from pydantic.json_schema import SkipJsonSchema
from fastapi_filters import FilterValues, create_filters
from fastapi_filters.ext.sqlalchemy import apply_filters
from app.models.entry import LogType, TransactionType
from datetime import datetime
router = APIRouter(prefix="/log", tags=["logging"])
@@ -62,10 +64,6 @@ async def get_log(
return LogOUT.model_validate(obj)
from app.models.entry import LogType, TransactionType
from datetime import datetime
@router.get("/")
async def get_logs(
search: str | SkipJsonSchema[None] = None,
@@ -73,6 +71,7 @@ async def get_logs(
filters: FilterValues = Depends(
create_filters(
created_by_id=str,
environment=str,
l_type=LogType,
t_type=TransactionType,
application=UUID,
@@ -93,3 +92,37 @@ async def get_logs(
LogEntry.message.ilike(f"%{search}%") | LogEntry.author.ilike(f"%{search}%")
)
return paginate(db, order_by_query(the_select))
@router.delete("/", status_code=200, operation_id="log_delete_many")
async def delete_logs(
application: UUID,
environment: str | SkipJsonSchema[None] = None,
l_type: LogType | SkipJsonSchema[None] = None,
t_type: TransactionType | SkipJsonSchema[None] = None,
object_reference: str | SkipJsonSchema[None] = None,
author: str | SkipJsonSchema[None] = None,
sub: str = Security(verify),
db: Session = Depends(get_db),
) -> int:
filters = {
"application": application,
"created_by_id": sub,
}
if environment is not None:
filters["environment"] = environment
if l_type is not None:
filters["l_type"] = l_type
if t_type is not None:
filters["t_type"] = t_type
if object_reference is not None:
filters["object_reference"] = object_reference
if author is not None:
filters["author"] = author
query = db.query(LogEntry).filter_by(**filters)
the_impact = query.count()
query.delete(synchronize_session=False)
db.commit()
return the_impact

View File

@@ -6,6 +6,7 @@ from pydantic.json_schema import SkipJsonSchema
class LogIN(BaseSchemaModelIN):
application: UUID
environment: str = "prod"
l_type: LogType = LogType.INFO
t_type: TransactionType = TransactionType.UNDEFINED

View File

@@ -24,14 +24,25 @@ def app_context(self, name: str = "Testing"):
@contextlib.contextmanager
def log_examples(self):
LOG_EXAMPLES = [
{"l_type": "info", "t_type": "create", "message": "User Max Mustermann created"},
{"l_type": "info", "t_type": "update", "message": "User Max Mustermann updated"},
{
"l_type": "info",
"t_type": "create",
"message": "User Max Mustermann created",
"environment": "dev",
},
{
"l_type": "info",
"t_type": "update",
"message": "User Max Mustermann updated",
"environment": "dev",
},
{
"l_type": "info",
"t_type": "create",
"author": "auth|max_muster",
"message": "User Max Mustermann created a Unit",
"object_reference": "1",
"environment": "dev",
},
{
"l_type": "info",
@@ -40,8 +51,14 @@ def log_examples(self):
"message": "User Max Mustermann updated Unit 1",
"object_reference": "1",
"previous_object": {"name": "Unit 1"},
"environment": "prod",
},
{
"l_type": "warning",
"t_type": "delete",
"message": "User Max Mustermann deleted",
"environment": "prod",
},
{"l_type": "warning", "t_type": "delete", "message": "User Max Mustermann deleted"},
]
with app_context(self) as app_id:
for entry in LOG_EXAMPLES:
@@ -129,6 +146,7 @@ class TestAPI:
assert re["t_type"] == "undefined"
assert re["message"] == None
assert re["author"] == "system"
assert re["environment"] == "prod"
assert re["object_reference"] == None
assert re["previous_object"] == None
assert re["created_by_id"] == CURRENT_USER
@@ -211,3 +229,34 @@ class TestAPI:
re = self.c.get("/log/?author%5Bne%5D=auth|max_muster")
assert re["total"] == 3
assert len(re["results"]) == 3
# environment
re = self.c.get("/log/?environment=dev")
assert re["total"] == 3
assert len(re["results"]) == 3
# application and environment
re = self.c.get("/log/?application=" + app_id + "&environment=prod")
assert re["total"] == 2
assert len(re["results"]) == 2
def test_logging_delete(self):
with log_examples(self) as app_id:
re = self.c.delete("/log/?application=" + str(app_id) + "&environment=prod", r_code=200)
assert re == 2
re = self.c.get("/log/?application=" + str(app_id) + "&environment=prod")
assert re["total"] == 0
re = self.c.get("/log/?application=" + str(app_id) + "&environment=dev")
assert re["total"] == 3
# clear complete application
re = self.c.get("/log/?application=" + str(app_id))
assert re["total"] == 3
re = self.c.delete("/log/?application=" + str(app_id), r_code=200)
assert re == 3
re = self.c.get("/log/?application=" + str(app_id))
assert re["total"] == 0

12
renovate.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", ":semanticCommitTypeAll(feat)"],
"packageRules": [
{
"automerge": true,
"description": "Automerge non-major updates",
"matchUpdateTypes": ["minor", "patch"],
"automergeType": "branch"
}
]
}

View File

@@ -1,22 +1,22 @@
annotated-types==0.7.0
anyio==4.6.0
anyio==4.6.2.post1
certifi==2024.8.30
creyPY==1.2.5
fastapi==0.115.0
fastapi==0.115.5
fastapi-pagination==0.12.31
h11==0.14.0
httpcore==1.0.6
httpx==0.27.2
idna==3.10
psycopg==3.2.3
psycopg-binary==3.2.3
psycopg==3.2.4
psycopg-binary==3.2.4
psycopg-pool==3.2.3
pydantic==2.9.2
pydantic_core==2.23.4
python-dotenv==1.0.1
sniffio==1.3.1
SQLAlchemy==2.0.35
starlette==0.38.6
starlette==0.40.0
typing_extensions==4.12.2
Mako==1.3.5 # Alembic
@@ -25,12 +25,12 @@ alembic==1.13.3 # Alembic
SQLAlchemy-Utils==0.41.2 # SQLAlchemy
click==8.1.7 # Uvicorn
click==8.1.8 # Uvicorn
uvicorn==0.31.1 # Uvicorn
iniconfig==2.0.0 # pytest
packaging==24.1 # pytest
pluggy==1.5.0 # pytest
pytest==8.3.3 # pytest
pytest==8.3.4 # pytest
fastapi-filters==0.2.9 # Filters