Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,14 @@ SENTRY_DSN=

# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
#WebEngage
WEBENGAGE_API_KEY=e57841de-1867-4a13-8535-d14c2288e17d
WEBENGAGE_API_URL=https://api.webengage.com/v2/accounts/
WEBENGAGE_LICENSE_CODE=11b5648a7
WEBENGAGE_CAMPAIGN_REGISTER_ID=~2o21rqq
WEBENGAGE_CAMPAIGN_FORGOT_PASSWORD_ID=13co4i3
[email protected]
INITIAL_ADMIN_PASSWORD=Test@12345

REDIS_URL=redis://localhost:6379/0

REDIS_URL=redis://redis:6379/0
33 changes: 33 additions & 0 deletions backend/app/alembic/versions/8f75a59e9165_add_users_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add users table

Revision ID: 8f75a59e9165
Revises: 049f237840d6
Create Date: 2025-12-18 10:17:51.389611

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '8f75a59e9165'
down_revision = '049f237840d6'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'phone_number')
op.drop_column('user', 'last_name')
op.drop_column('user', 'first_name')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('first_name', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user', sa.Column('last_name', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user', sa.Column('phone_number', sa.VARCHAR(), autoincrement=False, nullable=True))
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""update user adn opt tale

Revision ID: c09c4a1bfec5
Revises: 8f75a59e9165
Create Date: 2025-12-18 11:55:26.319177

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'c09c4a1bfec5'
down_revision = '8f75a59e9165'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Create enum types first (Postgres requires the type to exist before using it)
email_token_status = sa.Enum('active', 'used', 'expired', name='emailtokenstatus')
user_status = sa.Enum('active', 'inactive', 'banned', name='userstatus')
email_token_status.create(op.get_bind(), checkfirst=True)
user_status.create(op.get_bind(), checkfirst=True)

op.add_column('otp', sa.Column('email_token', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
op.add_column('otp', sa.Column('token_status', email_token_status, nullable=False))
op.create_index(op.f('ix_otp_email_token'), 'otp', ['email_token'], unique=True)
op.drop_column('otp', 'code')
op.drop_column('otp', 'type')
op.drop_column('otp', 'expires_at')
op.add_column('user', sa.Column('status', user_status, nullable=False))
op.add_column('user', sa.Column('token', sqlmodel.sql.sqltypes.AutoString(), nullable=True))
op.create_index(op.f('ix_user_token'), 'user', ['token'], unique=True)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_user_token'), table_name='user')
op.drop_column('user', 'token')
op.drop_column('user', 'status')
op.add_column('otp', sa.Column('expires_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=False))
op.add_column('otp', sa.Column('type', postgresql.ENUM('password_reset', 'email_verification', 'signup_confirmation', 'login_confirmation', name='otptype'), autoincrement=False, nullable=False))
op.add_column('otp', sa.Column('code', sa.INTEGER(), autoincrement=False, nullable=False))
op.drop_index(op.f('ix_otp_email_token'), table_name='otp')
op.drop_column('otp', 'token_status')
op.drop_column('otp', 'email_token')
# Drop enum types if they exist
email_token_status = sa.Enum('active', 'used', 'expired', name='emailtokenstatus')
user_status = sa.Enum('active', 'inactive', 'banned', name='userstatus')
email_token_status.drop(op.get_bind(), checkfirst=True)
user_status.drop(op.get_bind(), checkfirst=True)
# ### end Alembic commands ###
40 changes: 16 additions & 24 deletions backend/app/api/controllers/auth_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
from app.core.exceptions import AppException
from app.schemas.response import ResponseSchema
from app.schemas.user import (
ForgotPasswordSchema,
LoginSchema,
RegisterSchema,
ResendEmailSchema,
ResetPasswordSchema,
VerifySchema,
Expand Down Expand Up @@ -96,15 +94,9 @@ async def login(self, request: LoginSchema) -> JSONResponse:
except Exception as exc:
return self._error(exc)

async def register(self, request: RegisterSchema) -> JSONResponse:
async def register(self, request: LoginSchema) -> JSONResponse:
try:
result = await self.service.register(
request.email,
request.password,
request.first_name,
request.last_name,
request.phone_number,
)
result = await self.service.register(request.email, request.password)
return self._success(
data=result,
message=MSG.AUTH["SUCCESS"]["USER_REGISTERED"],
Expand All @@ -124,47 +116,47 @@ async def verify(self, request: VerifySchema) -> JSONResponse:
except Exception as exc:
return self._error(exc)

async def forgot_password(self, request: ForgotPasswordSchema) -> JSONResponse:
async def resend_email(self, request: ResendEmailSchema) -> JSONResponse:
try:
result = await self.service.forgot_password(email=request.email)
result = await self.service.resend_email(email=request.email)
return self._success(
data=result,
message=MSG.AUTH["SUCCESS"]["PASSWORD_RESET_EMAIL_SENT"],
message=MSG.AUTH["SUCCESS"]["VERIFICATION_EMAIL_RESENT"],
status_code=status.HTTP_200_OK,
)
except Exception as exc:
return self._error(exc)

async def reset_password(self, request: ResetPasswordSchema) -> JSONResponse:
async def logout(self) -> JSONResponse:
try:
result = await self.service.reset_password(
token=request.token, new_password=request.new_password
)
result = await self.service.logout()
return self._success(
data=result,
message=MSG.AUTH["SUCCESS"]["PASSWORD_HAS_BEEN_RESET"],
message=MSG.AUTH["SUCCESS"]["LOGGED_OUT"],
status_code=status.HTTP_200_OK,
)
except Exception as exc:
return self._error(exc)

async def resend_email(self, request: ResendEmailSchema) -> JSONResponse:
async def forgot_password(self, request: ResendEmailSchema) -> JSONResponse:
try:
result = await self.service.resend_email(email=request.email)
result = await self.service.forgot_password(email=request.email)
return self._success(
data=result,
message=MSG.AUTH["SUCCESS"]["VERIFICATION_EMAIL_RESENT"],
message=MSG.AUTH["SUCCESS"]["FORGOT_PASSWORD_EMAIL_SENT"],
status_code=status.HTTP_200_OK,
)
except Exception as exc:
return self._error(exc)

async def logout(self) -> JSONResponse:
async def reset_password(self, request: ResetPasswordSchema) -> JSONResponse:
try:
result = await self.service.logout()
result = await self.service.reset_password(
token=request.token, new_password=request.new_password
)
return self._success(
data=result,
message=MSG.AUTH["SUCCESS"]["LOGGED_OUT"],
message=MSG.AUTH["SUCCESS"]["PASSWORD_RESET_SUCCESSFUL"],
status_code=status.HTTP_200_OK,
)
except Exception as exc:
Expand Down
16 changes: 7 additions & 9 deletions backend/app/api/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

from app.api.controllers.auth_controller import UserController
from app.schemas.user import (
ForgotPasswordSchema,
LoginSchema,
RegisterSchema,
ResendEmailSchema,
ResetPasswordSchema,
VerifySchema,
Expand All @@ -22,7 +20,7 @@ async def login(request: LoginSchema) -> JSONResponse:


@router.post("/register")
async def register(request: RegisterSchema) -> JSONResponse:
async def register(request: LoginSchema) -> JSONResponse:
return await controller.register(request)


Expand All @@ -31,8 +29,13 @@ async def verify(request: VerifySchema) -> JSONResponse:
return await controller.verify(request)


@router.post("/resend-email")
async def resend_email(request: ResendEmailSchema) -> JSONResponse:
return await controller.resend_email(request)


@router.post("/forgot-password")
async def forgot_password(request: ForgotPasswordSchema) -> JSONResponse:
async def forgot_password(request: ResendEmailSchema) -> JSONResponse:
return await controller.forgot_password(request)


Expand All @@ -41,11 +44,6 @@ async def reset_password(request: ResetPasswordSchema) -> JSONResponse:
return await controller.reset_password(request)


@router.post("/resend-email")
async def resend_email(request: ResendEmailSchema) -> JSONResponse:
return await controller.resend_email(request)


@router.post("/logout")
async def logout() -> JSONResponse:
return await controller.logout()
15 changes: 5 additions & 10 deletions backend/app/backend_pre_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed

from app.core import security
from app.core.config import settings
from app.core.db import get_engine
from app.enums.user_enum import UserRole
from app.enums.user_enum import UserRole, UserStatus
from app.models.user import User

logging.basicConfig(level=logging.INFO)
Expand All @@ -25,7 +26,6 @@
def init(db_engine: Engine) -> None:
try:
with Session(db_engine) as session:
# Try to create session to check if DB is awake
session.exec(select(1))
except Exception as e:
logger.error(e)
Expand All @@ -39,11 +39,8 @@ def ensure_initial_admin(db_engine: Engine) -> None:
If a user with the hardcoded admin email already exists, this is a no-op.
Otherwise, create it with the specified credentials.
"""
admin_email = "[email protected]"
admin_password = "Password@1234"
admin_phone = "03056989246"
admin_first_name = "Asad"
admin_last_name = "ghafoor"
admin_email = settings.INITIAL_ADMIN_EMAIL
admin_password = settings.INITIAL_ADMIN_PASSWORD

with Session(db_engine) as session:
existing_admin = session.exec(
Expand All @@ -58,9 +55,7 @@ def ensure_initial_admin(db_engine: Engine) -> None:
admin_user = User(
email=admin_email,
hashed_password=hashed_password,
first_name=admin_first_name,
last_name=admin_last_name,
phone_number=admin_phone,
status=UserStatus.active,
role=UserRole.admin,
)

Expand Down
7 changes: 6 additions & 1 deletion backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def all_cors_origins(self) -> list[str]:
POSTGRES_PASSWORD: str = ""
POSTGRES_DB: str = ""
# Redis connection URL. Default points to the compose service `redis`.
REDIS_URL: str = "redis://localhost:6379/0"
REDIS_URL: str = "redis://redis:6379/0"
# Celery broker/result backend. By default reuse `REDIS_URL` so you can
# configure an Upstash or other hosted Redis via `REDIS_URL` or explicitly
# via `CELERY_BROKER_URL` / `CELERY_RESULT_BACKEND` env vars.
Expand Down Expand Up @@ -147,10 +147,15 @@ def r2_boto3_config(self) -> dict[str, Any]:
FIRST_SUPERUSER: EmailStr = "[email protected]"
FIRST_SUPERUSER_PASSWORD: str = "Test@1234"
USER_PASSWORD: str = "Test@1234"
INITIAL_ADMIN_EMAIL: EmailStr = "[email protected]"
INITIAL_ADMIN_PASSWORD: str = "Test@12345"

# WebEngage transactional email settings
WEBENGAGE_API_URL: HttpUrl | None = None
WEBENGAGE_API_KEY: str | None = None
WEBENGAGE_LICENSE_CODE: str | None = None
WEBENGAGE_CAMPAIGN_REGISTER_ID: str | None = None
WEBENGAGE_CAMPAIGN_FORGOT_PASSWORD_ID: str | None = None

def _check_default_secret(self, var_name: str, value: str | None) -> None:
if value == "changethis":
Expand Down
25 changes: 0 additions & 25 deletions backend/app/email-templates/build/new_account.html

This file was deleted.

Loading
Loading