Add the option to login with username or phone or email and fix the leave messages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
437fe72e48
commit
29aa5c2f36
23
DATABASE.md
23
DATABASE.md
@ -12,16 +12,23 @@ User accounts and profile information.
|
|||||||
|--------|------|-------------|-------------|
|
|--------|------|-------------|-------------|
|
||||||
| id | INTEGER | PRIMARY KEY | User identifier |
|
| id | INTEGER | PRIMARY KEY | User identifier |
|
||||||
| email | VARCHAR | UNIQUE, INDEXED | User email |
|
| email | VARCHAR | UNIQUE, INDEXED | User email |
|
||||||
|
| username | VARCHAR | UNIQUE, INDEXED | Username (optional, for login) |
|
||||||
| hashed_password | VARCHAR | NOT NULL | Bcrypt hashed password |
|
| hashed_password | VARCHAR | NOT NULL | Bcrypt hashed password |
|
||||||
| full_name | VARCHAR | NOT NULL | User's full name |
|
| full_name | VARCHAR | NOT NULL | User's full name |
|
||||||
| phone | VARCHAR | NULLABLE | Phone number |
|
| phone | VARCHAR | UNIQUE, INDEXED | Phone number (optional, for login) |
|
||||||
| address | VARCHAR | NULLABLE | Street address |
|
| address | VARCHAR | NULLABLE | Street address |
|
||||||
| city | VARCHAR | NULLABLE | City |
|
| city | VARCHAR | NULLABLE | City |
|
||||||
| postal_code | VARCHAR | NULLABLE | Postal/ZIP code |
|
| postal_code | VARCHAR | NULLABLE | Postal/ZIP code |
|
||||||
| country | VARCHAR | NULLABLE | Country |
|
| country | VARCHAR | NULLABLE | Country |
|
||||||
| is_active | BOOLEAN | DEFAULT TRUE | Account status |
|
| is_active | BOOLEAN | DEFAULT TRUE | Account status |
|
||||||
|
| is_admin | BOOLEAN | DEFAULT FALSE | Admin privileges |
|
||||||
|
| must_change_password | BOOLEAN | DEFAULT FALSE | Force password change |
|
||||||
|
| password_reset_pin | VARCHAR | NULLABLE | Password reset PIN |
|
||||||
|
| pin_expires_at | DATETIME | NULLABLE | PIN expiration time |
|
||||||
| created_at | DATETIME | DEFAULT NOW() | Account creation date |
|
| created_at | DATETIME | DEFAULT NOW() | Account creation date |
|
||||||
|
|
||||||
|
**Login Options:** Users can login with email, username, or phone number
|
||||||
|
|
||||||
### categories
|
### categories
|
||||||
Product categories.
|
Product categories.
|
||||||
|
|
||||||
@ -146,11 +153,18 @@ Contact form submissions.
|
|||||||
| Column | Type | Constraints | Description |
|
| Column | Type | Constraints | Description |
|
||||||
|--------|------|-------------|-------------|
|
|--------|------|-------------|-------------|
|
||||||
| id | INTEGER | PRIMARY KEY | Message identifier |
|
| id | INTEGER | PRIMARY KEY | Message identifier |
|
||||||
| name | VARCHAR | NOT NULL | Sender name |
|
| full_name | VARCHAR | NOT NULL | Sender full name |
|
||||||
| email | VARCHAR | NOT NULL | Sender email |
|
| email | VARCHAR | NOT NULL | Sender email |
|
||||||
|
| phone | VARCHAR | NULLABLE | Sender phone (optional) |
|
||||||
| subject | VARCHAR | NOT NULL | Message subject |
|
| subject | VARCHAR | NOT NULL | Message subject |
|
||||||
| message | TEXT | NOT NULL | Message content |
|
| message | TEXT | NOT NULL | Message content |
|
||||||
| created_at | DATETIME | DEFAULT NOW() | Submission date |
|
| created_at | DATETIME | DEFAULT NOW() | Submission date |
|
||||||
|
| is_read | BOOLEAN | DEFAULT FALSE | Admin read status |
|
||||||
|
| status | VARCHAR | DEFAULT 'new' | Status: new/read/replied |
|
||||||
|
| admin_notes | TEXT | NULLABLE | Internal admin notes |
|
||||||
|
|
||||||
|
**Constraints:** CHECK (status IN ('new', 'read', 'replied'))
|
||||||
|
**Indexes:** status, is_read, created_at
|
||||||
|
|
||||||
## Relationships Diagram
|
## Relationships Diagram
|
||||||
|
|
||||||
@ -192,12 +206,17 @@ The seed script includes:
|
|||||||
|
|
||||||
Indexed columns for performance:
|
Indexed columns for performance:
|
||||||
- users.email (UNIQUE)
|
- users.email (UNIQUE)
|
||||||
|
- users.username (UNIQUE, partial index on non-null)
|
||||||
|
- users.phone (UNIQUE, partial index on non-null)
|
||||||
- categories.name (UNIQUE)
|
- categories.name (UNIQUE)
|
||||||
- categories.slug (UNIQUE)
|
- categories.slug (UNIQUE)
|
||||||
- products.name
|
- products.name
|
||||||
- products.category_id
|
- products.category_id
|
||||||
- orders.order_number (UNIQUE)
|
- orders.order_number (UNIQUE)
|
||||||
- orders.user_id
|
- orders.user_id
|
||||||
|
- contact_messages.status
|
||||||
|
- contact_messages.is_read
|
||||||
|
- contact_messages.created_at
|
||||||
|
|
||||||
## Backup and Restore
|
## Backup and Restore
|
||||||
|
|
||||||
|
|||||||
298
SYSTEM_UPDATE_GUIDE.md
Normal file
298
SYSTEM_UPDATE_GUIDE.md
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# System Update - Quick Reference Guide
|
||||||
|
|
||||||
|
## Date
|
||||||
|
May 8, 2026
|
||||||
|
|
||||||
|
## Changes Implemented
|
||||||
|
|
||||||
|
### 1. Contact Messages System - FIXED
|
||||||
|
**Problem:** Database error when submitting contact form - column "full_name" did not exist
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Applied migration `007_enhance_contact_messages.sql`
|
||||||
|
- Renamed `name` → `full_name` in database
|
||||||
|
- Added: `phone`, `is_read`, `status`, `admin_notes` columns
|
||||||
|
- Added status constraint and indexes for performance
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Public contact form with validation
|
||||||
|
- ✅ Admin dashboard tab with message management
|
||||||
|
- ✅ Unread counter badge
|
||||||
|
- ✅ Status tracking (New/Read/Replied)
|
||||||
|
- ✅ Admin notes for internal use
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Flexible Login System - NEW
|
||||||
|
**Feature:** Users can now login with email, username, OR phone number
|
||||||
|
|
||||||
|
**Changes Made:**
|
||||||
|
|
||||||
|
#### Backend
|
||||||
|
- Added `username` column to users table (unique, indexed)
|
||||||
|
- Made `phone` column unique and indexed for login
|
||||||
|
- Updated `authenticate_user()` to search by email OR username OR phone
|
||||||
|
- Changed login endpoint to accept `identifier` instead of `email`
|
||||||
|
- Updated User model and schemas
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
- Login form: "Email" → "Email, Username, or Phone"
|
||||||
|
- Register form: Added optional username and phone fields
|
||||||
|
- Updated API calls to use new login format
|
||||||
|
|
||||||
|
**Example Logins:**
|
||||||
|
```
|
||||||
|
Email: admin@brandmaster.com
|
||||||
|
Username: admin123 (if set)
|
||||||
|
Phone: 0504370045 (if set)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
### Backend (8 files)
|
||||||
|
1. ✅ `backend/migrations/007_enhance_contact_messages.sql` - NEW
|
||||||
|
2. ✅ `backend/migrations/008_add_username_to_users.sql` - NEW
|
||||||
|
3. ✅ `backend/app/models/user.py` - Added username, updated phone
|
||||||
|
4. ✅ `backend/app/models/contact_message.py` - Enhanced model
|
||||||
|
5. ✅ `backend/app/schemas/user.py` - Added username field
|
||||||
|
6. ✅ `backend/app/schemas/contact.py` - Enhanced schemas
|
||||||
|
7. ✅ `backend/app/services/auth.py` - Flexible authenticate_user()
|
||||||
|
8. ✅ `backend/app/routers/auth.py` - Updated login endpoint
|
||||||
|
9. ✅ `backend/app/routers/contact.py` - Admin endpoints
|
||||||
|
10. ✅ `backend/app/main.py` - Registered admin router
|
||||||
|
|
||||||
|
### Frontend (3 files)
|
||||||
|
1. ✅ `frontend/src/pages/Login.jsx` - Flexible login form
|
||||||
|
2. ✅ `frontend/src/pages/Register.jsx` - Added username/phone fields
|
||||||
|
3. ✅ `frontend/src/pages/Admin.jsx` - Contact Messages tab
|
||||||
|
4. ✅ `frontend/src/pages/Contact.jsx` - Enhanced form
|
||||||
|
|
||||||
|
### Documentation (2 files)
|
||||||
|
1. ✅ `DATABASE.md` - Updated schema documentation
|
||||||
|
2. ✅ `deploy-complete-update.bat` - Automated deployment script
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Steps
|
||||||
|
|
||||||
|
### Quick Deploy (Automated)
|
||||||
|
```bash
|
||||||
|
cd c:\Users\dvirl\OneDrive\Desktop\gitea\brand-master
|
||||||
|
deploy-complete-update.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Deploy
|
||||||
|
```bash
|
||||||
|
# 1. Apply migrations
|
||||||
|
apply-migration.bat 007_enhance_contact_messages.sql
|
||||||
|
apply-migration.bat 008_add_username_to_users.sql
|
||||||
|
|
||||||
|
# 2. Build images
|
||||||
|
cd backend && docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest .
|
||||||
|
cd ../frontend && docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest .
|
||||||
|
|
||||||
|
# 3. Push to Harbor
|
||||||
|
docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest
|
||||||
|
docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest
|
||||||
|
|
||||||
|
# 4. Deploy with Helm
|
||||||
|
cd brand-master-chart
|
||||||
|
helm upgrade brand-master . --namespace my-apps --wait
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### ✅ Contact Form Fix
|
||||||
|
- [ ] Navigate to https://brand-master.dvirlabs.com/contact
|
||||||
|
- [ ] Fill in all fields (name, email, phone, subject, message)
|
||||||
|
- [ ] Submit form
|
||||||
|
- [ ] Verify success message (should NOT get database error)
|
||||||
|
|
||||||
|
### ✅ Admin Contact Messages
|
||||||
|
- [ ] Login as admin
|
||||||
|
- [ ] Click "Contact Messages" tab
|
||||||
|
- [ ] Verify unread counter appears
|
||||||
|
- [ ] Click on message to open details
|
||||||
|
- [ ] Update status to "Read"
|
||||||
|
- [ ] Add admin notes
|
||||||
|
- [ ] Save changes
|
||||||
|
- [ ] Verify unread counter updates
|
||||||
|
|
||||||
|
### ✅ Flexible Login
|
||||||
|
- [ ] Test login with email: `admin@brandmaster.com`
|
||||||
|
- [ ] Register new user with username: `testuser123`
|
||||||
|
- [ ] Test login with username: `testuser123`
|
||||||
|
- [ ] Register user with phone: `0501234567`
|
||||||
|
- [ ] Test login with phone: `0501234567`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Schema Changes
|
||||||
|
|
||||||
|
### contact_message Table
|
||||||
|
```sql
|
||||||
|
-- BEFORE
|
||||||
|
name VARCHAR -- Changed to full_name
|
||||||
|
email VARCHAR
|
||||||
|
subject VARCHAR
|
||||||
|
message TEXT
|
||||||
|
created_at TIMESTAMP
|
||||||
|
|
||||||
|
-- AFTER
|
||||||
|
full_name VARCHAR -- Renamed from 'name'
|
||||||
|
email VARCHAR
|
||||||
|
phone VARCHAR -- NEW (optional)
|
||||||
|
subject VARCHAR
|
||||||
|
message TEXT
|
||||||
|
created_at TIMESTAMP
|
||||||
|
is_read BOOLEAN -- NEW (default: false)
|
||||||
|
status VARCHAR -- NEW (new/read/replied)
|
||||||
|
admin_notes TEXT -- NEW (nullable)
|
||||||
|
```
|
||||||
|
|
||||||
|
### user Table
|
||||||
|
```sql
|
||||||
|
-- BEFORE
|
||||||
|
email VARCHAR (UNIQUE, INDEXED)
|
||||||
|
phone VARCHAR (NULLABLE)
|
||||||
|
|
||||||
|
-- AFTER
|
||||||
|
email VARCHAR (UNIQUE, INDEXED)
|
||||||
|
username VARCHAR (UNIQUE, INDEXED, NULLABLE) -- NEW
|
||||||
|
phone VARCHAR (UNIQUE, INDEXED, NULLABLE) -- Now unique & indexed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Changes
|
||||||
|
|
||||||
|
### Login Endpoint
|
||||||
|
```javascript
|
||||||
|
// BEFORE
|
||||||
|
POST /api/auth/login
|
||||||
|
{
|
||||||
|
email: "admin@brandmaster.com",
|
||||||
|
password: "Admin123!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// AFTER
|
||||||
|
POST /api/auth/login
|
||||||
|
{
|
||||||
|
identifier: "admin@brandmaster.com", // Can be email, username, or phone
|
||||||
|
password: "Admin123!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register Endpoint
|
||||||
|
```javascript
|
||||||
|
// BEFORE
|
||||||
|
POST /api/auth/register
|
||||||
|
{
|
||||||
|
email: "user@example.com",
|
||||||
|
password: "password123",
|
||||||
|
full_name: "John Doe"
|
||||||
|
}
|
||||||
|
|
||||||
|
// AFTER
|
||||||
|
POST /api/auth/register
|
||||||
|
{
|
||||||
|
email: "user@example.com",
|
||||||
|
username: "johndoe", // NEW (optional)
|
||||||
|
phone: "0501234567", // NEW (optional)
|
||||||
|
password: "password123",
|
||||||
|
full_name: "John Doe"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New Admin Endpoints
|
||||||
|
```
|
||||||
|
GET /api/admin/contact-messages - List all messages
|
||||||
|
GET /api/admin/contact-messages/unread-count - Get unread count
|
||||||
|
GET /api/admin/contact-messages/{id} - Get single message
|
||||||
|
PUT /api/admin/contact-messages/{id} - Update message
|
||||||
|
DELETE /api/admin/contact-messages/{id} - Delete message
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Default Admin Credentials
|
||||||
|
|
||||||
|
**Email:** `admin@brandmaster.com`
|
||||||
|
**Password:** `Admin123!`
|
||||||
|
|
||||||
|
**Can also login with:**
|
||||||
|
- Email: `admin@brandmaster.com`
|
||||||
|
- Username: (not set by default)
|
||||||
|
- Phone: (not set by default)
|
||||||
|
|
||||||
|
To add username/phone to admin account:
|
||||||
|
```sql
|
||||||
|
UPDATE "user"
|
||||||
|
SET username = 'admin', phone = '0504370045'
|
||||||
|
WHERE email = 'admin@brandmaster.com';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Contact Form Still Shows Error
|
||||||
|
```bash
|
||||||
|
# Check if migration was applied
|
||||||
|
kubectl exec -it -n my-apps deployment/brand-master-postgres -- psql -U brand_master -d brand_master_db
|
||||||
|
|
||||||
|
# Run this query
|
||||||
|
\d contact_message
|
||||||
|
|
||||||
|
# Should show: full_name, phone, is_read, status, admin_notes columns
|
||||||
|
```
|
||||||
|
|
||||||
|
### Login with Username Doesn't Work
|
||||||
|
```bash
|
||||||
|
# Check if migration was applied
|
||||||
|
\d "user"
|
||||||
|
|
||||||
|
# Should show: username column with UNIQUE constraint
|
||||||
|
|
||||||
|
# Check indexes
|
||||||
|
\di idx_user_username
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Logs Show Errors
|
||||||
|
```bash
|
||||||
|
kubectl logs -n my-apps deployment/brand-master-backend --tail=50
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
✅ No database errors when submitting contact form
|
||||||
|
✅ Contact messages appear in admin dashboard
|
||||||
|
✅ Unread counter shows correct number
|
||||||
|
✅ Can login with email
|
||||||
|
✅ Can login with username (when set)
|
||||||
|
✅ Can login with phone (when set)
|
||||||
|
✅ New users can register with username and phone
|
||||||
|
✅ All form validations work correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps (Optional Enhancements)
|
||||||
|
|
||||||
|
1. Email notifications for new contact messages
|
||||||
|
2. Reply to messages from admin panel
|
||||||
|
3. Set username/phone for existing users via profile page
|
||||||
|
4. Bulk actions for messages (delete multiple, mark all as read)
|
||||||
|
5. Export contact messages to CSV
|
||||||
|
6. Advanced search and filtering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** ✅ Ready for Deployment
|
||||||
|
**Deployment Script:** `deploy-complete-update.bat`
|
||||||
|
**Estimated Deployment Time:** 5-10 minutes
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,9 +16,10 @@ class User(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
email = Column(String, unique=True, index=True)
|
email = Column(String, unique=True, index=True)
|
||||||
|
username = Column(String, unique=True, index=True, nullable=True)
|
||||||
hashed_password = Column(String)
|
hashed_password = Column(String)
|
||||||
full_name = Column(String)
|
full_name = Column(String)
|
||||||
phone = Column(String, nullable=True)
|
phone = Column(String, unique=True, nullable=True, index=True)
|
||||||
address = Column(String, nullable=True)
|
address = Column(String, nullable=True)
|
||||||
city = Column(String, nullable=True)
|
city = Column(String, nullable=True)
|
||||||
postal_code = Column(String, nullable=True)
|
postal_code = Column(String, nullable=True)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -20,6 +20,11 @@ from app.config import settings
|
|||||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequest(BaseModel):
|
||||||
|
identifier: str # Can be email, username, or phone
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordRequest(BaseModel):
|
class ForgotPasswordRequest(BaseModel):
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
|
|
||||||
@ -51,8 +56,8 @@ def register(user: UserCreate, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/login")
|
@router.post("/login")
|
||||||
def login(email: str, password: str, db: Session = Depends(get_db)):
|
def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||||
user = authenticate_user(db, email, password)
|
user = authenticate_user(db, request.identifier, request.password)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -10,10 +10,13 @@ class UserBase(BaseModel):
|
|||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
password: str
|
password: str
|
||||||
|
username: Optional[str] = None
|
||||||
|
phone: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(BaseModel):
|
class UserUpdate(BaseModel):
|
||||||
full_name: Optional[str] = None
|
full_name: Optional[str] = None
|
||||||
|
username: Optional[str] = None
|
||||||
phone: Optional[str] = None
|
phone: Optional[str] = None
|
||||||
address: Optional[str] = None
|
address: Optional[str] = None
|
||||||
city: Optional[str] = None
|
city: Optional[str] = None
|
||||||
@ -39,6 +42,7 @@ class ResetPasswordWithPinRequest(BaseModel):
|
|||||||
|
|
||||||
class UserResponse(UserBase):
|
class UserResponse(UserBase):
|
||||||
id: int
|
id: int
|
||||||
|
username: Optional[str]
|
||||||
phone: Optional[str]
|
phone: Optional[str]
|
||||||
address: Optional[str]
|
address: Optional[str]
|
||||||
city: Optional[str]
|
city: Optional[str]
|
||||||
|
|||||||
Binary file not shown.
@ -73,8 +73,22 @@ def verify_token(token: str) -> Optional[int]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(db: Session, email: str, password: str) -> Optional[User]:
|
def authenticate_user(db: Session, identifier: str, password: str) -> Optional[User]:
|
||||||
user = db.query(User).filter(User.email == email).first()
|
"""
|
||||||
|
Authenticate user by email, username, or phone number
|
||||||
|
identifier: can be email, username, or phone number
|
||||||
|
"""
|
||||||
|
from sqlalchemy import or_
|
||||||
|
|
||||||
|
# Try to find user by email, username, or phone
|
||||||
|
user = db.query(User).filter(
|
||||||
|
or_(
|
||||||
|
User.email == identifier,
|
||||||
|
User.username == identifier,
|
||||||
|
User.phone == identifier
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
|
||||||
if not user or not verify_password(password, user.hashed_password):
|
if not user or not verify_password(password, user.hashed_password):
|
||||||
return None
|
return None
|
||||||
return user
|
return user
|
||||||
|
|||||||
24
backend/migrations/008_add_username_to_users.sql
Normal file
24
backend/migrations/008_add_username_to_users.sql
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
-- Migration: Add username field to users table
|
||||||
|
-- Date: 2026-05-08
|
||||||
|
-- Description: Add username column and update phone to be unique for flexible login
|
||||||
|
|
||||||
|
-- Add username column
|
||||||
|
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS username VARCHAR(255);
|
||||||
|
|
||||||
|
-- Make phone unique for login purposes (drop constraint first if exists)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'user_phone_key') THEN
|
||||||
|
ALTER TABLE "user" DROP CONSTRAINT user_phone_key;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Add unique constraint on phone (only for non-null values)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_phone_unique ON "user"(phone) WHERE phone IS NOT NULL;
|
||||||
|
|
||||||
|
-- Create unique index on username (only for non-null values)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_username ON "user"(username) WHERE username IS NOT NULL;
|
||||||
|
|
||||||
|
-- Add comments
|
||||||
|
COMMENT ON COLUMN "user".username IS 'Unique username for login (alternative to email)';
|
||||||
|
COMMENT ON COLUMN "user".phone IS 'Phone number (can be used for login when provided)';
|
||||||
188
deploy-complete-update.bat
Normal file
188
deploy-complete-update.bat
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
@echo off
|
||||||
|
REM Complete System Update Deployment Script (Windows)
|
||||||
|
REM This script deploys Contact Messages + Flexible Login features
|
||||||
|
|
||||||
|
echo ================================================================
|
||||||
|
echo Brand Master - Complete System Update Deployment
|
||||||
|
echo ================================================================
|
||||||
|
echo.
|
||||||
|
echo This will deploy:
|
||||||
|
echo 1. Contact Messages Management System
|
||||||
|
echo 2. Flexible Login (Email/Username/Phone)
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Configuration
|
||||||
|
set NAMESPACE=my-apps
|
||||||
|
set BACKEND_IMAGE=harbor.dvirlabs.com/my-apps/brand-master-backend:latest
|
||||||
|
set FRONTEND_IMAGE=harbor.dvirlabs.com/my-apps/brand-master-frontend:latest
|
||||||
|
|
||||||
|
REM Step 1: Apply Database Migrations
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 1: Applying database migrations...
|
||||||
|
echo ================================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
echo [1/2] Applying contact messages migration...
|
||||||
|
if exist "apply-migration.bat" (
|
||||||
|
call apply-migration.bat 007_enhance_contact_messages.sql
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Contact messages migration failed!
|
||||||
|
echo Please fix the error and try again.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Contact messages migration applied
|
||||||
|
) else (
|
||||||
|
echo [ERROR] Migration script not found!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
|
||||||
|
echo [2/2] Applying flexible login migration...
|
||||||
|
call apply-migration.bat 008_add_username_to_users.sql
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Flexible login migration failed!
|
||||||
|
echo Please fix the error and try again.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Flexible login migration applied
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 2: Build Backend Image
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 2: Building backend Docker image...
|
||||||
|
echo ================================================================
|
||||||
|
cd backend
|
||||||
|
docker build -t %BACKEND_IMAGE% .
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Backend build failed!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Backend image built successfully
|
||||||
|
cd ..
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 3: Build Frontend Image
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 3: Building frontend Docker image...
|
||||||
|
echo ================================================================
|
||||||
|
cd frontend
|
||||||
|
docker build -t %FRONTEND_IMAGE% .
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Frontend build failed!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Frontend image built successfully
|
||||||
|
cd ..
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 4: Push Images to Harbor
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 4: Pushing images to Harbor registry...
|
||||||
|
echo ================================================================
|
||||||
|
echo Pushing backend...
|
||||||
|
docker push %BACKEND_IMAGE%
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Backend push failed!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Backend image pushed
|
||||||
|
|
||||||
|
echo Pushing frontend...
|
||||||
|
docker push %FRONTEND_IMAGE%
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Frontend push failed!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Frontend image pushed
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 5: Deploy to Kubernetes
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 5: Deploying to Kubernetes...
|
||||||
|
echo ================================================================
|
||||||
|
cd brand-master-chart
|
||||||
|
helm upgrade brand-master . --namespace %NAMESPACE% --set backend.image.tag=latest --set frontend.image.tag=latest --wait --timeout 5m
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Helm deployment failed!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo [SUCCESS] Helm deployment successful
|
||||||
|
cd ..
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 6: Verify Deployment
|
||||||
|
echo ================================================================
|
||||||
|
echo Step 6: Verifying deployment...
|
||||||
|
echo ================================================================
|
||||||
|
echo Checking pods status...
|
||||||
|
kubectl get pods -n %NAMESPACE% | findstr brand-master
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Checking backend logs...
|
||||||
|
kubectl logs -n %NAMESPACE% deployment/brand-master-backend --tail=30
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ================================================================
|
||||||
|
echo Deployment Completed Successfully!
|
||||||
|
echo ================================================================
|
||||||
|
echo.
|
||||||
|
echo NEW FEATURES DEPLOYED:
|
||||||
|
echo.
|
||||||
|
echo 1. CONTACT MESSAGES MANAGEMENT
|
||||||
|
echo - Admin can view/manage contact form submissions
|
||||||
|
echo - Unread counter badge in admin dashboard
|
||||||
|
echo - Status tracking (New/Read/Replied)
|
||||||
|
echo - Admin notes functionality
|
||||||
|
echo.
|
||||||
|
echo 2. FLEXIBLE LOGIN
|
||||||
|
echo - Users can now login with:
|
||||||
|
echo * Email: admin@brandmaster.com
|
||||||
|
echo * Username: (if set during registration)
|
||||||
|
echo * Phone: (if set during registration)
|
||||||
|
echo.
|
||||||
|
echo TESTING INSTRUCTIONS:
|
||||||
|
echo ================================================================
|
||||||
|
echo.
|
||||||
|
echo A. Test Contact Form:
|
||||||
|
echo 1. Visit: https://brand-master.dvirlabs.com/contact
|
||||||
|
echo 2. Fill form with name, email, phone, subject, message
|
||||||
|
echo 3. Submit and verify success
|
||||||
|
echo.
|
||||||
|
echo B. Test Admin Dashboard:
|
||||||
|
echo 1. Login: https://brand-master.dvirlabs.com/login
|
||||||
|
echo 2. Credentials: admin@brandmaster.com / Admin123!
|
||||||
|
echo 3. Click "Contact Messages" tab
|
||||||
|
echo 4. View unread counter, open message, update status
|
||||||
|
echo.
|
||||||
|
echo C. Test Flexible Login:
|
||||||
|
echo 1. Try logging in with email: admin@brandmaster.com
|
||||||
|
echo 2. Create new user with username and phone
|
||||||
|
echo 3. Try logging in with username
|
||||||
|
echo 4. Try logging in with phone number
|
||||||
|
echo.
|
||||||
|
echo USEFUL COMMANDS:
|
||||||
|
echo ================================================================
|
||||||
|
echo - View backend logs:
|
||||||
|
echo kubectl logs -n %NAMESPACE% deployment/brand-master-backend -f
|
||||||
|
echo.
|
||||||
|
echo - View frontend logs:
|
||||||
|
echo kubectl logs -n %NAMESPACE% deployment/brand-master-frontend -f
|
||||||
|
echo.
|
||||||
|
echo - Check database:
|
||||||
|
echo kubectl exec -it -n %NAMESPACE% deployment/brand-master-postgres -- psql -U brand_master -d brand_master_db
|
||||||
|
echo.
|
||||||
|
echo - Check contact messages:
|
||||||
|
echo SELECT * FROM contact_message ORDER BY created_at DESC LIMIT 10;
|
||||||
|
echo.
|
||||||
|
echo - Check users table:
|
||||||
|
echo SELECT id, email, username, phone, full_name FROM "user";
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
@ -10,7 +10,7 @@ export default function Login() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { setUser, setToken } = useContext(AuthContext)
|
const { setUser, setToken } = useContext(AuthContext)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: '',
|
identifier: '',
|
||||||
password: '',
|
password: '',
|
||||||
})
|
})
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@ -36,11 +36,9 @@ export default function Login() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/auth/login', null, {
|
const response = await api.post('/auth/login', {
|
||||||
params: {
|
identifier: formData.identifier,
|
||||||
email: formData.email,
|
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if user must change password
|
// Check if user must change password
|
||||||
@ -55,7 +53,7 @@ export default function Login() {
|
|||||||
setTimeout(() => navigate('/'), 1000)
|
setTimeout(() => navigate('/'), 1000)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setToast({ type: 'error', message: 'Invalid email or password' })
|
setToast({ type: 'error', message: 'Invalid credentials' })
|
||||||
console.error('Login error:', error)
|
console.error('Login error:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@ -144,12 +142,13 @@ export default function Login() {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Email</label>
|
<label>Email, Username, or Phone</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="text"
|
||||||
name="email"
|
name="identifier"
|
||||||
value={formData.email}
|
value={formData.identifier}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
placeholder="Enter email, username, or phone"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,6 +10,8 @@ export default function Register() {
|
|||||||
const { setUser, setToken } = useContext(AuthContext)
|
const { setUser, setToken } = useContext(AuthContext)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: '',
|
email: '',
|
||||||
|
username: '',
|
||||||
|
phone: '',
|
||||||
password: '',
|
password: '',
|
||||||
full_name: '',
|
full_name: '',
|
||||||
})
|
})
|
||||||
@ -30,17 +32,17 @@ export default function Register() {
|
|||||||
try {
|
try {
|
||||||
const response = await api.post('/auth/register', {
|
const response = await api.post('/auth/register', {
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
|
username: formData.username || null,
|
||||||
|
phone: formData.phone || null,
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
full_name: formData.full_name,
|
full_name: formData.full_name,
|
||||||
})
|
})
|
||||||
|
|
||||||
setToast({ type: 'success', message: 'Account created successfully! Logging you in...' })
|
setToast({ type: 'success', message: 'Account created successfully! Logging you in...' })
|
||||||
|
|
||||||
const loginResponse = await api.post('/auth/login', null, {
|
const loginResponse = await api.post('/auth/login', {
|
||||||
params: {
|
identifier: formData.email,
|
||||||
email: formData.email,
|
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setToken(loginResponse.data.access_token)
|
setToken(loginResponse.data.access_token)
|
||||||
@ -83,6 +85,28 @@ export default function Register() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Username (Optional)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
value={formData.username}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Choose a unique username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Phone (Optional)</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="+972 XX-XXX-XXXX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user