Spaces:
Sleeping
Sleeping
Commit ·
f8bb6ab
1
Parent(s): 973f6f3
feat add buildspec
Browse files- README.md +1 -1
- app/util/db_utils.py +70 -0
- app/util/japan_multientry_visa_letter_generator.py +26 -7
- buildspec.yml +37 -0
- docker-compose.yaml +2 -0
- requirements.txt +5 -1
README.md
CHANGED
|
@@ -38,7 +38,7 @@ It currently supports:
|
|
| 38 |
| `/generate/japan-multientry` | POST | Generate a Japan multi-entry visa support letter |
|
| 39 |
| `/generate/sponsorship` | POST | Generate sponsorship letter |
|
| 40 |
| `/generate/housewife-statement` | POST | Generate housewife statement letter |
|
| 41 |
-
| `/generate/passport-collection | POST | Generate passport collection consent letter |
|
| 42 |
|
| 43 |
---
|
| 44 |
|
|
|
|
| 38 |
| `/generate/japan-multientry` | POST | Generate a Japan multi-entry visa support letter |
|
| 39 |
| `/generate/sponsorship` | POST | Generate sponsorship letter |
|
| 40 |
| `/generate/housewife-statement` | POST | Generate housewife statement letter |
|
| 41 |
+
| `/generate/passport-collection` | POST | Generate passport collection consent letter |
|
| 42 |
|
| 43 |
---
|
| 44 |
|
app/util/db_utils.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from sqlalchemy import create_engine, text
|
| 3 |
+
import logging
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
from sqlalchemy.engine import URL
|
| 6 |
+
from typing import List, Dict, Any
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
class DBManager:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
load_dotenv()
|
| 12 |
+
USERNAME = os.getenv("DB_USERNAME")
|
| 13 |
+
PASSWORD = os.getenv("DB_PASSWORD")
|
| 14 |
+
HOST = os.getenv("DB_HOST")
|
| 15 |
+
NAME = os.getenv("DB_NAME")
|
| 16 |
+
PORT = os.getenv("DB_PORT")
|
| 17 |
+
|
| 18 |
+
if not all([USERNAME, PASSWORD, HOST, NAME]):
|
| 19 |
+
raise ValueError("One or more database environment variables are not set.")
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
connection_url = URL.create(
|
| 23 |
+
drivername="postgresql+psycopg2",
|
| 24 |
+
username=USERNAME,
|
| 25 |
+
password=PASSWORD,
|
| 26 |
+
host=HOST,
|
| 27 |
+
port=PORT,
|
| 28 |
+
database=NAME,
|
| 29 |
+
# query={"sslmode": "require"} # kalau perlu SSL
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
self.engine = create_engine(connection_url, pool_pre_ping=True)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
try:
|
| 36 |
+
safe_url = connection_url.render_as_string(hide_password=True)
|
| 37 |
+
except Exception:
|
| 38 |
+
safe_url = str(connection_url).replace(PASSWORD, "***")
|
| 39 |
+
|
| 40 |
+
logging.info(f"Database engine created with URL: {safe_url}")
|
| 41 |
+
|
| 42 |
+
def _execute_query(self, query: str, params: dict = None) -> pd.DataFrame:
|
| 43 |
+
"""Executes a SQL query and returns the result as a pandas DataFrame."""
|
| 44 |
+
try:
|
| 45 |
+
with self.engine.connect() as connection:
|
| 46 |
+
df = pd.read_sql_query(sql=text(query), con=connection, params=params)
|
| 47 |
+
return df
|
| 48 |
+
except Exception as e:
|
| 49 |
+
logging.error(f"Failed to execute query: {e}", exc_info=True)
|
| 50 |
+
return pd.DataFrame()
|
| 51 |
+
|
| 52 |
+
def get_info_for_japan_multientry(self, application_id: int) -> pd.DataFrame:
|
| 53 |
+
query = """
|
| 54 |
+
(
|
| 55 |
+
select
|
| 56 |
+
'name' as name,
|
| 57 |
+
p.first_name || ' '|| p.last_name as value
|
| 58 |
+
from
|
| 59 |
+
application a
|
| 60 |
+
join profile p on p.id = a.profile_id
|
| 61 |
+
where a.id = :application_id)
|
| 62 |
+
union
|
| 63 |
+
select d.name, ad.value from service_document sd
|
| 64 |
+
join "document" d on d.id = sd.document_id
|
| 65 |
+
join application_document ad ON ad.document_id = d.id
|
| 66 |
+
--join document_extraction_result der on der.application_document_id = ad.id
|
| 67 |
+
where service_id = 292 and ad.application_id = :application_id and d.name in ('Current Residential Address in Home Country', 'E-mail address', 'Your Phone Number');
|
| 68 |
+
"""
|
| 69 |
+
params = {"application_id": application_id}
|
| 70 |
+
return self._execute_query(query, params)
|
app/util/japan_multientry_visa_letter_generator.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import datetime
|
| 2 |
from fpdf import FPDF
|
| 3 |
from .pdf_document_generator import PDFDocumentGenerator # The fpdf2-based one
|
| 4 |
-
|
|
|
|
| 5 |
class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
|
| 6 |
"""
|
| 7 |
Generates the specific PDF for the Japan Multiple Entry Visa application
|
|
@@ -15,13 +16,31 @@ class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
|
|
| 15 |
Creates the full PDF document for the Japan visa letter
|
| 16 |
by adding content to the FPDF object.
|
| 17 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
name = self.data.get("name", "YOUR NAME")
|
| 20 |
-
address = self.data.get("address", "YOUR ADDRESS")
|
| 21 |
-
city = self.data.get("city", "CITY")
|
| 22 |
-
postal_code = self.data.get("postal_code", "POSTAL CODE")
|
| 23 |
-
email = self.data.get("email", "your.email@example.com")
|
| 24 |
-
phone = self.data.get("phone", "+123456789")
|
| 25 |
|
| 26 |
today = datetime.date.today()
|
| 27 |
formatted_date = today.strftime("%d, %B, %Y")
|
|
|
|
| 1 |
import datetime
|
| 2 |
from fpdf import FPDF
|
| 3 |
from .pdf_document_generator import PDFDocumentGenerator # The fpdf2-based one
|
| 4 |
+
from .db_utils import DBManager
|
| 5 |
+
import pandas as pd
|
| 6 |
class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
|
| 7 |
"""
|
| 8 |
Generates the specific PDF for the Japan Multiple Entry Visa application
|
|
|
|
| 16 |
Creates the full PDF document for the Japan visa letter
|
| 17 |
by adding content to the FPDF object.
|
| 18 |
"""
|
| 19 |
+
|
| 20 |
+
if self.data.get("application_id"):
|
| 21 |
+
db_manager = DBManager()
|
| 22 |
+
info_df = db_manager.get_info_for_japan_multientry(self.data["application_id"])
|
| 23 |
+
raw_data_dict = pd.Series(info_df.value.values, index=info_df.name).to_dict()
|
| 24 |
+
|
| 25 |
+
data_to_use = {}
|
| 26 |
+
data_to_use["name"] = raw_data_dict.get("name")
|
| 27 |
+
data_to_use["address"] = raw_data_dict.get("Current Residential Address in Home Country")
|
| 28 |
+
data_to_use["email"] = raw_data_dict.get("E-mail address")
|
| 29 |
+
|
| 30 |
+
phone_raw = raw_data_dict.get("Your Phone Number")
|
| 31 |
+
if phone_raw and not phone_raw.startswith("+"):
|
| 32 |
+
data_to_use["phone"] = f"+{phone_raw}"
|
| 33 |
+
else:
|
| 34 |
+
data_to_use["phone"] = phone_raw
|
| 35 |
+
|
| 36 |
+
self.data = data_to_use
|
| 37 |
|
| 38 |
+
name = self.data.get("name", "[YOUR NAME]")
|
| 39 |
+
address = self.data.get("address", "[YOUR ADDRESS]")
|
| 40 |
+
city = self.data.get("city", "[CITY]")
|
| 41 |
+
postal_code = self.data.get("postal_code", "[POSTAL CODE]")
|
| 42 |
+
email = self.data.get("email", "[your.email@example.com]")
|
| 43 |
+
phone = self.data.get("phone", "[+123456789]")
|
| 44 |
|
| 45 |
today = datetime.date.today()
|
| 46 |
formatted_date = today.strftime("%d, %B, %Y")
|
buildspec.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 0.2
|
| 2 |
+
|
| 3 |
+
phases:
|
| 4 |
+
pre_build:
|
| 5 |
+
commands:
|
| 6 |
+
- echo Logging in to Amazon ECR...
|
| 7 |
+
- aws --version
|
| 8 |
+
- docker version
|
| 9 |
+
- echo $AWS_REGION
|
| 10 |
+
- aws configure list
|
| 11 |
+
- aws --debug ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin 825765383758.dkr.ecr.$AWS_REGION.amazonaws.com
|
| 12 |
+
|
| 13 |
+
build:
|
| 14 |
+
commands:
|
| 15 |
+
- echo Build started on `date`
|
| 16 |
+
- echo $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY $APP_ENV
|
| 17 |
+
- echo Building the Docker image...
|
| 18 |
+
- |
|
| 19 |
+
docker build \
|
| 20 |
+
-t $CONTAINER_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION \
|
| 21 |
+
. \
|
| 22 |
+
--build-arg AWS_ACCESS_KEY_ID \
|
| 23 |
+
--build-arg AWS_SECRET_ACCESS_KEY \
|
| 24 |
+
--build-arg APP_ENV
|
| 25 |
+
- docker tag $CONTAINER_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION 825765383758.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
|
| 26 |
+
|
| 27 |
+
post_build:
|
| 28 |
+
commands:
|
| 29 |
+
- echo Pushing the Docker image to Amazon ECR...
|
| 30 |
+
- docker push 825765383758.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
|
| 31 |
+
- echo Writing image definitions file...
|
| 32 |
+
- printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME 825765383758.dkr.ecr.$AWS_REGION.amazonaws.com/$CONTAINER_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION > imagedefinitions.json
|
| 33 |
+
|
| 34 |
+
artifacts:
|
| 35 |
+
files:
|
| 36 |
+
- imagedefinitions.json
|
| 37 |
+
- echo Writing image definitions file...
|
docker-compose.yaml
CHANGED
|
@@ -5,6 +5,8 @@ services:
|
|
| 5 |
build: .
|
| 6 |
ports:
|
| 7 |
- "7860:7860"
|
|
|
|
|
|
|
| 8 |
environment:
|
| 9 |
- PORT=7860
|
| 10 |
- PLAYWRIGHT_BROWSERS_PATH=/home/user/.cache/ms-playwright
|
|
|
|
| 5 |
build: .
|
| 6 |
ports:
|
| 7 |
- "7860:7860"
|
| 8 |
+
env_file:
|
| 9 |
+
- .env
|
| 10 |
environment:
|
| 11 |
- PORT=7860
|
| 12 |
- PLAYWRIGHT_BROWSERS_PATH=/home/user/.cache/ms-playwright
|
requirements.txt
CHANGED
|
@@ -11,4 +11,8 @@ beautifulsoup4
|
|
| 11 |
pymupdf
|
| 12 |
flask[async]
|
| 13 |
pandas
|
| 14 |
-
fpdf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
pymupdf
|
| 12 |
flask[async]
|
| 13 |
pandas
|
| 14 |
+
fpdf
|
| 15 |
+
pandas
|
| 16 |
+
|
| 17 |
+
SQLAlchemy
|
| 18 |
+
psycopg2-binary
|