From 078c5c850fd83cd50fecacfe5b8c44e939f782a7 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 05:22:27 +0200 Subject: [PATCH 01/35] Add files via upload --- render.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 render.yaml diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..6a49738 --- /dev/null +++ b/render.yaml @@ -0,0 +1,27 @@ +services: + - type: web + name: jobfinders + runtime: python + region: oregon + plan: free + + buildCommand: pip install -r requirements.txt + startCommand: gunicorn app:app --workers 2 --bind 0.0.0.0:$PORT + + envVars: + - key: FLASK_ENV + value: production + - key: SECRET_KEY + generateValue: true + - key: DATABASE_URL + fromDatabase: + name: jobfinders-db + property: connectionString + + # Runs before every deploy — handles migrations automatically + preDeployCommand: flask db upgrade + +databases: + - name: jobfinders-db + databaseName: jobfinders + plan: free From d7f95400f66f7bc29706ce9b36666cb167442c92 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 05:26:42 +0200 Subject: [PATCH 02/35] Update require.txt --- require.txt | Bin 1152 -> 588 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/require.txt b/require.txt index ba4d1e920fff015fdcd0ef03a212b9c919f567cf..a2731622f2fe03d6fe3766f0d55b684c7bde0b09 100644 GIT binary patch literal 588 zcmYL{J#Hg042Amve22Y&h556cE!3t6kSayWK#@6P#j0TMM3VRwiIDw1Y1OR>Hz6v zVxqkDAzqB*o45znOIPdIp0&OM14tWOh~{Es&Qa|*4P7lQnS-ArsKTheduI|-Jst_- z6vK_Yc*4jX_7O^F)_U?J~4e%SREHfWk0{rHIq-+62jy6NlSE&Uk72A$Y O)aR`x*5Bl>JnKJWPpj$x literal 1152 zcmZvcy-veG5QMiz;!!}@;kTfrK%%0cM2>NSA+du^P{P9l-|YHqkdSps;+vbDot?|~ zS7Eia_H6Gw8@uM2*|A-+&g{?*EcSa)OKbSGa!kg)aM^%q*lS@ZSd~555ubbbD(fA8 z;(W(;GUE4MH`TlN#np4NvC=teTf@=X7CvE5!HH`&@JMwx)W`=#smh#_g`cc#!RHE` zm^QE^uFP`Sa%Y=^sHyzQxy}*l@_gJc{rx+b1xpM$TB7P1s0(K)@v?AD721y6myRyv zTy38tx7<_haHRCzlG@^x`{A%AP@;Op>fjKM5b{7?oO{j6cAzRcle(6gOVH7)(Bhi- zZeT0$B5FxPXi=?&pOebQ74?JfIvw5%^$c4!>d5}=M2@7ngC%Lra3@VIWorjycj>K)eT=-DZ}H_b=gMYhy|^u-i!aiFmmkom|l?cVXeqjRO;p6`o6<6n-o zr$t^qvC6>%B_Vv52OClvdW0*zmRN%6Kg6R?GPxq07^7>weWR-Gi#k>JLd?9g$Vg9L z%AV5KIwEG8GL+J-yIR@Dug)~Ns1vtR4lV5w#Exp3wazFvQCaxuMs9Iz Date: Sat, 9 May 2026 05:29:24 +0200 Subject: [PATCH 03/35] Add files via upload --- render.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/render.yaml b/render.yaml index 6a49738..d5f5f80 100644 --- a/render.yaml +++ b/render.yaml @@ -5,7 +5,7 @@ services: region: oregon plan: free - buildCommand: pip install -r requirements.txt + buildCommand: pip install -r requirements.txt && flask db upgrade startCommand: gunicorn app:app --workers 2 --bind 0.0.0.0:$PORT envVars: @@ -18,9 +18,6 @@ services: name: jobfinders-db property: connectionString - # Runs before every deploy — handles migrations automatically - preDeployCommand: flask db upgrade - databases: - name: jobfinders-db databaseName: jobfinders From 235721cc24c3c4676ec830d186520f9f96be507d Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 05:42:47 +0200 Subject: [PATCH 04/35] Update requirements.txt to add and remove packages --- requirements.txt | Bin 4558 -> 2049 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1872c0741739fb29374a94bce6e7e53e29894bee..2e53b2ca2e4d3238815bb32cc66d4e7c57096541 100644 GIT binary patch literal 2049 zcmY*a%WmX05WE}kKja4_jAr~8ALfuC@FCa*oa`Y-gF|s7?ocGnhduJa-=3ZQ6(HD>EtU zo?lU8uS!6b+hro=qD^GJApy<<8)N5>PGuAiI^*%|YD%)8<=QxG=RUmo#umWJ6dR`a4s(q^)Crz6p^mAqNtPNnajLG^-fAFrhv>jz zstMny^2p%83tqlX49Y%1wlCDN^9&M|KC1y+3>WDvTF3-YLj}>Pasu&t%XQ^?-ouzo z9!^mNpD{_3ELSNk?K7xFl zwL(H>-%Clej@-zF;H-bfFpJgf5Yl$@WqBq?Xx@pueL6N+=d!BSB2rKDFoDEKf)Ed^ z?P>mApMHOohM*>5n1B5FjRv7+oLPmSMjQeCx)0pS#Y<}A#x{K@#=kCBp!yfqr+=*P z?}QN222(OG)#RmuorL?eG%IK;RsJ=9&5_zBA+qt9%Bl{@YI1;L!k-x&y@70^x2)|M zL~}+Zkd%wP7)cWJCejIrDl|#dbWfQ=3=G{tfrn8+mLOb&I)l2U3DF=DxGrwqLO}6e ztSdng&lY>Z(y&<>gcFYMiL0DY%di?3%C6QhU`d5l8~OtOUma9_5;j9mSyakSj!^IG zt1?;-BJ4phWGZ7as*mVG7c32ItgpQEs2^G^yUCXhR6<(3Mff>Kbogv87+8j83hyu? z%(5n1%*2gACwxAT56k%VFt9(79$29rJDz2@Zz`+f@}w}wn^D=w5{okZZPNMORIDGR VhyU>qh~nY_Lf%<%<1v02Jo+iywrJoPd*`zMZzn1J9J+BY)583v+-X>?G{;bo@Hq}|jGOeX!llIbw z>}l44ZDRxZuD3BxXF3IytxR=ul5Z~4Tt8QmL(^7T@D*L7EN^X7Ni1dgtM2U%UcNo( ztMlhKl9}s4!&sJV)qL24T8vIIsz&Il%*;-Hz}+%^J7@=~G2ly`Pit96^F_w?s*gb} z+$%jh=|B%mz$mxRGZRg^mxe~Zfo~(53rX8sq#e=V?Sr&U#D;A-=E2%L-vecv5p~iC zA~c-qF}K(Hdvw4t%1GkG*#zdDv;@{sj=!0XjJrRajt5LEl|N(}R*lV?mqOC%Y?8er zM)>Fa8VebZC4UnKZTcd~@jy#(^+})aYfD{fOY$^ zRi+vba&^`M3Rlo+j?813E6EjRkVdC72fqH2U8)9OL50Lg|4yEC@{9~52RE6&s73o8 zIZnkk>9+UHcDV1lgr7ya(&@cEA=b!~H(l0=kEJH};%k+&VJa*IRq*|%G{7iXvzOjr z)5x&;kU1~CgUX59mpc8V=X3f!eav?wTg{3;e=qw~R-+1dIACxU*z=k2*Yam9kMIf< zoWIR+=ybLeMzW}qE_%aG{vs8*U82zGyj5(&*V4Nl$EcFtNslaaIS25&R9cmOkaowWMPeFXHjVl3x_ z?z<8@Yv}+39g|p_!5`-acaV4AG%BjKJ3@gJb_;7D^@MSy+a8_Ub&gItZE!)pkm;k0 z5|&Dp)P42`hwd!pX5k_-qDV${JLmT4GX1I;#kqU=S!r>j?M@tW)dslolA~cL(hu2 z2yUd`WWUII@~9OOvc;XGQr6KExJPG*-ebhYN{P#rtZXFDTpdn%tQmi)3$mICX07BL zkEOoo#FKY=V4>(B3l`azrmqP+@pPoVm~xF?%qpfWJbGpfb7?DXV8gzJ4DmXiN6hD` za3Mj?jr9uOH`2itaxF_p4Yk-4IuqN{N`3WKzahtTzcxjBA1+XkNc7Nsl<_YfbaPX zHjjH2b?PZ}d&mJYFLL{M%2tXbv1aBDbM-hcjyq)_dn$^NeaHRiBG-D!H)@Ve1MIQchK@^FU8ov8U!zOX zYs&PZ3$P85)puf{l0J40;*Cxt3XPr>TUi`#5p*B-F)9|evAOQ~BTwsYcl1dsy%Td#)0r@;;3p*mqCH4M-nB^eZG@GoMb6zU{J9gu_)@lV zZ39Ctu#oJq`wg?j)|T>eB8&WIg708hbVWvWMF#IBw~)kD&N2Rb60*)#m^rpj OU2r3IJM@C!Ed2+eOS!!O From 4c51e44063019d20ca4a37fc636b640102e67709 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 05:52:25 +0200 Subject: [PATCH 05/35] Add files via upload --- .python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..530fe91 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.9 \ No newline at end of file From cc0a97c0e56cea2c7475cae3284600df30001b31 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 05:56:12 +0200 Subject: [PATCH 06/35] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2e53b2c..30e2eda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -98,6 +98,7 @@ SQLAlchemy~=2.0.40 srsly~=2.5.1 thinc~=8.3.6 threadpoolctl~=3.6.0 +timedelta tqdm~=4.67.1 typer~=0.15.3 typing_extensions~=4.13.2 From 48d6d7fa421efc5096aa725cd4d3416c0013e7a2 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:01:42 +0200 Subject: [PATCH 07/35] Update render.yaml --- render.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/render.yaml b/render.yaml index d5f5f80..ae62226 100644 --- a/render.yaml +++ b/render.yaml @@ -13,7 +13,11 @@ services: value: production - key: SECRET_KEY generateValue: true - - key: DATABASE_URL + - key: PRODUCTION_SQL_DB + fromDatabase: + name: jobfinders-db + property: connectionString + - key: DEV_SQL_DB fromDatabase: name: jobfinders-db property: connectionString @@ -21,4 +25,4 @@ services: databases: - name: jobfinders-db databaseName: jobfinders - plan: free + plan: free \ No newline at end of file From 7e98f51bb84fcfe0e250b549e1e1bde05b6550bb Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:06:23 +0200 Subject: [PATCH 08/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 30e2eda..da7a38f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,7 +63,8 @@ platformdirs~=4.3.7 preshed~=3.0.9 proto-plus~=1.26.1 protobuf~=6.30.2 -propcache~=0.3.1 +propcache~=0.3.1 +psycopg2-binary~=2.9.9 pyasn1~=0.6.1 pyasn1_modules~=0.4.2 pycares~=4.8.0 From c284ac76bbd557bc25fc49990ae0ecb777c5fe3d Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:13:59 +0200 Subject: [PATCH 09/35] Update render.yaml --- render.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/render.yaml b/render.yaml index ae62226..e0fd8e9 100644 --- a/render.yaml +++ b/render.yaml @@ -9,18 +9,19 @@ services: startCommand: gunicorn app:app --workers 2 --bind 0.0.0.0:$PORT envVars: - - key: FLASK_ENV - value: production - - key: SECRET_KEY - generateValue: true - key: PRODUCTION_SQL_DB fromDatabase: name: jobfinders-db property: connectionString - - key: DEV_SQL_DB - fromDatabase: - name: jobfinders-db - property: connectionString + - key: SECRET_KEY + generateValue: true + - key: IS_DEVELOPMENT_SERVER + value: false + - key: HOST_ADDRESSES + fromService: + type: web + name: jobfinders + property: host databases: - name: jobfinders-db From b5f76ba259db1dab09c8b7e36a6d5a8427d5a755 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:14:55 +0200 Subject: [PATCH 10/35] Update __init__.py --- src/config/__init__.py | 51 ++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/config/__init__.py b/src/config/__init__.py index a9da108..fbca8b2 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -6,7 +6,7 @@ class MySQLSettings(BaseSettings): PRODUCTION_DB: str = Field(..., alias="PRODUCTION_SQL_DB") - DEVELOPMENT_DB: str = Field(..., alias="DEV_SQL_DB") + DEVELOPMENT_DB: str = Field(default="", alias="DEV_SQL_DB") model_config = SettingsConfigDict( env_file=".env.developer", @@ -18,8 +18,9 @@ class MySQLSettings(BaseSettings): class MySQLSettingsTest(BaseSettings): PRODUCTION_DB: str = Field(default="sqlite:///:memory:") + class ResendSettings(BaseSettings): - API_KEY: str = Field(..., alias="RESEND_API_KEY") + API_KEY: str = Field(default="", alias="RESEND_API_KEY") from_: str = Field("norespond@jobfinders.site", alias="RESEND_FROM_EMAIL") model_config = SettingsConfigDict( @@ -41,16 +42,18 @@ class EmailSettings(BaseSettings): class RedisSettings(BaseSettings): - REDIS_URL: str = Field(..., alias="REDIS_URL") + REDIS_URL: str = Field(default="", alias="REDIS_URL") model_config = SettingsConfigDict( env_file=".env.developer", env_file_encoding="utf-8", - extra="ignore") + extra="ignore" + ) + class JwtSecrets(BaseSettings): - SECRET_KEY: str = Field(..., alias='JWT_SECRET') - ALGO: str = Field(..., alias='JWT_ALGO') + SECRET_KEY: str = Field(default="", alias='JWT_SECRET') + ALGO: str = Field(default="HS256", alias='JWT_ALGO') model_config = SettingsConfigDict( env_file=".env.developer", @@ -60,10 +63,10 @@ class JwtSecrets(BaseSettings): class PayfastSettings(BaseSettings): - MERCHANT_ID: str = Field(..., alias='PAYFAST_MERCHANT_ID') - MERCHANT_KEY: str = Field(..., alias='PAYFAST_MERCHANT_KEY') + MERCHANT_ID: str = Field(default="", alias='PAYFAST_MERCHANT_ID') + MERCHANT_KEY: str = Field(default="", alias='PAYFAST_MERCHANT_KEY') SANDBOX: bool = Field(True, alias='PAYFAST_SANDBOX') - PASS_PHRASE: str = Field(..., alias='PAYFAST_PASSPHRASE') + PASS_PHRASE: str = Field(default="", alias='PAYFAST_PASSPHRASE') model_config = SettingsConfigDict( env_file=".env.developer", @@ -71,11 +74,12 @@ class PayfastSettings(BaseSettings): extra="ignore" ) + class HasnodeBlogSettings(BaseSettings): - BLOG_URL: str = Field(..., alias='HASHNODE_BLOG_URL') - HASHNODE_TOKEN: str = Field(..., alias='HASHNODE_TOKEN') - BLOG_ID: str = Field(..., alias='HASHNODE_BLOG_ID') - AUTHOR_ID: str = Field(..., alias='HASHNODE_AUTHOR_ID') + BLOG_URL: str = Field(default="", alias='HASHNODE_BLOG_URL') + HASHNODE_TOKEN: str = Field(default="", alias='HASHNODE_TOKEN') + BLOG_ID: str = Field(default="", alias='HASHNODE_BLOG_ID') + AUTHOR_ID: str = Field(default="", alias='HASHNODE_AUTHOR_ID') model_config = SettingsConfigDict( env_file=".env.developer", @@ -83,26 +87,25 @@ class HasnodeBlogSettings(BaseSettings): extra="ignore" ) + class Settings(BaseSettings): APP_NAME: str = "Job Finders" LOGO_URL: str = "https://rental-manager.site/static/images/custom/logo.png" - SECRET_KEY: str - CLIENT_SECRET: str + SECRET_KEY: str = Field(..., alias="SECRET_KEY") + CLIENT_SECRET: str = Field(default="") IS_DEVELOPMENT_SERVER: bool = Field(..., alias="IS_DEVELOPMENT_SERVER") - HOST_ADDRESSES: str - HASHNODE_TOKEN: str = Field(..., alias='HASHNODE_TOKEN') + HOST_ADDRESSES: str = Field(default="") + HASHNODE_TOKEN: str = Field(default="", alias='HASHNODE_TOKEN') MYSQL_SETTINGS: MySQLSettings = Field(default_factory=MySQLSettings) EMAIL_SETTINGS: EmailSettings = Field(default_factory=EmailSettings) REDIS: RedisSettings = Field(default_factory=RedisSettings) ACTIVITY_RETENTION_DAYS: int = 180 - ACTIVITY_CACHE_TTL: int = 3600 # 1 hour + ACTIVITY_CACHE_TTL: int = 3600 JWT_SECRETS: JwtSecrets = Field(default_factory=JwtSecrets) PAYFAST_SETTINGS: PayfastSettings = Field(default_factory=PayfastSettings) - SECRET_KEY: str = Field(..., alias="SECRET_KEY") - OPENROUTER_API_KEY: str = Field(..., alias="OPENROUTER_API_KEY") - ADMIN_USERNAME: str = Field(..., alias="ADMIN_USERNAME") - ADMIN_PASSWORD: str = Field(..., alias="ADMIN_PASSWORD") - # HASHNODE_BLOG: HasnodeBlogSettings = Field(default_factory=HasnodeBlogSettings) + OPENROUTER_API_KEY: str = Field(default="", alias="OPENROUTER_API_KEY") + ADMIN_USERNAME: str = Field(default="admin", alias="ADMIN_USERNAME") + ADMIN_PASSWORD: str = Field(default="", alias="ADMIN_PASSWORD") model_config = SettingsConfigDict( env_file=".env.developer", @@ -120,4 +123,4 @@ def config_test() -> Settings: test_settings = Settings() test_settings.MYSQL_SETTINGS.DEVELOPMENT_DB = "sqlite:///:memory:" test_settings.MYSQL_SETTINGS.PRODUCTION_DB = "sqlite:///:memory:" - return test_settings + return test_settings \ No newline at end of file From 36adea79e243a417137e50845e39944551f97e72 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:17:31 +0200 Subject: [PATCH 11/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index da7a38f..f3b15a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -77,7 +77,8 @@ PyMuPDF~=1.25.5 PyMySQL~=1.1.1 pyparsing~=3.2.3 python-docx~=1.1.2 -python-dotenv~=1.1.0 +python-dotenv~=1.1.0 +redis RapidFuzz~=3.13.0 requests~=2.32.3 requests-cache~=1.2.1 From c4cfc40605460a164868532740dad419bc3d79f1 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:20:22 +0200 Subject: [PATCH 12/35] Update requirements.txt --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f3b15a7..43fe3c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,8 @@ docx2txt~=0.9 email_validator~=2.2.0 exceptiongroup==1.1.3 Flask~=3.1.0 -Flask-Bcrypt~=1.0.1 +Flask-Bcrypt~=1.0.1 +Flask-Migrate~=4.0.7 frozenlist~=1.6.0 google-api-python-client~=2.167.0 google-auth-httplib2~=0.2.0 @@ -78,6 +79,7 @@ PyMySQL~=1.1.1 pyparsing~=3.2.3 python-docx~=1.1.2 python-dotenv~=1.1.0 +python-slugify~=8.0.4 redis RapidFuzz~=3.13.0 requests~=2.32.3 From 62524c7955965c5491b22fb94a7c070f396ff749 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:22:28 +0200 Subject: [PATCH 13/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 43fe3c2..2a79883 100644 --- a/requirements.txt +++ b/requirements.txt @@ -99,7 +99,8 @@ spacy~=3.8.5 spacy-legacy~=3.0.12 spacy-loggers~=1.0.5 SQLAlchemy~=2.0.40 -srsly~=2.5.1 +srsly~=2.5.1 +textstat thinc~=8.3.6 threadpoolctl~=3.6.0 timedelta From 64d7e3997b7d7d93400e596de0f50f2abf6dd964 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:28:20 +0200 Subject: [PATCH 14/35] Update __init__.py --- src/logger/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/logger/__init__.py b/src/logger/__init__.py index 93c2a10..e7644e2 100644 --- a/src/logger/__init__.py +++ b/src/logger/__init__.py @@ -3,11 +3,17 @@ import socket import sys from src.config import config_instance +import os class AppLogger: def __init__(self, name: str, is_file_logger: bool = False, log_level: int = logging.INFO): logging_file = 'logs/job.log' + if is_file_logger: + os.makedirs("logs", exist_ok=True) # add this line + handler = logging.FileHandler(logging_file) + else: + handler = logging.StreamHandler(sys.stdout) logger_name = name if name else config_instance().APP_NAME self.logger = logging.getLogger(logger_name) self.logger.setLevel(level=log_level) From 07093c812afbb705685b839fdeb0560380f28049 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:30:38 +0200 Subject: [PATCH 15/35] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2a79883..f399c7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -115,3 +115,4 @@ weasel~=0.4.1 Werkzeug~=3.1.3 wrapt~=1.17.2 yarl~=1.20.0 +yaml \ No newline at end of file From 838c9b811c77b3f7ba868a098a1f1b62ef466d38 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:32:03 +0200 Subject: [PATCH 16/35] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f399c7f..1f8b27e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -115,4 +115,4 @@ weasel~=0.4.1 Werkzeug~=3.1.3 wrapt~=1.17.2 yarl~=1.20.0 -yaml \ No newline at end of file +PyYAML~=6.0.2 \ No newline at end of file From 910d33e42fe8761a95c720ccd29f10e06f316a47 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:35:45 +0200 Subject: [PATCH 17/35] Update render.yaml --- render.yaml | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/render.yaml b/render.yaml index e0fd8e9..c1409f0 100644 --- a/render.yaml +++ b/render.yaml @@ -5,10 +5,11 @@ services: region: oregon plan: free - buildCommand: pip install -r requirements.txt && flask db upgrade + buildCommand: pip install -r requirements.txt && python -m spacy download en_core_web_sm && flask db upgrade startCommand: gunicorn app:app --workers 2 --bind 0.0.0.0:$PORT envVars: + # Required — app won't boot without these - key: PRODUCTION_SQL_DB fromDatabase: name: jobfinders-db @@ -23,6 +24,52 @@ services: name: jobfinders property: host + # Email + - key: RESEND_API_KEY + value: "" + - key: RESEND_FROM_EMAIL + value: norespond@jobfinders.site + + # Redis + - key: REDIS_URL + value: "" + + # JWT + - key: JWT_SECRET + generateValue: true + - key: JWT_ALGO + value: HS256 + + # PayFast + - key: PAYFAST_MERCHANT_ID + value: "" + - key: PAYFAST_MERCHANT_KEY + value: "" + - key: PAYFAST_SANDBOX + value: true + - key: PAYFAST_PASSPHRASE + value: "" + + # Hashnode + - key: HASHNODE_TOKEN + value: "" + - key: HASHNODE_BLOG_URL + value: "" + - key: HASHNODE_BLOG_ID + value: "" + - key: HASHNODE_AUTHOR_ID + value: "" + + # App + - key: CLIENT_SECRET + generateValue: true + - key: OPENROUTER_API_KEY + value: "" + - key: ADMIN_USERNAME + value: admin + - key: ADMIN_PASSWORD + generateValue: true + databases: - name: jobfinders-db databaseName: jobfinders From be154cb1f1f41627b6452d704bdc3c1ac26a3ad0 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:40:04 +0200 Subject: [PATCH 18/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1f8b27e..9b7d098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -aiodns~=3.4.0 +aiodns~=3.4.0 +aiofiles aiohappyeyeballs~=2.6.1 aiomysql~=0.2.0 aiohttp~=3.11.18 From f85536503283749e326196d881424dd3c96ad7b3 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:42:41 +0200 Subject: [PATCH 19/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9b7d098..3c35916 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,8 @@ murmurhash~=1.0.12 numpy~=2.2.5 oauthlib~=3.2.2 packaging~=25.0 -pip~=24.0 +pip~=24.0 +Pillow~=11.2.1 platformdirs~=4.3.7 preshed~=3.0.9 proto-plus~=1.26.1 From 0460bbc93b4768e100aae25caa22e8ec5ad40a9b Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:46:05 +0200 Subject: [PATCH 20/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3c35916..47db4ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -117,4 +117,5 @@ weasel~=0.4.1 Werkzeug~=3.1.3 wrapt~=1.17.2 yarl~=1.20.0 -PyYAML~=6.0.2 \ No newline at end of file +PyYAML~=6.0.2 +PyJWT~=2.10.1 \ No newline at end of file From 805f7d14701d5e32acc025c3b9f2fdc4d6b574c6 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:50:29 +0200 Subject: [PATCH 21/35] Update billing_sql.py --- src/database/sql/billing_sql.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/sql/billing_sql.py b/src/database/sql/billing_sql.py index a74b8bc..112a70b 100644 --- a/src/database/sql/billing_sql.py +++ b/src/database/sql/billing_sql.py @@ -183,7 +183,8 @@ class PaymentMethodORM(Base): __tablename__ = "payment_methods" method_id = Column(String(ID_LEN), primary_key=True, default=lambda: str(uuid.uuid4())) - company_id = Column(String(ID_LEN), ForeignKey("company_billing.company_id"), nullable=False) + company_id = Column(String(ID_LEN), ForeignKey("companies.company_id"), nullable=False) # changed + provider = Column(String(20), default="payfast") # 'payfast' or 'manual' payfast_token = Column(String(255), nullable=True) From 859492689dd88fa4b823581479557329ccd2beca Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:54:26 +0200 Subject: [PATCH 22/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 47db4ac..9930256 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,8 @@ email_validator~=2.2.0 exceptiongroup==1.1.3 Flask~=3.1.0 Flask-Bcrypt~=1.0.1 -Flask-Migrate~=4.0.7 +Flask-Migrate~=4.0.7 +Flask-Limiter~=3.9.0 frozenlist~=1.6.0 google-api-python-client~=2.167.0 google-auth-httplib2~=0.2.0 From dbd4073b6379812919e9ab535fab34c3c969d237 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 06:57:45 +0200 Subject: [PATCH 23/35] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9930256..1efda04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -89,7 +89,7 @@ requests~=2.32.3 requests-cache~=1.2.1 requests-oauthlib~=2.0.0 resend~=2.7.0 -rich~=14.0.0 +rich~=13.9.4 rsa~=4.9.1 scikit-learn~=1.6.1 scipy~=1.15.2 From 40c1a6bd9ef7954d1b6388d7d1a7690babea650b Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:02:58 +0200 Subject: [PATCH 24/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1efda04..a89bcf2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,7 +29,8 @@ cymem~=2.0.11 dnspython~=2.7.0 docx2txt~=0.9 email_validator~=2.2.0 -exceptiongroup==1.1.3 +exceptiongroup==1.1.3 +Faker~=37.1.0 Flask~=3.1.0 Flask-Bcrypt~=1.0.1 Flask-Migrate~=4.0.7 From 0f169388cd20990e8b44d6b00952a59a55946ac4 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:06:03 +0200 Subject: [PATCH 25/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a89bcf2..fb2166d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,8 @@ docx2txt~=0.9 email_validator~=2.2.0 exceptiongroup==1.1.3 Faker~=37.1.0 -Flask~=3.1.0 +Flask~=3.1.0 +Flask-Cors~=5.0.1 Flask-Bcrypt~=1.0.1 Flask-Migrate~=4.0.7 Flask-Limiter~=3.9.0 From 3e817e99d0244e4e825d6cfe54aad3667c2491fc Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:08:44 +0200 Subject: [PATCH 26/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fb2166d..2526f27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,8 @@ asgiref~=3.8.1 async-timeout==4.0.3 attrs~=25.3.0 bcrypt~=4.3.0 -beautifulsoup4~=4.13.3 +beautifulsoup4~=4.13.3 +bleach~=6.2.0 blinker~=1.9.0 blis~=1.3.0 cachetools~=5.5.2 From 98b2e6c2521bcc742800daa9ce4418296650c07a Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:11:31 +0200 Subject: [PATCH 27/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2526f27..e7c8ea4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,8 @@ aiohttp~=3.11.18 aiosignal~=1.3.2 annotated-types~=0.7.0 anyio==3.7.1 -argon2-cffi-bindings~=21.2.0 +argon2-cffi-bindings~=21.2.0 +APScheduler~=3.11.0 asgiref~=3.8.1 async-timeout==4.0.3 attrs~=25.3.0 From a4d8d6a619203ebde04de93e67c9a7a15aaff8ea Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:14:19 +0200 Subject: [PATCH 28/35] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7c8ea4..3bc118b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -72,7 +72,8 @@ preshed~=3.0.9 proto-plus~=1.26.1 protobuf~=6.30.2 propcache~=0.3.1 -psycopg2-binary~=2.9.9 +psycopg2-binary~=2.9.9 +psutil~=7.0.0 pyasn1~=0.6.1 pyasn1_modules~=0.4.2 pycares~=4.8.0 From 452f7dd9608626cd441c1a6e77d601972b32bfc7 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:18:16 +0200 Subject: [PATCH 29/35] Update render.yaml --- render.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.yaml b/render.yaml index c1409f0..e4bd71c 100644 --- a/render.yaml +++ b/render.yaml @@ -66,7 +66,7 @@ services: - key: OPENROUTER_API_KEY value: "" - key: ADMIN_USERNAME - value: admin + value: adminadmin@jobfinders.site - key: ADMIN_PASSWORD generateValue: true From d8b4587ccb5b3301f69d8136f793523418e116c3 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:25:02 +0200 Subject: [PATCH 30/35] Update utils.py --- src/routes/utils.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/routes/utils.py b/src/routes/utils.py index d86d526..3af30a0 100644 --- a/src/routes/utils.py +++ b/src/routes/utils.py @@ -19,7 +19,7 @@ MEDIA_DIR.mkdir(parents=True, exist_ok=True) SOUTH_AFRICA_PROVINCES = { "Eastern Cape": sorted([ - "Bhisho", "East London", "Grahamstown", "King William’s Town", "Mthatha", "Port Alfred", "Port Elizabeth", "Queenstown" + "Bhisho", "East London", "Grahamstown", "King William's Town", "Mthatha", "Port Alfred", "Port Elizabeth", "Queenstown" ]), "Free State": sorted([ "Bethlehem", "Bloemfontein", "Harrismith", "Kroonstad", "Parys", "Welkom" @@ -282,11 +282,11 @@ async def create_common_context(search_term: str, job_list: list[Job], page: int total_pages=math.ceil(len(job_list) / per_page), affiliate_template=affiliate_template, provinces=provinces, - categories=enriched_categories, # ✅ Pass the enriched list + categories=enriched_categories, current_year=current_year ) -async def create_context(user:User, search_term: str, page: int = 1, per_page: int = 10): +async def create_context(user: User, search_term: str, page: int = 1, per_page: int = 10): """ Create context for jobs strictly matching the search term in the job record. @@ -297,7 +297,7 @@ async def create_context(user:User, search_term: str, page: int = 1, per_page: i :return: Rendered template response. """ # Validate search term before filtering - if search_term not in get_service('scraper')().search_terms and search_term is not "home": + if search_term not in get_service('scraper')().search_terms and search_term != "home": return None category_search = await get_controller('jobs_search').search_jobs_by_category(category=search_term, page=page, page_size=per_page) jobs_list = category_search.get('jobs') if category_search else [] @@ -329,20 +329,19 @@ async def not_found(search_term: str): "title": "404 Not Found", "seo": { "title": f"No Jobs Found - {search_term}", - "description": f"We couldn’t find any job listings for {search_term}. Try searching again.", + "description": f"We couldn't find any job listings for {search_term}. Try searching again.", "keywords": "jobs, careers, not found, job search" } } return render_template("error.html", **context), 404 -async def gone(user:User, search_term: str): +async def gone(user: User, search_term: str): """Render a 410 Gone page for permanently removed job listings.""" message = f"The page for '{search_term}' has been permanently removed." utils_logger.info(message) context = { - "current_user": user, "message": message, "title": "410 Gone", @@ -360,7 +359,6 @@ async def sub_job_detail(user: User, job: Job): """Render detailed job view with SEO tags and similar jobs.""" seo = await create_seo_tags_for_job(job=job) similar_jobs = await get_service('scraper')().similar_jobs(search_term=job.search_term, title=job.title) - # utils_logger.info(f"Similar Jobs: {similar_jobs}") affiliate_template = random.choice(load_affiliate_templates()) context = dict(term=job.title, job=job, search_terms=get_service('scraper').search_terms, similar_jobs=similar_jobs, From df489447b15b20b4ac28a4ec6812456c29428278 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:30:33 +0200 Subject: [PATCH 31/35] Update __init__.py --- src/main/__init__.py | 85 ++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/src/main/__init__.py b/src/main/__init__.py index 74213c9..18a346f 100644 --- a/src/main/__init__.py +++ b/src/main/__init__.py @@ -53,22 +53,18 @@ def _register_template_filters(app): app.jinja_env.filters['current_year'] = current_year app.jinja_env.filters['format_currency'] = format_currency app.jinja_env.globals['icon'] = icon + @app.template_filter('round') def round_filter(value, precision=0): return round(value, precision) - # Add safe HTML filter import bleach @app.template_filter('safe_html') def safe_html_filter(html): """Sanitize HTML output to prevent XSS""" return bleach.clean(html, tags=bleach.sanitizer.ALLOWED_TAGS + ['p', 'br', 'div']) -def supported_content_types () -> dict[str, str]: - """ - - :return: - """ +def supported_content_types() -> dict[str, str]: return { # Documents 'pdf': 'application/pdf', @@ -92,7 +88,6 @@ def supported_content_types () -> dict[str, str]: } -# Create App Method def create_app(config): """Flask application factory with enhanced security""" from src.utils import template_folder, static_folder @@ -103,11 +98,10 @@ def create_app(config): # ======================== app.url_map.strict_slashes = False app.config['SECRET_KEY'] = os.environ.get('APP_SECRET_KEY', config.SECRET_KEY) - app.config['SESSION_COOKIE_SECURE'] = True # Only send cookies over HTTPS - app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent client-side JS access - app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection - app.config['PERMANENT_SESSION_LIFETIME'] = timedelta.Timedelta(minutes=30) # Shorter sessions - + app.config['SESSION_COOKIE_SECURE'] = True + app.config['SESSION_COOKIE_HTTPONLY'] = True + app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + app.config['PERMANENT_SESSION_LIFETIME'] = timedelta.Timedelta(minutes=30) # Proxy configuration app.wsgi_app = ProxyFix( @@ -125,13 +119,11 @@ def create_app(config): app.static_folder = static_folder() app.config['BASE_URL'] = "https://jobfinders.site" - # Configure upload settings app.config['UPLOAD_FOLDER'] = os.path.join(app.static_folder, 'uploads') app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 # 2MB limit - app.config['SQLALCHEMY_DATABASE_URI'] = config.MYSQL_SETTINGS.DEVELOPMENT_DB + app.config['SQLALCHEMY_DATABASE_URI'] = config.MYSQL_SETTINGS.PRODUCTION_DB os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) - # Safe file upload validation app.config['ALLOWED_EXTENSIONS'] = { 'pdf', 'doc', 'docx', 'txt', 'md', 'rtf', 'odt', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff', 'tif' @@ -140,12 +132,25 @@ def create_app(config): with app.app_context(): # ======================== - # 3. Security Monitoring + # 3. Database + Migrations + # ======================== + from src.database.sql import db + from flask_migrate import Migrate + db.init_app(app) + Migrate(app, db) + + # ======================== + # 4. Security Monitoring # ======================== - # Initialize security logging security_logger = logging.getLogger('security') security_logger.setLevel(logging.WARNING) - security_handler = logging.FileHandler('security.log') + # Use StreamHandler in production to avoid filesystem issues on Render + is_production = not config.IS_DEVELOPMENT_SERVER + if is_production: + security_handler = logging.StreamHandler() + else: + os.makedirs('logs', exist_ok=True) + security_handler = logging.FileHandler('logs/security.log') security_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) @@ -157,93 +162,79 @@ def create_app(config): service_factory = ServiceFactory(app) controller_factory = ControllerFactory(app, service_factory) - # Store factories in app for access in routes app.service_factory = service_factory app.controller_factory = controller_factory # ======================== # 5. Boot Sequence # ======================== - # Run boot sequence - # Run boot sequence from src.main.boot import boot boot() - # Initialize scraper if needed scraper = service_factory.get_junction_scraper() scraper.init_app(app) + # ======================== # 6. Security Middleware # ======================== - # Adding Security headers from src.firewall.headers import configure_security_headers configure_security_headers(app) from src.firewall.monitor import detect_attacks - # Security Middleware app.before_request(detect_attacks) - # Database security + from src.firewall.database_sec import safe_query app.extensions['safe_query'] = safe_query - # CSRF Protection - # from src.firewall.csrf import init_csrf_protection - # init_csrf_protection(app) - # ======================== # 7. Error Handling # ======================== from src.main.error_handling import register_error_handlers register_error_handlers(app) + # ======================== - # BLUE PRINTS REGISTRATIONS + # 8. Blueprints + Filters # ======================== - # Register blueprints _register_blueprints(app) - # Register template filters _register_template_filters(app) # ======================== - # ADD AND INITIALIZE RATE LIMITER + # 9. Rate Limiter + # ======================== from src.firewall.rate_limiting import limiter, update_cloudflare_ips limiter.init_app(app) update_cloudflare_ips() - + # ======================== - # 9. Secure Teardown + # 10. Secure Teardown # ======================== - # Clean Controllers Upon Exit @app.teardown_appcontext def shutdown_controllers(exception=None): if _controller_factory := app.extensions.get('controller_factory'): _controller_factory.close_all() - - # Clear sensitive data from g Object for key in list(vars(g).keys()): controller = getattr(g, key, None) if hasattr(controller, 'close_sessions'): controller.close_sessions() + # ======================== - # 10. Security Auditing + # 11. Security Auditing # ======================== @app.after_request def security_audit(response): - """Log security-relevant request/response data""" from src.firewall.auditing import log_security_event log_security_event(request, response) return response - ############################################ - ## AP SCHEDULER INTERGRATION - ############################################ + # ======================== + # 12. AP Scheduler + # ======================== from src.tasks.task_scheduler.ap_scheduler import create_scheduler from src.tasks.task_scheduler.admin_ap_scheduler import schedule_app_tasks scheduler = create_scheduler(app=app) - - # This Schedules Admin Jobs that are suppose to run in AP Scheduler schedule_app_tasks(scheduler=scheduler, app=app) scheduler.start() - atexit.register(scheduler.shutdown) + atexit.register(scheduler.shutdown) - return app \ No newline at end of file + return app From ba871921e620f30f81c67007860ed69ff8ad8c83 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:31:14 +0200 Subject: [PATCH 32/35] Update render.yaml --- render.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.yaml b/render.yaml index e4bd71c..e8dd7ad 100644 --- a/render.yaml +++ b/render.yaml @@ -66,7 +66,7 @@ services: - key: OPENROUTER_API_KEY value: "" - key: ADMIN_USERNAME - value: adminadmin@jobfinders.site + value: admin@jobfinders.site - key: ADMIN_PASSWORD generateValue: true From dc989662165c8be712f9b5001c5ab3a4b393f6c9 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:42:45 +0200 Subject: [PATCH 33/35] Update __init__.py --- src/main/__init__.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/__init__.py b/src/main/__init__.py index 18a346f..c145873 100644 --- a/src/main/__init__.py +++ b/src/main/__init__.py @@ -30,7 +30,6 @@ def _register_blueprints(app): from src.routes.agents_routes.employee_agents_routes import employee_agents_route from src.routes.employer_routes.employer_routes import employer_route - blueprints = [ auth_route, home_route, jobs_workflow_route, jobs_search_route, jobs_actions_bp, jobs_analytics_bp, seo_route, blog_route, users_route, jobseeker_route, @@ -42,6 +41,7 @@ def _register_blueprints(app): for blueprint in blueprints: app.register_blueprint(blueprint) + def _register_template_filters(app): """Register Jinja2 template filters""" from src.utils import format_title, format_description, intcomma, datetimeformat, current_year, number_format, icon, format_currency @@ -64,6 +64,7 @@ def safe_html_filter(html): """Sanitize HTML output to prevent XSS""" return bleach.clean(html, tags=bleach.sanitizer.ALLOWED_TAGS + ['p', 'br', 'div']) + def supported_content_types() -> dict[str, str]: return { # Documents @@ -121,7 +122,6 @@ def create_app(config): app.config['UPLOAD_FOLDER'] = os.path.join(app.static_folder, 'uploads') app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 # 2MB limit - app.config['SQLALCHEMY_DATABASE_URI'] = config.MYSQL_SETTINGS.PRODUCTION_DB os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) app.config['ALLOWED_EXTENSIONS'] = { @@ -132,19 +132,16 @@ def create_app(config): with app.app_context(): # ======================== - # 3. Database + Migrations + # 3. Database Init # ======================== - from src.database.sql import db - from flask_migrate import Migrate - db.init_app(app) - Migrate(app, db) + from src.database.sql import engine, Base + Base.metadata.create_all(bind=engine) # ======================== # 4. Security Monitoring # ======================== security_logger = logging.getLogger('security') security_logger.setLevel(logging.WARNING) - # Use StreamHandler in production to avoid filesystem issues on Render is_production = not config.IS_DEVELOPMENT_SERVER if is_production: security_handler = logging.StreamHandler() From b436ef3db8c5d9d3baa1d31b50919d86b393a6b6 Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:43:20 +0200 Subject: [PATCH 34/35] Update render.yaml --- render.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.yaml b/render.yaml index e8dd7ad..6ea1722 100644 --- a/render.yaml +++ b/render.yaml @@ -5,7 +5,7 @@ services: region: oregon plan: free - buildCommand: pip install -r requirements.txt && python -m spacy download en_core_web_sm && flask db upgrade + buildCommand: pip install -r requirements.txt && python -m spacy download en_core_web_sm startCommand: gunicorn app:app --workers 2 --bind 0.0.0.0:$PORT envVars: From 66125d9de4fd0ed513a1b84db43ac105def5f6eb Mon Sep 17 00:00:00 2001 From: Mr Viincci Le Roy <60152334+MrViincciLeRoy@users.noreply.github.com> Date: Sat, 9 May 2026 07:48:20 +0200 Subject: [PATCH 35/35] Update __init__.py --- src/main/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/__init__.py b/src/main/__init__.py index c145873..bc20013 100644 --- a/src/main/__init__.py +++ b/src/main/__init__.py @@ -122,6 +122,7 @@ def create_app(config): app.config['UPLOAD_FOLDER'] = os.path.join(app.static_folder, 'uploads') app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 # 2MB limit + app.config['SQLALCHEMY_DATABASE_URI'] = config.MYSQL_SETTINGS.PRODUCTION_DB os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) app.config['ALLOWED_EXTENSIONS'] = {