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')) 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 == 'ZPOclCF5od59SgW6PLM2': 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")