mirror of
https://github.com/dat515-2025/Group-8.git
synced 2026-03-22 15:12:08 +01:00
Compare commits
11 Commits
14397b8a25
...
c864e753c9
| Author | SHA1 | Date | |
|---|---|---|---|
| c864e753c9 | |||
| b4a453be04 | |||
| d290664352 | |||
| 008f111fa7 | |||
| ece2c4d4c5 | |||
| 2d0d309d2b | |||
| 7f8dd2e846 | |||
| e0c18912f3 | |||
| 99384aeb0a | |||
| 912697b046 | |||
|
|
356e1d868c |
3
.github/workflows/deploy-pr.yaml
vendored
3
.github/workflows/deploy-pr.yaml
vendored
@@ -101,7 +101,8 @@ jobs:
|
|||||||
--set image.digest="$DIGEST" \
|
--set image.digest="$DIGEST" \
|
||||||
--set-string rabbitmq.password="$RABBITMQ_PASSWORD" \
|
--set-string rabbitmq.password="$RABBITMQ_PASSWORD" \
|
||||||
--set-string database.password="$DB_PASSWORD" \
|
--set-string database.password="$DB_PASSWORD" \
|
||||||
--set-string database.encryptionSecret="$PR"
|
--set-string database.encryptionSecret="$PR" \
|
||||||
|
--set-string app.name="finance-tracker-pr-$PR"
|
||||||
|
|
||||||
- name: Post preview URLs as PR comment
|
- name: Post preview URLs as PR comment
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""Cascade categories
|
||||||
|
|
||||||
|
Revision ID: 59cebf320c4a
|
||||||
|
Revises: 46b9e702e83f
|
||||||
|
Create Date: 2025-10-30 13:42:44.555284
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '59cebf320c4a'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = '46b9e702e83f'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('category_transaction', sa.Column('category_id', sa.Integer(), nullable=False))
|
||||||
|
op.add_column('category_transaction', sa.Column('transaction_id', sa.Integer(), nullable=False))
|
||||||
|
op.drop_constraint(op.f('category_transaction_ibfk_2'), 'category_transaction', type_='foreignkey')
|
||||||
|
op.drop_constraint(op.f('category_transaction_ibfk_1'), 'category_transaction', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'category_transaction', 'transaction', ['transaction_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'category_transaction', 'categories', ['category_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.drop_column('category_transaction', 'id_category')
|
||||||
|
op.drop_column('category_transaction', 'id_transaction')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('category_transaction', sa.Column('id_transaction', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
|
||||||
|
op.add_column('category_transaction', sa.Column('id_category', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
|
||||||
|
op.drop_constraint(None, 'category_transaction', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'category_transaction', type_='foreignkey')
|
||||||
|
op.create_foreign_key(op.f('category_transaction_ibfk_1'), 'category_transaction', 'categories', ['id_category'], ['id'])
|
||||||
|
op.create_foreign_key(op.f('category_transaction_ibfk_2'), 'category_transaction', 'transaction', ['id_transaction'], ['id'])
|
||||||
|
op.drop_column('category_transaction', 'transaction_id')
|
||||||
|
op.drop_column('category_transaction', 'category_id')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pythonjsonlogger import jsonlogger
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI
|
from fastapi import Depends, FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from prometheus_fastapi_instrumentator import Instrumentator, metrics
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
||||||
from app.services import bank_scraper
|
from app.services import bank_scraper
|
||||||
@@ -15,11 +18,11 @@ from app.api.auth import router as auth_router
|
|||||||
from app.api.csas import router as csas_router
|
from app.api.csas import router as csas_router
|
||||||
from app.api.categories import router as categories_router
|
from app.api.categories import router as categories_router
|
||||||
from app.api.transactions import router as transactions_router
|
from app.api.transactions import router as transactions_router
|
||||||
from app.services.user_service import auth_backend, current_active_verified_user, fastapi_users, get_oauth_provider, UserManager, get_jwt_strategy
|
from app.services.user_service import auth_backend, current_active_verified_user, fastapi_users, get_oauth_provider, \
|
||||||
|
UserManager, get_jwt_strategy
|
||||||
from app.core.security import extract_bearer_token, is_token_revoked, decode_and_verify_jwt
|
from app.core.security import extract_bearer_token, is_token_revoked, decode_and_verify_jwt
|
||||||
from app.services.user_service import SECRET
|
from app.services.user_service import SECRET
|
||||||
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from fastapi_users.db import SQLAlchemyUserDatabase
|
from fastapi_users.db import SQLAlchemyUserDatabase
|
||||||
@@ -31,7 +34,6 @@ sentry_sdk.init(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fastApi = FastAPI()
|
fastApi = FastAPI()
|
||||||
app = fastApi
|
|
||||||
|
|
||||||
# CORS for frontend dev server
|
# CORS for frontend dev server
|
||||||
fastApi.add_middleware(
|
fastApi.add_middleware(
|
||||||
@@ -46,11 +48,38 @@ fastApi.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
prometheus = Instrumentator().instrument(fastApi)
|
||||||
|
|
||||||
|
prometheus.expose(
|
||||||
|
fastApi,
|
||||||
|
endpoint="/metrics",
|
||||||
|
include_in_schema=True,
|
||||||
|
)
|
||||||
|
|
||||||
fastApi.include_router(auth_router)
|
fastApi.include_router(auth_router)
|
||||||
fastApi.include_router(categories_router)
|
fastApi.include_router(categories_router)
|
||||||
fastApi.include_router(transactions_router)
|
fastApi.include_router(transactions_router)
|
||||||
|
|
||||||
logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s %(message)s')
|
|
||||||
|
for h in list(logging.root.handlers):
|
||||||
|
logging.root.removeHandler(h)
|
||||||
|
|
||||||
|
_log_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
_formatter = jsonlogger.JsonFormatter(
|
||||||
|
fmt='%(asctime)s %(levelname)s %(name)s %(message)s %(pathname)s %(lineno)d %(process)d %(thread)d'
|
||||||
|
)
|
||||||
|
_log_handler.setFormatter(_formatter)
|
||||||
|
|
||||||
|
logging.root.setLevel(logging.INFO)
|
||||||
|
logging.root.addHandler(_log_handler)
|
||||||
|
|
||||||
|
|
||||||
|
for _name in ("uvicorn", "uvicorn.error", "uvicorn.access"):
|
||||||
|
_logger = logging.getLogger(_name)
|
||||||
|
_logger.handlers = [_log_handler]
|
||||||
|
_logger.propagate = True
|
||||||
|
|
||||||
|
|
||||||
@fastApi.middleware("http")
|
@fastApi.middleware("http")
|
||||||
async def auth_guard(request: Request, call_next):
|
async def auth_guard(request: Request, call_next):
|
||||||
# Enforce revoked/expired JWTs are rejected globally
|
# Enforce revoked/expired JWTs are rejected globally
|
||||||
@@ -85,9 +114,10 @@ async def log_traffic(request: Request, call_next):
|
|||||||
"process_time": process_time,
|
"process_time": process_time,
|
||||||
"client_host": client_host
|
"client_host": client_host
|
||||||
}
|
}
|
||||||
logging.info(str(log_params))
|
logging.getLogger(__name__).info("http_request", extra=log_params)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
fastApi.include_router(
|
fastApi.include_router(
|
||||||
fastapi_users.get_oauth_router(
|
fastapi_users.get_oauth_router(
|
||||||
get_oauth_provider("MojeID"),
|
get_oauth_provider("MojeID"),
|
||||||
@@ -114,6 +144,7 @@ fastApi.include_router(
|
|||||||
|
|
||||||
fastApi.include_router(csas_router)
|
fastApi.include_router(csas_router)
|
||||||
|
|
||||||
|
|
||||||
# Liveness/root endpoint
|
# Liveness/root endpoint
|
||||||
@fastApi.get("/", include_in_schema=False)
|
@fastApi.get("/", include_in_schema=False)
|
||||||
async def root():
|
async def root():
|
||||||
@@ -136,4 +167,5 @@ async def debug_scrape_csas_all():
|
|||||||
async def debug_scrape_csas_user(user_id: str, user: User = Depends(current_active_verified_user)):
|
async def debug_scrape_csas_user(user_id: str, user: User = Depends(current_active_verified_user)):
|
||||||
logging.info("[Debug] Queueing CSAS scrape for single user via HTTP endpoint (Celery) | user_id=%s", user_id)
|
logging.info("[Debug] Queueing CSAS scrape for single user via HTTP endpoint (Celery) | user_id=%s", user_id)
|
||||||
task = load_transactions.delay(user_id)
|
task = load_transactions.delay(user_id)
|
||||||
return {"status": "queued", "action": "csas_scrape_single", "user_id": user_id, "task_id": getattr(task, 'id', None)}
|
return {"status": "queued", "action": "csas_scrape_single", "user_id": user_id,
|
||||||
|
"task_id": getattr(task, 'id', None)}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from app.core.base import Base
|
|||||||
association_table = Table(
|
association_table = Table(
|
||||||
"category_transaction",
|
"category_transaction",
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("id_category", Integer, ForeignKey("categories.id")),
|
Column("category_id", Integer, ForeignKey("categories.id", ondelete="CASCADE"), primary_key=True),
|
||||||
Column("id_transaction", Integer, ForeignKey("transaction.id"))
|
Column("transaction_id", Integer, ForeignKey("transaction.id", ondelete="CASCADE"), primary_key=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ class Transaction(Base):
|
|||||||
|
|
||||||
# Relationship
|
# Relationship
|
||||||
user = relationship("User", back_populates="transactions")
|
user = relationship("User", back_populates="transactions")
|
||||||
categories = relationship("Category", secondary=association_table, back_populates="transactions")
|
categories = relationship("Category", secondary=association_table, back_populates="transactions", passive_deletes=True)
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ MarkupSafe==3.0.2
|
|||||||
multidict==6.6.4
|
multidict==6.6.4
|
||||||
packaging==25.0
|
packaging==25.0
|
||||||
pamqp==3.3.0
|
pamqp==3.3.0
|
||||||
|
prometheus-fastapi-instrumentator==7.1.0
|
||||||
|
prometheus_client==0.23.1
|
||||||
prompt_toolkit==3.0.52
|
prompt_toolkit==3.0.52
|
||||||
propcache==0.3.2
|
propcache==0.3.2
|
||||||
pwdlib==0.2.1
|
pwdlib==0.2.1
|
||||||
@@ -68,3 +70,4 @@ watchfiles==1.1.0
|
|||||||
wcwidth==0.2.14
|
wcwidth==0.2.14
|
||||||
websockets==15.0.1
|
websockets==15.0.1
|
||||||
yarl==1.20.1
|
yarl==1.20.1
|
||||||
|
python-json-logger==2.0.7
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: {{ .Values.app.name }}
|
app: {{ .Values.app.name }}
|
||||||
|
endpoint: metrics
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: {{ .Values.app.name }}
|
app: {{ .Values.app.name }}
|
||||||
|
endpoint: metrics
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Values.app.name }}
|
- name: {{ .Values.app.name }}
|
||||||
|
|||||||
14
7project/charts/myapp-chart/templates/monitoring.yaml
Normal file
14
7project/charts/myapp-chart/templates/monitoring.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: fastapi-servicemonitor
|
||||||
|
labels:
|
||||||
|
release: kube-prometheus-stack
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ .Values.app.name }}
|
||||||
|
endpoints:
|
||||||
|
- port: http
|
||||||
|
path: /metrics
|
||||||
|
interval: 15s
|
||||||
@@ -2,9 +2,12 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Values.app.name }}
|
name: {{ .Values.app.name }}
|
||||||
|
labels:
|
||||||
|
app: {{ .Values.app.name }}
|
||||||
spec:
|
spec:
|
||||||
ports:
|
ports:
|
||||||
- port: {{ .Values.service.port }}
|
- name: http
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
targetPort: {{ .Values.app.port }}
|
targetPort: {{ .Values.app.port }}
|
||||||
selector:
|
selector:
|
||||||
app: {{ .Values.app.name }}
|
app: {{ .Values.app.name }}
|
||||||
|
|||||||
@@ -4,21 +4,4 @@ import react from '@vitejs/plugin-react'
|
|||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
|
||||||
proxy: {
|
|
||||||
// We'll proxy any request that starts with '/api-cnb'
|
|
||||||
'/api-cnb': {
|
|
||||||
// This is the real API server we want to talk to
|
|
||||||
target: 'https://api.cnb.cz',
|
|
||||||
|
|
||||||
// This is crucial: it changes the 'Origin' header
|
|
||||||
// to match the target, so the CNB server is happy.
|
|
||||||
changeOrigin: true,
|
|
||||||
|
|
||||||
// This rewrites the request path. It removes the '/api-cnb' part
|
|
||||||
// so the CNB server gets the correct path ('/cnbapi/exrates/daily...').
|
|
||||||
rewrite: (path) => path.replace(/^\/api-cnb/, ''),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ The tracker should not store the transactions in the database - security vulnera
|
|||||||
|
|
||||||
Last 3 minutes of the meeting, summarize action items.
|
Last 3 minutes of the meeting, summarize action items.
|
||||||
|
|
||||||
- [ ] Dont store data in database (security) - Load it on login (from CSAS API and local database), load automatically with email
|
- [ ] Change the name on frontend from 7project
|
||||||
- [ ] Go through the checklist
|
- [ ] Finalize the funcionality and everyting in the code part
|
||||||
- [ ] Look for possible APIs (like stocks or financial details whatever)
|
- [ ] Try to finalize report with focus on reproducibility
|
||||||
- [ ] Report
|
- [ ] More high level explanation of the workflow in the report
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -64,3 +64,21 @@ resource "kubectl_manifest" "argocd-tunnel-bind" {
|
|||||||
base_domain = var.cloudflare_domain
|
base_domain = var.cloudflare_domain
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "helm_release" "loki_stack" {
|
||||||
|
name = "loki-stack"
|
||||||
|
repository = "https://grafana.github.io/helm-charts"
|
||||||
|
chart = "loki-stack"
|
||||||
|
namespace = kubernetes_namespace.monitoring.metadata[0].name
|
||||||
|
version = "2.9.12"
|
||||||
|
|
||||||
|
set = [{
|
||||||
|
name = "grafana.enabled"
|
||||||
|
value = "false"
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
helm_release.kube_prometheus_stack
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user