This commit is contained in:
commit
663e3cdbd7
30 changed files with 1296 additions and 0 deletions
101
.forgejo/workflows/beta.yml
Normal file
101
.forgejo/workflows/beta.yml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
name: Deploy Beta (NATIVE)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Generate CloudWatch Config Locally
|
||||
run: |
|
||||
mkdir -p tmp
|
||||
cat << 'EOF' > tmp/amazon-cloudwatch-agent.json
|
||||
{
|
||||
"agent": { "metrics_collection_interval": 60, "run_as_user": "root" },
|
||||
"metrics": {
|
||||
"metrics_collected": {
|
||||
"disk": { "measurement": ["used_percent"], "metrics_collection_interval": 60, "resources": ["/"] },
|
||||
"mem": { "measurement": ["mem_used_percent"], "metrics_collection_interval": 60 }
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"logs_collected": {
|
||||
"files": {
|
||||
"collect_list": [
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/app.log", "log_group_name": "/emailservice/app", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/error.log", "log_group_name": "/emailservice/error", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/requests.log", "log_group_name": "/emailservice/requests", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/gunicorn-access.log", "log_group_name": "/emailservice/gunicorn-access", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/gunicorn-error.log", "log_group_name": "/emailservice/gunicorn-error", "log_stream_name": "{instance_id}", "retention_in_days": 30 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Package Application
|
||||
run: |
|
||||
tar -czf app.tar.gz --exclude=.git --exclude=tmp .
|
||||
|
||||
- name: Deploy to Beta EC2
|
||||
run: |
|
||||
apk add --no-cache openssh-client aws-cli
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.BETA_EC2_SSH_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
# Copy app archive and configuration files
|
||||
scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa app.tar.gz tmp/amazon-cloudwatch-agent.json emailservice.service ${{ secrets.BETA_EC2_USERNAME }}@${{ secrets.BETA_EC2_HOST }}:/tmp/
|
||||
|
||||
# Execute deployment commands on remote server
|
||||
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.BETA_EC2_USERNAME }}@${{ secrets.BETA_EC2_HOST }} << 'EOF'
|
||||
set -e
|
||||
|
||||
# ── 2. Install Host Dependencies ──────────────────────────────
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-pip python3-venv python3-dev default-libmysqlclient-dev pkg-config libpq-dev build-essential
|
||||
|
||||
# ── 3. Extract codebase to target directory ───────────────────
|
||||
sudo mkdir -p /home/ubuntu/emailservice
|
||||
sudo chown -R ubuntu:ubuntu /home/ubuntu/emailservice
|
||||
tar -xzf /tmp/app.tar.gz -C /home/ubuntu/emailservice
|
||||
|
||||
# ── 4. Set up python virtual environment & dependencies ────────
|
||||
cd /home/ubuntu/emailservice
|
||||
mkdir -p logs
|
||||
python3 -m venv venv
|
||||
./venv/bin/pip install --upgrade pip
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
|
||||
# ── 5. Configure environment variables ─────────────────────────
|
||||
echo "DEBUG=0" > .env
|
||||
echo "DB_USER=vignesh" >> .env
|
||||
echo "DB_PASSWORD=vtechnosoft@123A" >> .env
|
||||
echo "DB_HOST=127.0.0.1" >> .env
|
||||
echo "DB_PORT=5432" >> .env
|
||||
|
||||
# ── 6. Run Database Migrations ─────────────────────────────────
|
||||
./venv/bin/python manage.py migrate --noinput
|
||||
|
||||
# ── 7. Configure and restart Systemd service ───────────────────
|
||||
sudo mv /tmp/emailservice.service /etc/systemd/system/emailservice.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable emailservice
|
||||
sudo systemctl restart emailservice
|
||||
|
||||
# ── 8. Configure CloudWatch Agent ──────────────────────────────
|
||||
sudo mv /tmp/amazon-cloudwatch-agent.json /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
|
||||
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
|
||||
-a fetch-config -m ec2 -s \
|
||||
-c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
|
||||
|
||||
# ── 9. Cleanup tmp files ───────────────────────────────────────
|
||||
rm -f /tmp/app.tar.gz
|
||||
EOF
|
||||
101
.forgejo/workflows/prod.yml
Normal file
101
.forgejo/workflows/prod.yml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
name: Deploy Prod (NATIVE)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Generate CloudWatch Config Locally
|
||||
run: |
|
||||
mkdir -p tmp
|
||||
cat << 'EOF' > tmp/amazon-cloudwatch-agent.json
|
||||
{
|
||||
"agent": { "metrics_collection_interval": 60, "run_as_user": "root" },
|
||||
"metrics": {
|
||||
"metrics_collected": {
|
||||
"disk": { "measurement": ["used_percent"], "metrics_collection_interval": 60, "resources": ["/"] },
|
||||
"mem": { "measurement": ["mem_used_percent"], "metrics_collection_interval": 60 }
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"logs_collected": {
|
||||
"files": {
|
||||
"collect_list": [
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/app.log", "log_group_name": "/emailservice/app", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/error.log", "log_group_name": "/emailservice/error", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/requests.log", "log_group_name": "/emailservice/requests", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/gunicorn-access.log", "log_group_name": "/emailservice/gunicorn-access", "log_stream_name": "{instance_id}", "retention_in_days": 30 },
|
||||
{ "file_path": "/home/ubuntu/emailservice/logs/gunicorn-error.log", "log_group_name": "/emailservice/gunicorn-error", "log_stream_name": "{instance_id}", "retention_in_days": 30 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Package Application
|
||||
run: |
|
||||
tar -czf app.tar.gz --exclude=.git --exclude=tmp .
|
||||
|
||||
- name: Deploy to Prod EC2
|
||||
run: |
|
||||
apk add --no-cache openssh-client aws-cli
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
# Copy app archive and configuration files
|
||||
scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa app.tar.gz tmp/amazon-cloudwatch-agent.json emailservice.service ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/tmp/
|
||||
|
||||
# Execute deployment commands on remote server
|
||||
ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }} << 'EOF'
|
||||
set -e
|
||||
|
||||
# ── 2. Install Host Dependencies ──────────────────────────────
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-pip python3-venv python3-dev default-libmysqlclient-dev pkg-config libpq-dev build-essential
|
||||
|
||||
# ── 3. Extract codebase to target directory ───────────────────
|
||||
sudo mkdir -p /home/ubuntu/emailservice
|
||||
sudo chown -R ubuntu:ubuntu /home/ubuntu/emailservice
|
||||
tar -xzf /tmp/app.tar.gz -C /home/ubuntu/emailservice
|
||||
|
||||
# ── 4. Set up python virtual environment & dependencies ────────
|
||||
cd /home/ubuntu/emailservice
|
||||
mkdir -p logs
|
||||
python3 -m venv venv
|
||||
./venv/bin/pip install --upgrade pip
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
|
||||
# ── 5. Configure environment variables ─────────────────────────
|
||||
echo "DEBUG=0" > .env
|
||||
echo "DB_USER=vignesh" >> .env
|
||||
echo "DB_PASSWORD=vtechnosoft@123A" >> .env
|
||||
echo "DB_HOST=127.0.0.1" >> .env
|
||||
echo "DB_PORT=5432" >> .env
|
||||
|
||||
# ── 6. Run Database Migrations ─────────────────────────────────
|
||||
./venv/bin/python manage.py migrate --noinput
|
||||
|
||||
# ── 7. Configure and restart Systemd service ───────────────────
|
||||
sudo mv /tmp/emailservice.service /etc/systemd/system/emailservice.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable emailservice
|
||||
sudo systemctl restart emailservice
|
||||
|
||||
# ── 8. Configure CloudWatch Agent ──────────────────────────────
|
||||
sudo mv /tmp/amazon-cloudwatch-agent.json /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
|
||||
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
|
||||
-a fetch-config -m ec2 -s \
|
||||
-c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
|
||||
|
||||
# ── 9. Cleanup tmp files ───────────────────────────────────────
|
||||
rm -f /tmp/app.tar.gz
|
||||
EOF
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
db.sqlite3
|
||||
.env
|
||||
.DS_Store
|
||||
35
AGENTS.md
Normal file
35
AGENTS.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
description: "Core project memory, architecture, and guidelines for the emailservice."
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# Email Service: Project Context & Memory
|
||||
|
||||
This file serves as the core memory for the `emailservice` project. It contains our understanding of the project's architecture, technologies, and guidelines. The agent will read this file automatically to regain context.
|
||||
|
||||
## Project Overview
|
||||
This project is an Email Service designed for an e-commerce website using a microservices architecture. It acts as a centralized, reusable service that other internal services can call (using an API key for authentication).
|
||||
Key capabilities include:
|
||||
1. **Sending emails** on behalf of other services (e.g., User Verification, Password Resets, Promotional Campaigns).
|
||||
2. **Reading/fetching inbox emails** based on specific filters.
|
||||
|
||||
**Architecture Flow:**
|
||||
```text
|
||||
[Calling Service (e.g., Website)] --(API call with key)--> [EmailService] --(Send/Read)--> [Internet/Email Provider]
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
- **Framework**: Django (Python) - Full-stack (Backend for APIs, Frontend for Admin/Dashboard).
|
||||
- **API**: Django REST Framework (DRF) for API key authentication and email endpoints.
|
||||
- **Database**: SQLite (default for development) or PostgreSQL (for production).
|
||||
- **Email Protocol**: SMTP for sending, IMAP for reading (or 3rd-party integrations as needed).
|
||||
|
||||
## Architecture & Design Decisions
|
||||
- *(Add any major architectural decisions here as the project evolves)*
|
||||
|
||||
## Agent Guidelines
|
||||
- Focus on clean, maintainable code.
|
||||
- *(Add specific rules for the agent here, e.g., "Always write unit tests for new routes", "Use standard logging")*
|
||||
|
||||
---
|
||||
> **Note to user:** You can manually edit this file anytime to update my understanding of the project, or use the `/learn` command in chat to have me update my rules automatically!
|
||||
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
16
core/asgi.py
Normal file
16
core/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for core project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
28
core/aws_secrets.py
Normal file
28
core/aws_secrets.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import boto3
|
||||
import json
|
||||
import logging
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_secret(secret_name, region_name="ap-south-2"):
|
||||
session = boto3.session.Session()
|
||||
client = session.client(
|
||||
service_name='secretsmanager',
|
||||
region_name=region_name
|
||||
)
|
||||
|
||||
try:
|
||||
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
|
||||
except ClientError as e:
|
||||
logger.error(f"Error fetching secret {secret_name}: {e}")
|
||||
return {}
|
||||
|
||||
if 'SecretString' in get_secret_value_response:
|
||||
secret = get_secret_value_response['SecretString']
|
||||
try:
|
||||
return json.loads(secret)
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"Secret {secret_name} is not valid JSON")
|
||||
return {}
|
||||
return {}
|
||||
149
core/settings.py
Normal file
149
core/settings.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
"""
|
||||
Django settings for core project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 6.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/6.1/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
from core.aws_secrets import get_secret
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-h3*qqwi%s4(ve^!!27d1sn6gew&4ol0^eh^n*3$aq=p+ht#)6k'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'rest_framework_api_key',
|
||||
'mailer',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'core.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/6.1/ref/settings/#databases
|
||||
|
||||
if DEBUG:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
else:
|
||||
db_secret = get_secret("arn:aws:secretsmanager:ap-south-2:764709663363:secret:emailservicebetadb-ZXQg5w")
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': db_secret.get('database', 'emailservice'),
|
||||
'USER': db_secret.get('username', 'vignesh'),
|
||||
'PASSWORD': db_secret.get('password', ''),
|
||||
'HOST': db_secret.get('host', '127.0.0.1'),
|
||||
'PORT': db_secret.get('port', '5432'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/6.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/6.1/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
# Email
|
||||
# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration
|
||||
|
||||
email_secret = get_secret("arn:aws:secretsmanager:ap-south-2:764709663363:secret:emailcredentials-DOIQtc")
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = email_secret.get('smtp', 'smtp.hostinger.com')
|
||||
EMAIL_PORT = int(email_secret.get('smtp_port', 465))
|
||||
EMAIL_USE_SSL = True # Assuming SSL for port 465
|
||||
EMAIL_HOST_USER = email_secret.get('username', 'info@tradhox.com')
|
||||
EMAIL_HOST_PASSWORD = email_secret.get('password', '')
|
||||
23
core/urls.py
Normal file
23
core/urls.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
URL configuration for core project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/6.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('mailer.urls')),
|
||||
]
|
||||
16
core/wsgi.py
Normal file
16
core/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for core project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
21
emailservice.service
Normal file
21
emailservice.service
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[Unit]
|
||||
Description=Email Service Backend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/emailservice
|
||||
EnvironmentFile=/home/ubuntu/emailservice/.env
|
||||
ExecStart=/home/ubuntu/emailservice/venv/bin/gunicorn core.wsgi:application \
|
||||
--bind 0.0.0.0:8081 \
|
||||
--workers 3 \
|
||||
--access-logfile /home/ubuntu/emailservice/logs/gunicorn-access.log \
|
||||
--error-logfile /home/ubuntu/emailservice/logs/gunicorn-error.log \
|
||||
--log-level info \
|
||||
--capture-output
|
||||
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
0
mailer/__init__.py
Normal file
0
mailer/__init__.py
Normal file
3
mailer/admin.py
Normal file
3
mailer/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
mailer/apps.py
Normal file
5
mailer/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MailerConfig(AppConfig):
|
||||
name = 'mailer'
|
||||
26
mailer/migrations/0001_initial.py
Normal file
26
mailer/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 6.1 on 2026-08-22 06:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EmailLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recipient', models.EmailField(max_length=254)),
|
||||
('subject', models.CharField(max_length=255)),
|
||||
('template_name', models.CharField(max_length=100)),
|
||||
('status', models.CharField(max_length=50)),
|
||||
('sent_at', models.DateTimeField(auto_now_add=True)),
|
||||
('error_message', models.TextField(blank=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
mailer/migrations/__init__.py
Normal file
0
mailer/migrations/__init__.py
Normal file
12
mailer/models.py
Normal file
12
mailer/models.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from django.db import models
|
||||
|
||||
class EmailLog(models.Model):
|
||||
recipient = models.EmailField()
|
||||
subject = models.CharField(max_length=255)
|
||||
template_name = models.CharField(max_length=100) # 'verification', 'password_reset', 'promotional'
|
||||
status = models.CharField(max_length=50) # 'success', 'failed'
|
||||
sent_at = models.DateTimeField(auto_now_add=True)
|
||||
error_message = models.TextField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.recipient} - {self.subject} ({self.status})"
|
||||
262
mailer/templates/emails/sellercentral/common/password_reset.html
Normal file
262
mailer/templates/emails/sellercentral/common/password_reset.html
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en"><head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Reset Your Password - Tradhox</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* Base styles for email clients */
|
||||
body, table, td, a {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
/* Design System Colors & Fonts */
|
||||
:root {
|
||||
--surface: #fbf9f8;
|
||||
--on-surface: #1b1c1c;
|
||||
--primary-container: #6b1a2c;
|
||||
--on-primary-container: #ef8191;
|
||||
--on-primary: #ffffff;
|
||||
--secondary: #5f5e5b;
|
||||
--outline-variant: #dac0c2;
|
||||
--surface-container-low: #f5f3f3;
|
||||
--primary: #4d0218;
|
||||
|
||||
--font-headline: 'Libre Caslon Text', serif;
|
||||
--font-body: 'Work Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
background-color: var(--surface-container-low);
|
||||
font-family: var(--font-body);
|
||||
color: var(--on-surface);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
background-color: #f5f3f3;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
background-color: #ffffff;
|
||||
margin: 40px auto 0;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
border: 1px solid #dac0c2;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(107, 26, 44, 0.02);
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px 32px 32px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #dac0c2;
|
||||
background-color: #fbf9f8;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
font-family: var(--font-headline);
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #4d0218;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 48px 40px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-headline);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1b1c1c;
|
||||
margin: 0 0 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1b1c1c;
|
||||
margin: 0 0 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
text-align: center;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: #6b1a2c;
|
||||
color: #ffffff;
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
padding: 14px 32px;
|
||||
border-radius: 2px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #4d0218;
|
||||
}
|
||||
|
||||
.secondary-text {
|
||||
color: #5f5e5b;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: #dac0c2;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #f5f3f3;
|
||||
padding: 32px 40px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #dac0c2;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
font-size: 12px;
|
||||
color: #5f5e5b;
|
||||
line-height: 18px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer h4 {
|
||||
font-family: var(--font-headline);
|
||||
font-size: 16px;
|
||||
color: #1b1c1c;
|
||||
margin: 0 0 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.security-tips {
|
||||
text-align: left;
|
||||
background-color: #fbf9f8;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dac0c2;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.security-tips ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #5f5e5b;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: #5f5e5b;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
margin: 0 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
color: #4d0218;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.main-container {
|
||||
margin-top: 20px;
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
.content, .footer, .header {
|
||||
padding-left: 24px !important;
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style:solid}}}.block{display:block}.inline{display:inline}.outline{outline-style:var(--tw-outline-style);outline-width:1px}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|
||||
</style>
|
||||
</head>
|
||||
<body style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; margin: 0; padding: 0; background-color: #f5f3f3; font-family: 'Work Sans', sans-serif; color: #1b1c1c; -webkit-font-smoothing: antialiased; width: 100% !important;">
|
||||
<center class="wrapper" style="width: 100%; table-layout: fixed; background-color: #f5f3f3; padding-bottom: 60px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="main-container" role="presentation" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #ffffff; margin: 40px auto 0; width: 100%; max-width: 600px; border: 1px solid #dac0c2; border-radius: 4px; overflow: hidden; box-shadow: 0 4px 20px rgba(107, 26, 44, 0.02);" width="100%" bgcolor="#ffffff">
|
||||
<tbody><tr>
|
||||
<td class="header" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 40px 32px 32px; text-align: center; border-bottom: 1px solid #dac0c2; background-color: #fbf9f8;" align="center" bgcolor="#fbf9f8">
|
||||
<p class="brand-logo" style="font-family: 'Libre Caslon Text', serif; font-size: 32px; font-weight: 700; color: #4d0218; font-style: italic; margin: 0; line-height: 40px;">Tradhox</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 48px 40px; background-color: #ffffff;" bgcolor="#ffffff">
|
||||
<h1 style="font-family: 'Libre Caslon Text', serif; font-size: 24px; font-weight: 600; color: #1b1c1c; margin: 0 0 24px; line-height: 32px;">Reset Your Password</h1>
|
||||
<p style="font-family: 'Work Sans', sans-serif; font-size: 16px; line-height: 24px; color: #1b1c1c; margin: 0 0 24px; font-weight: 400;">We received a request to reset the password for your Tradhox account. We are here to help you regain access securely.</p>
|
||||
<p style="font-family: 'Work Sans', sans-serif; font-size: 16px; line-height: 24px; color: #1b1c1c; margin: 0 0 24px; font-weight: 400;">Click the button below to choose a new password. This link will expire in 24 hours for your security.</p>
|
||||
<div class="button-container" style="text-align: center; margin: 40px 0;">
|
||||
<a class="btn" href="{{ reset_link }}" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; display: inline-block; background-color: #6b1a2c; color: #ffffff; font-family: 'Work Sans', sans-serif; font-size: 14px; font-weight: 600; text-decoration: none; padding: 14px 32px; border-radius: 2px; line-height: 20px; letter-spacing: 0.5px;">Reset Password →</a>
|
||||
</div>
|
||||
<div class="divider" style="height: 1px; background-color: #dac0c2; margin: 32px 0;"></div>
|
||||
<p class="secondary-text" style="font-family: 'Work Sans', sans-serif; line-height: 24px; margin: 0 0 24px; font-weight: 400; color: #5f5e5b; font-size: 14px; margin-bottom: 0;"><strong>Didn't request this change?</strong><br>
|
||||
If you did not initiate this password reset, you can safely ignore this email. Your password will remain unchanged and your account is secure. We recommend reviewing your recent account activity if you are concerned.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="footer" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f5f3f3; padding: 32px 40px; text-align: center; border-top: 1px solid #dac0c2;" bgcolor="#f5f3f3" align="center">
|
||||
<div class="security-tips" style="text-align: left; background-color: #fbf9f8; padding: 24px; border-radius: 4px; border: 1px solid #dac0c2; margin-top: 24px;">
|
||||
<h4 style="font-family: 'Libre Caslon Text', serif; font-size: 16px; color: #1b1c1c; margin: 0 0 12px; font-weight: 600;">Security Tips</h4>
|
||||
<ul style="margin: 0; padding-left: 20px; color: #5f5e5b; font-size: 14px; line-height: 22px;">
|
||||
<li>Never share your password with anyone.</li>
|
||||
<li>Create a strong password using a mix of letters, numbers, and symbols.</li>
|
||||
<li>Ensure you are on the official Tradhox website before entering credentials.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="links" style="margin-top: 24px;">
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #5f5e5b; text-decoration: none; font-size: 12px; margin: 0 8px; font-weight: 500;">Privacy Policy</a> |
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #5f5e5b; text-decoration: none; font-size: 12px; margin: 0 8px; font-weight: 500;">Terms of Service</a> |
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #5f5e5b; text-decoration: none; font-size: 12px; margin: 0 8px; font-weight: 500;">Contact Us</a>
|
||||
</div>
|
||||
<p style="font-family: 'Work Sans', sans-serif; margin: 0 0 24px; font-weight: 400; font-size: 12px; color: #5f5e5b; line-height: 18px; margin-bottom: 12px; margin-top: 24px;">© {% now "Y" %} Tradhox Artisanal Marketplace. All rights reserved.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</center>
|
||||
</body></html>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en"><head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Verify Your Email Address - Tradhox</title>
|
||||
<!-- Use Tailwind CSS for rapid styling, simulating pure HTML/CSS structure where possible -->
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Work+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* Email client specific resets - simulated for standard web rendering here */
|
||||
body { margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #f5f3f3; }
|
||||
table { border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||
img { -ms-interpolation-mode: bicubic; }
|
||||
/* Add some basic web-view styling to simulate the email frame */
|
||||
.email-container { max-width: 600px; margin: 0 auto; background-color: #fbf9f8; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style:solid}}}.block{display:block}.inline{display:inline}.outline{outline-style:var(--tw-outline-style);outline-width:1px}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-surface-container-low font-body-md text-on-surface p-4 md:p-8" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #f5f3f3;">
|
||||
<div class="email-container rounded-lg border border-outline-variant shadow-sm overflow-hidden my-8" style="max-width: 600px; margin: 0 auto; background-color: #fbf9f8;">
|
||||
<!-- Header -->
|
||||
<div class="bg-surface border-b border-outline-variant p-8 text-center">
|
||||
<h1 class="font-headline-lg text-headline-lg text-primary italic m-0">Tradhox</h1>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="p-8 md:p-12 space-y-8 bg-surface">
|
||||
<div class="text-center space-y-4">
|
||||
<h2 class="font-headline-md text-headline-md text-on-surface m-0">Verify Your Email Address</h2>
|
||||
<p class="font-body-md text-body-md text-on-surface-variant m-0">
|
||||
Welcome to Tradhox Artisanal Marketplace. To ensure the security of your account and protect the integrity of our community, please verify your email address.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Verification Action -->
|
||||
<div class="bg-surface-container py-8 px-6 rounded text-center border border-outline-variant space-y-6">
|
||||
<p class="font-label-caps text-label-caps text-secondary uppercase tracking-widest m-0">Your Verification Code</p>
|
||||
<div class="font-display-lg text-display-lg text-primary tracking-widest py-4 bg-surface rounded inline-block px-8 border border-outline-variant shadow-sm">
|
||||
7 4 9 2 0 1
|
||||
</div>
|
||||
<div class="flex items-center justify-center my-6">
|
||||
<span class="h-px bg-outline-variant w-16"></span>
|
||||
<span class="font-label-caps text-label-caps text-secondary mx-4">OR</span>
|
||||
<span class="h-px bg-outline-variant w-16"></span>
|
||||
</div>
|
||||
<a class="inline-block bg-primary-container text-on-primary font-button text-button py-3 px-8 rounded hover:bg-primary transition-colors no-underline" href="{{ verification_link }}">
|
||||
Verify Email Address →
|
||||
</a>
|
||||
</div>
|
||||
<!-- Security Notice -->
|
||||
<div class="pt-6 border-t border-outline-variant">
|
||||
<h3 class="font-label-caps text-label-caps text-on-surface-variant mb-2">Why is this required?</h3>
|
||||
<p class="font-body-md text-body-md text-secondary m-0 text-sm">
|
||||
Verifying your email helps us maintain a trusted environment for our artisans and buyers. It ensures you receive important updates about your orders and protects your account from unauthorized access.
|
||||
</p>
|
||||
</div>
|
||||
<div class="pt-4">
|
||||
<p class="font-body-md text-body-md text-secondary m-0 text-sm">
|
||||
If you did not create an account with Tradhox, you can safely ignore this email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer (from JSON structure mapped to email) -->
|
||||
<div class="bg-surface-container-low border-t border-outline-variant p-8 text-center space-y-4">
|
||||
<h4 class="font-headline-md text-headline-md text-on-surface-variant m-0 text-lg">Tradhox</h4>
|
||||
<div class="flex flex-wrap justify-center gap-4 text-sm font-label-caps text-label-caps">
|
||||
<a class="text-on-secondary-container hover:text-primary underline" href="#">Privacy Policy</a>
|
||||
<a class="text-on-secondary-container hover:text-primary underline" href="#">Terms of Service</a>
|
||||
<a class="text-on-secondary-container hover:text-primary underline" href="#">Unsubscribe</a>
|
||||
<a class="text-on-secondary-container hover:text-primary underline" href="#">Contact Us</a>
|
||||
</div>
|
||||
<p class="font-body-md text-body-md text-secondary text-xs m-0 pt-4">
|
||||
© {% now "Y" %} Tradhox Artisanal Marketplace. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>
|
||||
116
mailer/templates/emails/sellercentral/common/welcome_email.html
Normal file
116
mailer/templates/emails/sellercentral/common/welcome_email.html
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en"><head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Welcome to Tradhox</title>
|
||||
<!-- Tailwind is requested by instructions, but prompt asks for pure HTML/CSS for email (tables, inline styles).
|
||||
Since instructions override prompt usually for frameworks (like enforcing Tailwind), but email structure requires tables,
|
||||
I will use a hybrid: standard Tailwind script for token parsing (if used via classes in a preview context),
|
||||
but primarily focus on email-safe inline styles and table structures to satisfy the email requirement while respecting tokens. -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Work+Sans:wght@400;600&display=swap');
|
||||
|
||||
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
|
||||
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
||||
img { -ms-interpolation-mode: bicubic; }
|
||||
|
||||
img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
|
||||
table { border-collapse: collapse !important; }
|
||||
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; background-color: #fbf9f8; font-family: 'Work Sans', sans-serif; color: #1b1c1c; }
|
||||
|
||||
.heading { font-family: 'Libre Caslon Text', serif; }
|
||||
</style>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style:solid}}}.block{display:block}.inline{display:inline}.outline{outline-style:var(--tw-outline-style);outline-width:1px}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|
||||
</style>
|
||||
</head>
|
||||
<body style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: 'Work Sans', sans-serif; color: #1b1c1c; background-color: #fbf9f8; height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #fbf9f8; border-collapse: collapse !important;" width="100%" bgcolor="#fbf9f8">
|
||||
<tbody><tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 40px 0;">
|
||||
<!-- Main Email Container -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #ffffff; border: 1px solid #dac0c2; max-width: 600px; width: 100%; border-collapse: collapse !important;" width="100%" bgcolor="#ffffff">
|
||||
<!-- Header / Logo Area -->
|
||||
<tbody><tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 32px; border-bottom: 1px solid #dac0c2;">
|
||||
<h1 class="heading" style="font-family: 'Libre Caslon Text', serif; margin: 0; font-size: 32px; font-weight: 700; color: #4d0218; font-style: italic;">Tradhox</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image -->
|
||||
<tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
||||
<img alt="Artisanal craftsmanship" data-alt="A beautifully crafted Indian artisanal pottery piece sitting gracefully on a raw wooden table. The lighting is soft and natural, casting gentle shadows that highlight the intricate, hand-painted details. The scene embodies a premium, minimalistic aesthetic with warm cream and burgundy tones, reflecting a deep respect for traditional craftsmanship." src="https://lh3.googleusercontent.com/aida-public/AB6AXuDwKj0sSyvy4Vb6IAeT-HFnkRzbjSC_t19Qm3uNT0f0A6g7dEA1JnSRWuxHvlZ75TY1tUq4GgRvbrIBIpCziv2Ae0VnmWQh5hCKEulaZy9PCPJZJ0IHGlBplmGg6opaLWmIrKt5t49vzSCC3c6y5DoS38kUH1DO8kkAuDlXwl6m9dAXApzArDMus3Fzsb7KQDVzz9J4MuNySnYlDuMurTIweC1IMsLloBMZJw6HEOzG1cn9DT0Yq2FhEQ" style="-ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 100%; max-width: 600px;" width="600" height="auto">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Welcome Content -->
|
||||
<tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 48px 32px;">
|
||||
<h2 class="heading" style="font-family: 'Libre Caslon Text', serif; margin: 0 0 24px 0; font-size: 24px; font-weight: 600; color: #1b1c1c;">Welcome to the Family of Makers</h2>
|
||||
<p style="margin: 0 0 32px 0; font-size: 16px; line-height: 24px; color: #554244; text-align: center;">
|
||||
We are thrilled to welcome you to Tradhox, where traditional artistry meets a modern global marketplace. Here, we celebrate human craftsmanship and the stories woven into every handmade piece.
|
||||
</p>
|
||||
<!-- CTA Button -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-collapse: collapse !important;">
|
||||
<tbody><tr>
|
||||
<td align="center" bgcolor="#6b1a2c" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-radius: 4px;">
|
||||
<a href="{{ shop_link }}" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; display: inline-block; padding: 16px 32px; font-family: 'Work Sans', sans-serif; font-size: 14px; font-weight: 600; color: #ffffff; text-decoration: none; border: 1px solid #6b1a2c; border-radius: 4px;" target="_blank">
|
||||
Start Your Journey →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Featured Items Teaser (Bento-ish layout for email) -->
|
||||
<tr>
|
||||
<td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 0 32px 48px 32px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-collapse: collapse !important;">
|
||||
<tbody><tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding-bottom: 16px;" valign="top" width="48%">
|
||||
<img data-alt="Close-up of vibrant, hand-woven sustainable textiles showcasing intricate patterns. The fabric is draped elegantly against a pristine white background. High-quality lighting enhances the rich textures and deep maroon accents, creating a premium, artisanal feel suitable for a high-trust marketplace." src="https://lh3.googleusercontent.com/aida-public/AB6AXuDKWj6PI5PVLtD8yTefLydBNdSLfglyqu79rQgchDnzJHDEQtH7wM0hz47zSHnTxBtK27KgjE27S5dY0s4ssg_CCzLqmZpIjoM3ew2YXQDVM4GC_O6ZFA2m2F-YjYhNyn78Mm--yGIGTRnFFf6JCWcZgQUJRNpAD4QGYHcEQ4pRJEbRuqIKdVi-18eSoR1RTOc9dFZwpH8u0qb3TeLtrvBnEArLbpk9-VbGCifLYxerqun0qcbwjX8akA" style="-ms-interpolation-mode: bicubic; height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; border: 1px solid #dac0c2; border-radius: 4px; margin-bottom: 12px;" width="100%" height="auto">
|
||||
<h3 style="margin: 0 0 8px 0; font-family: 'Work Sans', sans-serif; font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #4d0218;">Sustainable Crafts</h3>
|
||||
</td>
|
||||
<td width="4%" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;"></td>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding-bottom: 16px;" valign="top" width="48%">
|
||||
<img data-alt="A skilled artisan's hands carefully shaping a delicate piece of GI-tagged wooden craft. The workspace is softly lit, emphasizing the dedication and precision of the maker. The overall mood is warm, authentic, and rooted in cultural preservation, with subtle hints of burgundy and soft beige in the environment." src="https://lh3.googleusercontent.com/aida-public/AB6AXuAor7SuW3PDkut6kw6zd7SJcjuQb8YWP7VHhdik0Axf2u9wxc18kJdEwf_63ld10x_E2W11yMrkooGzdRpZe_wL2ywQjqaqCcaBpAqUiG-ocYu61WLuyWP5VySMT8bAarpnG4il0EGyQ2EgWSv2rkKwzjGzrshCz56h69pmwCCQjWK71ox84KT1jyxyHPhoCgyHzAwLehB4DAlANLpSpPr6Ku0zgOISfyBT_BCR-ApuYSfkgYab1zbWIw" style="-ms-interpolation-mode: bicubic; height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; border: 1px solid #dac0c2; border-radius: 4px; margin-bottom: 12px;" width="100%" height="auto">
|
||||
<h3 style="margin: 0 0 8px 0; font-family: 'Work Sans', sans-serif; font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #4d0218;">GI Tagged Heritage</h3>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td align="center" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 32px; background-color: #f5f3f3; border-top: 1px solid #dac0c2;" bgcolor="#f5f3f3">
|
||||
<p style="margin: 0 0 16px 0; font-family: 'Libre Caslon Text', serif; font-size: 18px; font-weight: 700; color: #554244;">
|
||||
Tradhox
|
||||
</p>
|
||||
<!-- Social Icons (Placeholders) -->
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; margin-bottom: 24px; border-collapse: collapse !important;">
|
||||
<tbody><tr>
|
||||
<td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 0 8px;">
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #6b1a2c; text-decoration: none; font-size: 12px; font-weight: 600;">Instagram</a>
|
||||
</td>
|
||||
<td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 0 8px;">
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #6b1a2c; text-decoration: none; font-size: 12px; font-weight: 600;">Facebook</a>
|
||||
</td>
|
||||
<td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding: 0 8px;">
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #6b1a2c; text-decoration: none; font-size: 12px; font-weight: 600;">Twitter</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p style="margin: 0; font-size: 12px; color: #636260;">
|
||||
© {% now "Y" %} Tradhox Artisanal Marketplace. All rights reserved.<br>
|
||||
<a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #636260; text-decoration: underline;">Privacy Policy</a> • <a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #636260; text-decoration: underline;">Terms of Service</a> • <a href="#" style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; color: #636260; text-decoration: underline;">Unsubscribe</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</body></html>
|
||||
33
mailer/templates/mailer/base.html
Normal file
33
mailer/templates/mailer/base.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Service Portal</title>
|
||||
<!-- Bulma CSS Framework -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<style>
|
||||
.hero.is-primary {
|
||||
background-color: #3e8ed0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<strong>📧 Email Service Portal</strong>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu is-active">
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
38
mailer/templates/mailer/dashboard.html
Normal file
38
mailer/templates/mailer/dashboard.html
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "mailer/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-8">
|
||||
<div class="box">
|
||||
<h1 class="title is-3">Dashboard</h1>
|
||||
<p class="subtitle is-5">Welcome to the Email Service Administration Portal</p>
|
||||
<hr>
|
||||
|
||||
<div class="notification is-info is-light">
|
||||
This portal allows you to manage email templates, view email logs, and manage API keys for the e-commerce microservices.
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card h-100">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">Test Emails</p>
|
||||
<p class="subtitle is-6">Send a test email using templates</p>
|
||||
<a href="{% url 'send_test_email' %}" class="button is-primary is-fullwidth">Send Test Email</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="card h-100">
|
||||
<div class="card-content">
|
||||
<p class="title is-4">Logs</p>
|
||||
<p class="subtitle is-6">View sent/received emails</p>
|
||||
<a href="#" class="button is-info is-fullwidth" disabled>View Logs</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
mailer/templates/mailer/login.html
Normal file
42
mailer/templates/mailer/login.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "mailer/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-4-desktop is-6-tablet">
|
||||
<div class="box mt-6">
|
||||
<h1 class="title has-text-centered mb-5">Login</h1>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="notification is-danger is-light">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" name="username" placeholder="Enter your username" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" name="password" placeholder="Enter your password" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field mt-5">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary is-fullwidth"><strong>Sign In</strong></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
50
mailer/templates/mailer/send_test.html
Normal file
50
mailer/templates/mailer/send_test.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{% extends "mailer/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-6">
|
||||
<div class="box mt-5">
|
||||
<h1 class="title is-4">Send Test Email</h1>
|
||||
<p class="subtitle is-6">Use this form to test sending emails using your configured templates.</p>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="notification is-{{ message.tags }} is-light">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label">Recipient Email</label>
|
||||
<div class="control">
|
||||
<input class="input" type="email" name="recipient" placeholder="test@example.com" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Template</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select name="template_name" required>
|
||||
<option value="verification">Verification Email</option>
|
||||
<option value="password_reset">Password Reset Email</option>
|
||||
<option value="promotional">Promotional Offer</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field mt-5">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">Send Test Email</button>
|
||||
<a href="{% url 'dashboard' %}" class="button is-light">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
mailer/tests.py
Normal file
3
mailer/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
mailer/urls.py
Normal file
7
mailer/urls.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.dashboard, name='dashboard'),
|
||||
path('test-email/', views.send_test_email, name='send_test_email'),
|
||||
]
|
||||
73
mailer/views.py
Normal file
73
mailer/views.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.conf import settings
|
||||
from .models import EmailLog
|
||||
|
||||
def dashboard(request):
|
||||
return render(request, 'mailer/dashboard.html')
|
||||
|
||||
def send_test_email(request):
|
||||
if request.method == 'POST':
|
||||
recipient = request.POST.get('recipient')
|
||||
template_name = request.POST.get('template_name')
|
||||
|
||||
if not recipient or not template_name:
|
||||
messages.error(request, "Recipient and template are required.")
|
||||
return redirect('send_test_email')
|
||||
|
||||
subject = f"Test Email: {template_name}"
|
||||
context = {}
|
||||
|
||||
# Mock data for templates
|
||||
if template_name == 'verification':
|
||||
context = {'verification_link': 'https://tradhox.com/verify/mock-token'}
|
||||
subject = "Verify Your Email"
|
||||
elif template_name == 'password_reset':
|
||||
context = {'reset_link': 'https://tradhox.com/reset/mock-token'}
|
||||
subject = "Password Reset Request"
|
||||
elif template_name == 'promotional':
|
||||
context = {
|
||||
'image_url': 'https://via.placeholder.com/600x200',
|
||||
'message_text': 'This is a test promotional message just for you!',
|
||||
'shop_link': 'https://tradhox.com/shop'
|
||||
}
|
||||
subject = "Welcome to Tradhox Family"
|
||||
|
||||
try:
|
||||
html_content = render_to_string(f'emails/sellercentral/common/{template_name}.html', context)
|
||||
text_content = strip_tags(html_content)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
subject,
|
||||
text_content,
|
||||
settings.EMAIL_HOST_USER,
|
||||
[recipient]
|
||||
)
|
||||
msg.attach_alternative(html_content, "text/html")
|
||||
msg.send(fail_silently=False)
|
||||
|
||||
# Log success
|
||||
EmailLog.objects.create(
|
||||
recipient=recipient,
|
||||
subject=subject,
|
||||
template_name=template_name,
|
||||
status='success'
|
||||
)
|
||||
messages.success(request, f"Test email sent successfully to {recipient}!")
|
||||
except Exception as e:
|
||||
# Log failure
|
||||
EmailLog.objects.create(
|
||||
recipient=recipient,
|
||||
subject=subject,
|
||||
template_name=template_name,
|
||||
status='failed',
|
||||
error_message=str(e)
|
||||
)
|
||||
messages.error(request, f"Failed to send email: {e}")
|
||||
|
||||
return redirect('dashboard')
|
||||
|
||||
return render(request, 'mailer/send_test.html')
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
14
requirements.txt
Normal file
14
requirements.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
asgiref==3.12.1
|
||||
boto3==1.43.78
|
||||
botocore==1.43.78
|
||||
Django==6.1
|
||||
djangorestframework==3.18.0
|
||||
djangorestframework-api-key==3.1.0
|
||||
jmespath==1.1.0
|
||||
packaging==26.3
|
||||
psycopg2-binary==2.9.12
|
||||
python-dateutil==2.9.0.post0
|
||||
s3transfer==0.19.2
|
||||
six==1.17.0
|
||||
sqlparse==0.6.0
|
||||
urllib3==2.7.0
|
||||
12
start.sh
Executable file
12
start.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Activate the virtual environment
|
||||
source venv/bin/activate
|
||||
|
||||
# Apply any pending migrations
|
||||
echo "Applying database migrations..."
|
||||
python manage.py migrate
|
||||
|
||||
# Start the Django development server
|
||||
echo "Starting the Django server on http://127.0.0.1:8000..."
|
||||
python manage.py runserver
|
||||
Loading…
Reference in a new issue