Skip to content
This repository has been archived by the owner on Nov 13, 2023. It is now read-only.

Commit

Permalink
Stl 1821 erica api v.2 (#5)
Browse files Browse the repository at this point in the history
* STL-1821 | API v1 restructured, leaned to FastAPI

* STL-1821 | Removed prefixes from tax and unlock_code ressources

* STL-1821 | Added v2

* STL-1821 | Variables renamed

* STL-1821 | Changes after 1st review

* STL-1821 | Changes after 2nd review

* STL-1821 | Removed code smell

* STL-1821 | Removed code smell

* STL-1821 | Added FastAPI-Versioning | Update unlock_code to fsc in v2 | Model classes in different files

* STL-1821 | Removed code smells

* STL-1821 | Removed code duplication

* STL-1821 | Removed FastAPI-Versioning library and usage | Added specific response models for HTTP 200 in v2 | Reverted pipfiles to match origin/main

* STL-1821 | Removed code smell

* STL-1821 | Pipfile.lock everted to state in origin/main

* STL-1821 | Added enum Status | Modified base class for get from queue

* STL-1821 | Changed 01 to v2 | Added "Success.." keyword to response models | Update enum status
  • Loading branch information
VictorDelCampo authored Feb 21, 2022
1 parent 78d60d4 commit c332b6f
Show file tree
Hide file tree
Showing 27 changed files with 658 additions and 262 deletions.
6 changes: 5 additions & 1 deletion erica/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from prometheus_client import Gauge
from prometheus_fastapi_instrumentator import Instrumentator

from erica.api.api import api_router
from erica.pyeric.eric import verify_using_stick

app = FastAPI()
Expand Down Expand Up @@ -34,7 +35,10 @@ def get(cls):
up_metric.labels(job='erica').set(1.0) # Always 1 when the erica_app is running.
up_metric.labels(job='dongle').set_function(DongleStatus.get)

# Add router
app.include_router(api_router)

# Add default metrics and expose endpoint.
Instrumentator().instrument(app).expose(app)

from erica import routes

9 changes: 9 additions & 0 deletions erica/api/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi import APIRouter

from erica.api.v1.api_v1 import api_router_01
from erica.api.v2.api_v2 import api_router_02

api_router = APIRouter()

api_router.include_router(api_router_01)
api_router.include_router(api_router_02)
15 changes: 15 additions & 0 deletions erica/api/v1/api_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from fastapi import APIRouter

from erica.api.v1.endpoints import ping
from erica.api.v1.endpoints.rente import tax, est, address, unlock_code
from erica.api.v1.endpoints.grundsteuer import grundsteuer

api_router_01 = APIRouter()
api_router_01.prefix = '/01'

api_router_01.include_router(ping.router, prefix="/ping", tags=["Ping"])
api_router_01.include_router(address.router, prefix="/address", tags=["Adresse"])
api_router_01.include_router(est.router, tags=["Steuererklärung"])
api_router_01.include_router(grundsteuer.router, prefix="/grundsteuer", tags=["Grundsteuer"])
api_router_01.include_router(tax.router, tags=["Finanzverwaltung"])
api_router_01.include_router(unlock_code.router, tags=["Freischaltcode"])
19 changes: 19 additions & 0 deletions erica/api/v1/endpoints/grundsteuer/grundsteuer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from fastapi import status, APIRouter
from erica.request_processing.erica_input.v1.erica_input import GrundsteuerData
router = APIRouter()


@router.post('/', status_code=status.HTTP_201_CREATED)
def send_grundsteuer(grundsteuer_data: GrundsteuerData, include_elster_responses: bool = False):
"""
The Grundsteuer data is validated and then send to ELSTER using ERiC. If it is successful, this should return a 201
HTTP response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf
If there is any error with the validation, this should return a 400 response. If the validation failed with
{‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]}
or a 400 repsonse for other client errors and a 500 response for server errors with
{‘code’ : int,‘message’: str,‘description’: str}
:param grundsteuer_data: the JSON input data for the land tax declaration
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
return {"request": "successful"}
10 changes: 10 additions & 0 deletions erica/api/v1/endpoints/ping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import APIRouter

router = APIRouter()


@router.get('/')
def ping():
"""Simple route that can be used to check if the app has started.
"""
return 'pong'
17 changes: 17 additions & 0 deletions erica/api/v1/endpoints/rente/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fastapi import status, APIRouter
from erica.request_processing.erica_input.v1.erica_input import GetAddressData

router = APIRouter()


@router.post('/', status_code=status.HTTP_200_OK)
def get_address(get_address: GetAddressData, include_elster_responses: bool = False):
"""
The address data of the given idnr is requested at Elster and returned. Be aware, that you need a permission
(aka an activated unlock_code) to query a person's data.
:param get_address: the JSON input data for the request
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
# For now, we do not allow data requests as we cannot guarantee that Elster already has the relevant data gathered
raise NotImplementedError()
49 changes: 49 additions & 0 deletions erica/api/v1/endpoints/rente/est.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import logging

from fastapi import HTTPException, status, APIRouter
from erica.request_processing.erica_input.v1.erica_input import EstData
from erica.pyeric.eric_errors import EricProcessNotSuccessful
from erica.request_processing.requests_controller import EstValidationRequestController, EstRequestController

router = APIRouter()


@router.get('/est_validations')
def validate_est(est: EstData, include_elster_responses: bool = False):
"""
Data for a Est is validated using ERiC. If the validation is successful then this should return
a 200 HTTP response with {'success': bool, 'est': est}. Otherwise this should return a 400 response if the
validation failed with {‘code’ : int,‘message’: str,‘description’: str,‘‘validation_problems’ : [{‘code’: int,
‘message’: str}]} or a 400 response for other client errors and a 500 response for server errors with {‘code’ :
int, ‘message’: str, ‘description’: str}
:param est: the JSON input data for the ESt
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
try:
request = EstValidationRequestController(est, include_elster_responses)
return request.process()
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not validate est", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses))


@router.post('/ests', status_code=status.HTTP_201_CREATED)
def send_est(est: EstData, include_elster_responses: bool = False):
"""
An Est is validated and then send to ELSTER using ERiC. If it is successful, this should return a 200 HTTP
response with {'transfer_ticket': str, 'pdf': str}. The pdf is base64 encoded binary data of the pdf
If there is any error with the validation, this should return a 400 response. If the validation failed with
{‘code’ : int,‘message’: str,‘description’: str, ‘validation_problems’ : [{‘code’: int, ‘message’: str}]}
or a 400 repsonse for other client errors and a 500 response for server errors with
{‘code’ : int,‘message’: str,‘description’: str}
:param est: the JSON input data for the ESt
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
try:
request = EstRequestController(est, include_elster_responses)
return request.process()
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not send est", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses))
31 changes: 31 additions & 0 deletions erica/api/v1/endpoints/rente/tax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging

from fastapi import HTTPException, status, APIRouter
from starlette.responses import FileResponse
from erica.pyeric.eric_errors import EricProcessNotSuccessful
from erica.request_processing.requests_controller import CheckTaxNumberRequestController

router = APIRouter()


@router.get('/tax_number_validity/{state_abbreviation}/{tax_number}', status_code=status.HTTP_200_OK)
def is_valid_tax_number(state_abbreviation: str, tax_number: str):
"""
Validates a tax number and returns the result
:param state_abbreviation: Abbreviation of the state of the tax office
:param tax_number: Tax number in the standard schema
"""
try:
return CheckTaxNumberRequestController.process(state_abbreviation, tax_number)
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not validate tax number", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_responses=False))


@router.get('/tax_offices/', status_code=status.HTTP_200_OK)
def get_tax_offices():
"""
The list of tax offices for all states is requested and returned.
"""
return FileResponse("erica/static/tax_offices.json")
65 changes: 65 additions & 0 deletions erica/api/v1/endpoints/rente/unlock_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import logging

from fastapi import HTTPException, status, APIRouter
from erica.request_processing.erica_input.v1.erica_input import UnlockCodeRequestData, UnlockCodeActivationData, \
UnlockCodeRevocationData
from erica.pyeric.eric_errors import EricProcessNotSuccessful
from erica.request_processing.requests_controller import UnlockCodeRequestController, \
UnlockCodeActivationRequestController, UnlockCodeRevocationRequestController

router = APIRouter()


@router.post('/unlock_code_requests', status_code=status.HTTP_201_CREATED)
def request_unlock_code(unlock_code_request: UnlockCodeRequestData, include_elster_responses: bool = False):
"""
A new unlock code for the sent id_nr is requested. If everything is successful, return a 200 HTTP response
with {'elster_request_id': str, 'idnr': str}. If there is any error return a 400 repsonse for client
errors and a 500 response for server errors with {‘code’ : int, ‘message’: str,‘description’: str}
:param unlock_code_request: the JSON input data for the request
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
try:
request = UnlockCodeRequestController(unlock_code_request, include_elster_responses)
return request.process()
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not request unlock code", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses))


@router.post('/unlock_code_activations', status_code=status.HTTP_201_CREATED)
def activate_unlock_code(unlock_code_activation: UnlockCodeActivationData, include_elster_responses: bool = False):
"""
An unlock code is used activated for the sent id_nr. If everything is successful, return a 200 HTTP response
with {'id_nr': str}. If there is any error return a 400 response for client
errors and a 500 response for server errors with {‘code’ : int,‘message’: str,‘description’: str}.
:param unlock_code_activation: the JSON input data for the activation
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
try:
request = UnlockCodeActivationRequestController(unlock_code_activation, include_elster_responses)
return request.process()
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not activate unlock code", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses))


@router.post('/unlock_code_revocations', status_code=status.HTTP_200_OK)
def revoke_unlock_code(unlock_code_revocation: UnlockCodeRevocationData, include_elster_responses: bool = False):
"""
The permission at Elster is revoked. If everything is successful, return a 200 HTTP response
with {'id_nr': str}. If there is any error return a 400 response for client
errors and a 500 response for server errors with {‘code’ : int, ‘message’: str, ‘description’: str}.
Especially, an error is returned if there is no activated or not activated unlock_code for the corresponding idnr.
:param unlock_code_revocation: the JSON input data for the revocation
:param include_elster_responses: query parameter which indicates whether the ERiC/Server response are returned
"""
try:
request = UnlockCodeRevocationRequestController(unlock_code_revocation, include_elster_responses)
return request.process()
except EricProcessNotSuccessful as e:
logging.getLogger().info("Could not revoke unlock code", exc_info=True)
raise HTTPException(status_code=422, detail=e.generate_error_response(include_elster_responses))
16 changes: 16 additions & 0 deletions erica/api/v2/api_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi import APIRouter

from erica.api.v2.endpoints import ping
from erica.api.v2.endpoints.rente import fsc, est, tax


api_router_02 = APIRouter()
api_router_02.prefix = '/v2'

api_router_02.include_router(ping.router, prefix="/ping", tags=["Ping"])
api_router_02.include_router(est.router, tags=["Steuererklärung"])
api_router_02.include_router(tax.router, tags=["Finanzverwaltung"])
api_router_02.include_router(fsc.router, prefix="/fsc", tags=["Freischaltcode"])



10 changes: 10 additions & 0 deletions erica/api/v2/endpoints/ping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from fastapi import APIRouter

router = APIRouter()


@router.get('/')
def ping():
"""Simple route that can be used to check if the app has started.
"""
return 'pong'
64 changes: 64 additions & 0 deletions erica/api/v2/endpoints/rente/est.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging

from fastapi import status, APIRouter
from starlette.responses import JSONResponse

from erica.api.v2.responses.model import response_model_post_to_queue, response_model_get_est_validation_from_queue, \
response_model_get_send_est_from_queue
from erica.pyeric.utils import generate_dummy_error_response
from erica.request_processing.erica_input.v2.erica_input import EstDataWithTtl

router = APIRouter()


@router.post('/est_validations', status_code=status.HTTP_201_CREATED, responses=response_model_post_to_queue)
def validate_est(est_data_ttl: EstDataWithTtl):
"""
Route for validation of a tax declaration using the job queue.
:param est_data_ttl: payload with TTL, JSON input data for the ESt and declaration year.
"""
try:
raise NotImplementedError()
except NotImplementedError:
logging.getLogger().info("Could not validate est", exc_info=True)
return JSONResponse(status_code=422, content=generate_dummy_error_response())


@router.get('/est_validations/{request_id}', status_code=status.HTTP_200_OK,
responses=response_model_get_est_validation_from_queue)
def get_validate_est_job(request_id: str):
"""
Route for retrieving job status of a tax declaration validation from the queue.
:param request_id: the id of the job.
"""
try:
raise NotImplementedError()
except NotImplementedError:
logging.getLogger().info("Could not retrieve status of job " + request_id, exc_info=True)
return JSONResponse(status_code=500, content=generate_dummy_error_response())


@router.post('/ests', status_code=status.HTTP_201_CREATED, responses=response_model_post_to_queue)
def send_est(est_data_ttl: EstDataWithTtl):
"""
Route for sending a tax declaration using the job queue.
:param est_data_ttl: payload with TTL, JSON input data for the ESt and declaration year.
"""
try:
raise NotImplementedError()
except NotImplementedError:
logging.getLogger().info("Could not send est", exc_info=True)
return JSONResponse(status_code=422, content=generate_dummy_error_response())


@router.get('/ests/{request_id}', status_code=status.HTTP_200_OK, responses=response_model_get_send_est_from_queue)
def get_send_est_job(request_id: str):
"""
Route for retrieving job status of a sent tax declaration from the queue.
:param request_id: the id of the job.
"""
try:
raise NotImplementedError()
except NotImplementedError:
logging.getLogger().info("Could not retrieve status of job " + request_id, exc_info=True)
return JSONResponse(status_code=500, content=generate_dummy_error_response())
Loading

0 comments on commit c332b6f

Please sign in to comment.