qr-api/app/main.py
Argiris Deligiannidis 51823b2c10
All checks were successful
continuous-integration/drone/push Build is passing
Create dotenv token value
2024-02-03 14:00:25 +02:00

228 lines
6.6 KiB
Python

import os
import io
import fitz
import qrcode
import datetime
import logging
import urllib.parse
from typing import Optional
from dotenv import load_dotenv
from fastapi.responses import FileResponse
from fastapi import FastAPI, File, UploadFile, Response
load_dotenv()
SERVICE_URL = os.getenv('SERVICE_URL')
DESTINATION = os.getenv('DESTINATION')
CHUNK_SIZE = int(os.getenv('CHUNK_SIZE'))
env_TOKEN = os.getenv('TOKEN')
log = logging.getLogger(__name__)
app = FastAPI()
def urlencode(str):
return urllib.parse.quote(str)
def urldecode(str):
return urllib.parse.unquote(str)
def make_qrcode(qr_data):
"""
Generate a QR code from the given qr_data.
Args:
qr_data (str): The data to be encoded in the QR code.
Returns:
io.BytesIO: The QR code image as a BytesIO object.
"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=6,
border=2,
)
qr.add_data(qr_data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
fp = io.BytesIO()
img.save(fp, "PNG")
return fp
def create_qr_rectangle(preset, page):
"""
Create a QR rectangle based on the preset and page provided.
Parameters:
- preset (str): The preset for the QR rectangle.
- page (Page): The page on which the QR rectangle will be created.
Returns:
- rect (Rect): The created QR rectangle.
"""
w = page.rect.width # page width
if preset == "inv1":
margin = 15
left = w*0.40
elif preset == "inv2":
margin = 20
left = w*0.88
else:
margin = 0
left = 0
rect = fitz.Rect(left, margin, left + 65, margin + 65)
return rect
async def chunked_copy(src, dst):
"""
Asynchronously copies the contents of the source file to the destination file
in chunks.
Args:
src: The source file object.
dst: The destination file path.
Returns:
None
"""
await src.seek(0)
with open(dst, "wb") as buffer:
while True:
contents = await src.read(CHUNK_SIZE)
if not contents:
log.info(f"Src completely consumed\n")
break
log.info(f"Consumed {len(contents)} bytes from Src file\n")
buffer.write(contents)
@app.post("/signinvoice/")
async def sign_invoice(
file: UploadFile = File(...),
preset: Optional[str] = None,
code_content: Optional[str] = None,
company: Optional[str] = None,
inv_type: Optional[str] = None,
inv_series: Optional[str] = None,
inv_num: Optional[str] = None,
in_all: bool = False
):
"""
Sign an invoice file and generate a QR code for it.
Args:
file (UploadFile): The file to be signed.
preset (str, optional): The preset for the QR code.
code_content (str, optional): The content for the QR code.
company (str, optional): The company name.
inv_type (str, optional): The invoice type.
inv_series (str, optional): The invoice series.
inv_num (str, optional): The invoice number.
in_all (bool): Whether to include the QR code in all pages.
Returns:
FileResponse: The signed invoice file with QR code.
"""
inv_code = "{}_{}_{}_{}".format(company, inv_type, inv_series, inv_num).replace(" ", "_").replace(",", "").replace("#", "")
inv_url = "{}/getinvoice/?inv_code={}".format(SERVICE_URL, inv_code)
#print("SIGNED URL: {}".format(inv_url))
temp_path = os.path.join(DESTINATION, f"{inv_code}_temp.pdf")
full_path = os.path.join(DESTINATION, f"{inv_code}.pdf")
await chunked_copy(file, temp_path)
if not code_content:
code_content = inv_url
qr_image = make_qrcode(code_content)
doc = fitz.open(temp_path)
add_qr = True
for page in doc:
if add_qr:
rect = create_qr_rectangle(preset, page)
if not page.is_wrapped:
page.wrap_contents()
page.insert_image(rect, stream=qr_image)
if not in_all and page.number == 0:
add_qr = False
doc.save(full_path, deflate=True, garbage=3)
if os.path.isfile(temp_path):
os.remove(temp_path)
print("{} : Returned Signed Invoice".format(datetime.datetime.now()))
return FileResponse(full_path)
@app.get("/getinvoice/")
async def get_invoice(
company: Optional[str] = None,
inv_code: Optional[str] = None,
inv_type: Optional[str] = None,
inv_series: Optional[str] = None,
inv_num: Optional[str] = None,
search_file: bool = False
):
"""
An asynchronous function to retrieve an invoice file based on various parameters.
Args:
company (Optional[str]): The company name.
inv_code (Optional[str]): The invoice code.
inv_type (Optional[str]): The invoice type.
inv_series (Optional[str]): The invoice series.
inv_num (Optional[str]): The invoice number.
search_file (bool): Flag to indicate whether to search for the file.
Returns:
Union[FileResponse, Dict[str, str]]: Returns the invoice file if found, otherwise returns a message.
"""
if search_file:
inv_code = "{}_{}_{}_{}".format(company, inv_type, inv_series, inv_num).replace(" ", "_").replace(",", "").replace("#", "")
if inv_code:
full_path = os.path.join(DESTINATION, "{}.pdf".format(inv_code))
if os.path.isfile(full_path):
print("FETCHING INVOICE: {} | {}".format(inv_code, full_path))
return FileResponse(full_path)
else:
print("{} : No invoice found".format(datetime.datetime.now()))
return {"message": "No invoice found"}
@app.get("/createqr/")
async def create_qr_code(code_content: Optional[str] = None, token: Optional[str] = None):
"""
Create a QR code from the given code_content.
Args:
code_content (str, optional): The content to be encoded in the QR code. Defaults to None.
Returns:
Response: The QR code image as a Response object with media type "image/png".
"""
if token == env_TOKEN:
if code_content:
qr_image = make_qrcode(code_content)
print("{} : QR Created".format(datetime.datetime.now()))
return Response(content=qr_image.getvalue(), media_type="image/png")
else:
print("{} : QR Code Content not provided".format(datetime.datetime.now()))
return "QR Code Content not provided"
else:
return "Unauthorized"
@app.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse("favicon.ico")