Compare commits
2 Commits
ddc10c25a0
...
721fbeed66
Author | SHA1 | Date | |
---|---|---|---|
|
721fbeed66 | ||
|
d39e8d727b |
30
.drone.yml
Normal file
30
.drone.yml
Normal file
@ -0,0 +1,30 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
steps:
|
||||
- name: container-build
|
||||
image: 'docker:dind'
|
||||
environment:
|
||||
USERNAME:
|
||||
from_secret: docker_username
|
||||
PASSWORD:
|
||||
from_secret: docker_password
|
||||
volumes:
|
||||
- name: dockersock
|
||||
path: /var/run/docker.sock
|
||||
commands:
|
||||
- >-
|
||||
if [ "${DRONE_BRANCH}" = "main" ]; then export
|
||||
BUILD_TAG="latest"; else export BUILD_TAG="${DRONE_BRANCH}"; fi;
|
||||
- docker login -u $USERNAME -p $PASSWORD git.argideli.com
|
||||
- >-
|
||||
docker build -t
|
||||
git.argideli.com/${DRONE_REPO_NAMESPACE}/${DRONE_REPO_NAME}:$BUILD_TAG
|
||||
.
|
||||
- >-
|
||||
docker push
|
||||
git.argideli.com/${DRONE_REPO_NAMESPACE}/${DRONE_REPO_NAME}:$BUILD_TAG
|
||||
volumes:
|
||||
- name: dockersock
|
||||
host:
|
||||
path: /var/run/docker.sock
|
160
.gitignore
vendored
Normal file
160
.gitignore
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
# Use the official Python base image
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /code
|
||||
|
||||
# Copy the requirements file to the working directory
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# Install the Python dependencies
|
||||
RUN pip config --user set global.progress_bar off
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# Copy the application code to the working directory
|
||||
COPY ./app /code/app
|
||||
COPY ./app/dot_env /code/app/.env
|
||||
COPY ./app/favicon.ico /code/favicon.ico
|
||||
COPY ./app/favicon.ico /code/app/favicon.ico
|
||||
|
||||
# Expose the port on which the application will run
|
||||
EXPOSE 80
|
||||
|
||||
# Run the FastAPI application using uvicorn server
|
||||
#CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
45
README.md
45
README.md
@ -1,2 +1,45 @@
|
||||
# qr-api
|
||||
# Python QR Codes API
|
||||
|
||||
Python API for adding QR Codes in invoices
|
||||
|
||||
# Deployment
|
||||
|
||||
docker-compose up
|
||||
|
||||
The signed invoices are saved at the invoices folder, their filenames are inv_code.pdf
|
||||
|
||||
# Example Calls
|
||||
## Sign with QR
|
||||
<ins>HTTP-POST</ins>
|
||||
|
||||
https://qrcode.argideli.com/signinvoice/?company=Quertex&inv_type=tpy&in_all=0&preset=inv2&inv_series=2&inv_num=5578
|
||||
|
||||
The call body is a multipart form-data, containing the actual invoice. Upon signing with the QR code the new file is returned
|
||||
|
||||
<ins>Parameters</ins>
|
||||
|
||||
* file: UploadFile = File(...) = File to be signed
|
||||
* preset: str = Invoice Preset Selection
|
||||
* code_content: str = QR code Data, if empty the code data that will be put inside the QR will be the url to download the invoice
|
||||
* company: str = Invoice type, english letters (ex. Quertex), NOTE: The Upper Case letter WILL be carried to the filename
|
||||
* inv_type: str = Invoice type, english letters (ex. tpy)
|
||||
* inv_series: str = Invoice series, english letters (ex. 2 or B1)
|
||||
* inv_num: str = Invoice Number
|
||||
* in_all: bool = Sign all pages (0|1)
|
||||
|
||||
|
||||
## Get invoice
|
||||
<ins>HTTP-GET</ins>
|
||||
|
||||
https://qrcode.argideli.com/getinvoice/?inv_code=Quertex_tpy_2_5578
|
||||
|
||||
Get a saved invoice by calling an HTTP/GET and providing the filename as the inv_code parameter, alternatevily the identifier can be reconstructed from the company, inv_type, inv_series, inv_num parameters
|
||||
|
||||
<ins>Parameters</ins>
|
||||
|
||||
* inv_code: str = Invoice identifier/Filename without .pdf extention
|
||||
* company: str = Invoice type, english letters (ex. Quertex), NOTE: The Upper Case letter WILL be carried to the filename
|
||||
* inv_type: str = Invoice type, english letters (ex. tpy)
|
||||
* inv_series: str = Invoice series, english letters (ex. 2 or B1)
|
||||
* inv_num: str = Invoice Number
|
||||
* search_file: bool = Get file by reconstructing the filename from (company, inv_type, inv_series, inv_num), (0|1)
|
||||
|
160
app/.gitignore
vendored
Normal file
160
app/.gitignore
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
7
app/dot_env
Normal file
7
app/dot_env
Normal file
@ -0,0 +1,7 @@
|
||||
SERVICE_URL= "https://qrcode.argideli.com"
|
||||
DESTINATION = "/var/invoices/"
|
||||
|
||||
#SERVICE_URL= "http://127.0.0.1:8000"
|
||||
#DESTINATION = "../invoices/"
|
||||
|
||||
CHUNK_SIZE =1048576 # = 2 ** 20 = 1MB
|
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
24
app/favicon.svg
Normal file
24
app/favicon.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 45.973 45.973"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M43.454,18.443h-2.437c-0.453-1.766-1.16-3.42-2.082-4.933l1.752-1.756c0.473-0.473,0.733-1.104,0.733-1.774
|
||||
c0-0.669-0.262-1.301-0.733-1.773l-2.92-2.917c-0.947-0.948-2.602-0.947-3.545-0.001l-1.826,1.815
|
||||
C30.9,6.232,29.296,5.56,27.529,5.128V2.52c0-1.383-1.105-2.52-2.488-2.52h-4.128c-1.383,0-2.471,1.137-2.471,2.52v2.607
|
||||
c-1.766,0.431-3.38,1.104-4.878,1.977l-1.825-1.815c-0.946-0.948-2.602-0.947-3.551-0.001L5.27,8.205
|
||||
C4.802,8.672,4.535,9.318,4.535,9.978c0,0.669,0.259,1.299,0.733,1.772l1.752,1.76c-0.921,1.513-1.629,3.167-2.081,4.933H2.501
|
||||
C1.117,18.443,0,19.555,0,20.935v4.125c0,1.384,1.117,2.471,2.501,2.471h2.438c0.452,1.766,1.159,3.43,2.079,4.943l-1.752,1.763
|
||||
c-0.474,0.473-0.734,1.106-0.734,1.776s0.261,1.303,0.734,1.776l2.92,2.919c0.474,0.473,1.103,0.733,1.772,0.733
|
||||
s1.299-0.261,1.773-0.733l1.833-1.816c1.498,0.873,3.112,1.545,4.878,1.978v2.604c0,1.383,1.088,2.498,2.471,2.498h4.128
|
||||
c1.383,0,2.488-1.115,2.488-2.498v-2.605c1.767-0.432,3.371-1.104,4.869-1.977l1.817,1.812c0.474,0.475,1.104,0.735,1.775,0.735
|
||||
c0.67,0,1.301-0.261,1.774-0.733l2.92-2.917c0.473-0.472,0.732-1.103,0.734-1.772c0-0.67-0.262-1.299-0.734-1.773l-1.75-1.77
|
||||
c0.92-1.514,1.627-3.179,2.08-4.943h2.438c1.383,0,2.52-1.087,2.52-2.471v-4.125C45.973,19.555,44.837,18.443,43.454,18.443z
|
||||
M22.976,30.85c-4.378,0-7.928-3.517-7.928-7.852c0-4.338,3.55-7.85,7.928-7.85c4.379,0,7.931,3.512,7.931,7.85
|
||||
C30.906,27.334,27.355,30.85,22.976,30.85z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
226
app/main.py
Normal file
226
app/main.py
Normal file
@ -0,0 +1,226 @@
|
||||
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")
|
15
docker-compose-local.yml
Normal file
15
docker-compose-local.yml
Normal file
@ -0,0 +1,15 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
qrcode-api:
|
||||
container_name: qrcode-api
|
||||
image: localhost:7776/qrcodes:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./invoices:/var/invoices
|
||||
ports:
|
||||
- 8188:80
|
||||
|
||||
networks:
|
||||
qrcode-net:
|
||||
driver: bridge
|
||||
|
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@ -0,0 +1,32 @@
|
||||
version: '3.5'
|
||||
services:
|
||||
qrcode-api:
|
||||
container_name: qrcode-api
|
||||
image: git.argideli.com/quertex/qr-api:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./invoices:/var/invoices
|
||||
networks:
|
||||
- qrcode-net
|
||||
ports:
|
||||
- 8188:80
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.scope=qrcode-api-master"
|
||||
|
||||
qrcode-api-watchtower:
|
||||
container_name: qrcode-api-watchtower
|
||||
image: containrrr/watchtower
|
||||
networks:
|
||||
- qrcode-net
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./config.json:/config.json
|
||||
command: --interval 300 --scope api
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.scope=qrcode-api-master"
|
||||
|
||||
networks:
|
||||
qrcode-net:
|
||||
name: qrcode-net
|
||||
driver: bridge
|
||||
|
1
invoices/.gitignore
vendored
Normal file
1
invoices/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.pdf
|
18
requirements.txt
Normal file
18
requirements.txt
Normal file
@ -0,0 +1,18 @@
|
||||
annotated-types==0.6.0
|
||||
anyio==4.2.0
|
||||
click==8.1.7
|
||||
fastapi==0.109.0
|
||||
h11==0.14.0
|
||||
idna==3.6
|
||||
pydantic==2.5.3
|
||||
pydantic_core==2.14.6
|
||||
PyMuPDF==1.23.13
|
||||
PyMuPDFb==1.23.9
|
||||
pypng==0.20220715.0
|
||||
python-dotenv==1.0.0
|
||||
python-multipart==0.0.6
|
||||
qrcode==7.4.2
|
||||
sniffio==1.3.0
|
||||
starlette==0.35.1
|
||||
typing_extensions==4.9.0
|
||||
uvicorn==0.25.0
|
6
start_dev_server.sh
Normal file
6
start_dev_server.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#/bin/bash
|
||||
source ./venv/bin/activate
|
||||
cd app
|
||||
uvicorn main:app --reload
|
||||
cd ..
|
||||
deactivate
|
Loading…
x
Reference in New Issue
Block a user