From 29aa5c2f36b2f0fd804423cf37fa9205d48a4dc4 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Fri, 8 May 2026 18:54:26 +0300 Subject: [PATCH] Add the option to login with username or phone or email and fix the leave messages --- DATABASE.md | 23 +- SYSTEM_UPDATE_GUIDE.md | 298 ++++++++++++++++++ backend/app/__pycache__/main.cpython-314.pyc | Bin 5611 -> 5611 bytes .../contact_message.cpython-314.pyc | Bin 1212 -> 1212 bytes .../__pycache__/product.cpython-314.pyc | Bin 2848 -> 2848 bytes .../models/__pycache__/user.cpython-314.pyc | Bin 2008 -> 2100 bytes backend/app/models/user.py | 3 +- .../routers/__pycache__/auth.cpython-314.pyc | Bin 11391 -> 11792 bytes .../__pycache__/contact.cpython-314.pyc | Bin 6829 -> 6829 bytes .../__pycache__/products.cpython-314.pyc | Bin 15692 -> 15692 bytes backend/app/routers/auth.py | 9 +- .../__pycache__/contact.cpython-314.pyc | Bin 2569 -> 2569 bytes .../__pycache__/product.cpython-314.pyc | Bin 5055 -> 5055 bytes .../schemas/__pycache__/user.cpython-314.pyc | Bin 4274 -> 4570 bytes backend/app/schemas/user.py | 4 + .../services/__pycache__/auth.cpython-314.pyc | Bin 6570 -> 6874 bytes backend/app/services/auth.py | 18 +- .../migrations/008_add_username_to_users.sql | 24 ++ deploy-complete-update.bat | 188 +++++++++++ frontend/src/pages/Login.jsx | 21 +- frontend/src/pages/Register.jsx | 34 +- 21 files changed, 599 insertions(+), 23 deletions(-) create mode 100644 SYSTEM_UPDATE_GUIDE.md create mode 100644 backend/migrations/008_add_username_to_users.sql create mode 100644 deploy-complete-update.bat diff --git a/DATABASE.md b/DATABASE.md index 2bc9da3..9349986 100644 --- a/DATABASE.md +++ b/DATABASE.md @@ -12,16 +12,23 @@ User accounts and profile information. |--------|------|-------------|-------------| | id | INTEGER | PRIMARY KEY | User identifier | | email | VARCHAR | UNIQUE, INDEXED | User email | +| username | VARCHAR | UNIQUE, INDEXED | Username (optional, for login) | | hashed_password | VARCHAR | NOT NULL | Bcrypt hashed password | | 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 | | city | VARCHAR | NULLABLE | City | | postal_code | VARCHAR | NULLABLE | Postal/ZIP code | | country | VARCHAR | NULLABLE | Country | | 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 | +**Login Options:** Users can login with email, username, or phone number + ### categories Product categories. @@ -146,11 +153,18 @@ Contact form submissions. | Column | Type | Constraints | Description | |--------|------|-------------|-------------| | 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 | +| phone | VARCHAR | NULLABLE | Sender phone (optional) | | subject | VARCHAR | NOT NULL | Message subject | | message | TEXT | NOT NULL | Message content | | 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 @@ -192,12 +206,17 @@ The seed script includes: Indexed columns for performance: - users.email (UNIQUE) +- users.username (UNIQUE, partial index on non-null) +- users.phone (UNIQUE, partial index on non-null) - categories.name (UNIQUE) - categories.slug (UNIQUE) - products.name - products.category_id - orders.order_number (UNIQUE) - orders.user_id +- contact_messages.status +- contact_messages.is_read +- contact_messages.created_at ## Backup and Restore diff --git a/SYSTEM_UPDATE_GUIDE.md b/SYSTEM_UPDATE_GUIDE.md new file mode 100644 index 0000000..267f070 --- /dev/null +++ b/SYSTEM_UPDATE_GUIDE.md @@ -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 diff --git a/backend/app/__pycache__/main.cpython-314.pyc b/backend/app/__pycache__/main.cpython-314.pyc index eb0a86b33f8ebb92cccd452ff52445bf136f20fc..0ea81bf9c7a6f782a8b82536136e23f8ef21152d 100644 GIT binary patch delta 19 ZcmaE@{aTw#n~#@^0SJ6pHgY`_1pqP{1c(3t delta 19 ZcmaE@{aTw#n~#@^0SK-#Zsd9>3IH`h1r7iJ diff --git a/backend/app/models/__pycache__/contact_message.cpython-314.pyc b/backend/app/models/__pycache__/contact_message.cpython-314.pyc index 6140d4b507a8a8ff998b43332fd95fc6458a1b1e..c8653659877d1d62fc8459f54f628cb74708c366 100644 GIT binary patch delta 19 ZcmdnPxrdWWn~#@^0SJ6pHgauZ0RSe81Ev4~ delta 19 ZcmdnPxrdWWn~#@^0SGoQZsgj;0stt~1P1^B diff --git a/backend/app/models/__pycache__/product.cpython-314.pyc b/backend/app/models/__pycache__/product.cpython-314.pyc index 028e46865cbab03e13242199c8413f1aea5aaf40..8700f61ab75b4ff64afcd02e1eba8c54c5d82a13 100644 GIT binary patch delta 20 acmZ1=wm^(qn~#@^0SJ6p{%zzI$WL+sJp2k&7L~2ZGO=Cf{UquMc7ZLsoMZh;Rv4EUO|z zEQbjrl+TUE=YjB{`q<$5c;Vvg=1fq%d|1TzQN-%ODoX@V1R-LAU@;+}MI3Mgg`r}c zNMa&b#6+=}Ck7T12bza)phP`HFjg|a1mb3763G(jSQ$lzAg&5Py&WT%|&gV2NUokTQd&@GX|m;?$yBETup)WO5_3oGx>bIMBc% zF(7e^Bd;_kCow4}RTC;I0h7{XECR77uVT(+RGKWo63@v2G9oWAH+6FvOEe?nzsZ+a z)%E#BC!{S1*&uR&2uJAZb w7GYb%Xgm2J+Z0h-kWF@AN7&@%r4a_04g4B`~Uy| delta 454 zcmdlYaD$&un~#@^0SKn${LNg;x{>c7BNrQp4+Nj5O}@$K&Sb(kS&K=oK8PuZ)tm_; zQ^FI=s>l$_fg;9>MT`%N7(W&<0kD`L&^$J{9YRnscCc7I*ya*p6hVlX2o?iH!D3=4 z4itxo#YzMiLA-%XB3UdIE3L>7#1X_8#2&8Cd;x<5w!tYTVx9)ZgJS;=BJeAq}mlZO+LmR&o9o%)?xCE K0Z0{r6afJ1gG;ag diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 77ae775..6ed290d 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -16,9 +16,10 @@ class User(Base): id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True) + username = Column(String, unique=True, index=True, nullable=True) hashed_password = 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) city = Column(String, nullable=True) postal_code = Column(String, nullable=True) diff --git a/backend/app/routers/__pycache__/auth.cpython-314.pyc b/backend/app/routers/__pycache__/auth.cpython-314.pyc index 454bda125b4f0d477a0aa4b920c65b0b251cdaa0..ddf9b571f951f292cadfb4627d6a076e63a260fa 100644 GIT binary patch delta 2614 zcmai0?N3u@6u(bjZY#x0TUttasV$USS`em!fWwzeB|1g1P9%zUl@@3QEj+hX=aPY2 z7GLHz*wJiX^wW}MaXK^pf_>PxnMjt5>@G1e*_UM|Q^(%o;&#q$3wkq06Mpx3?(;k6 zIlt$e^IZNJm>HCsq}3$?{{AvQN!9vhrAqeFZ0K>7TL=hIr$>mEA1i4y5(}b*CqJ~p zPGxxFGo~7qEQj;vsV=W(6-`d&l1jEE7K^9TnQT0pjKw@?uXqvuJSV&*d~Yv5yZ@n4 zVCl7DVa9DZ3mbR? zKDcH_0oG0P5WQ$b08tD^YjB{HA>?}b84a8Gc4F)x zh9z+#mQ#|Pzl<}*gdod)zqXxf<;jthlEuY?_`U&tvweztUM;PJ8x@-&>vT+!3p@uO zKDdR(Ov}rE#{43#D^&>7!XJWzi4O@bcR!%VCx^9}cG5$X&*IOD1M1~pJFz;Zg~3AK z3BqA}pFx26Iu}fYT(&~HPZ;_Q@NTFBDoZ5y>Y|T9sW|Gh(^UYHRN^%bmKWbfjf3vu z80{w|7yJ^kcDJ&AW7O19h-ShRHTRpMV$>3~4ptWFeWXYC3kc}sf)r#ua8v4a(ou&c zPP_2{K^+*&4m1RDs2WlUPBp4phk0f^mf_=JGw&x79W37tZql59R4vBdt^v zN8w9nkVWPeoVyL|2n5{q>?rJXub>$FPP?a8O4zJa`xu&(M0RCcfGL{XWY%vVx23lr6(>`HaoAg0mc zOd~vSdBE@UvH{rSYhWB+_0_m_D)M=QSzo+|!sc0oXJV)J;AatZu{(JFQxYp^z-qsU ztQqKBa~H$+_(zN3JIKRI;opSM{S^MZzukycZ3zxdkqCYoA?(zj{c^SGa&(StE)c9D zI8Q*qbGktIQi@d9cuvX2hDPJ*k>qlzX^AryiquIKq+T&1U~gkPJr35I-^baFx!Q(C zhBA9(#YHu(mJa1O$1iaHuHFH+WzX}!4h!%^Zbh*zp;wrNs;~#k?m}bdpjJLQNz0MH zhs=C&6=IyB7jzR9LX`8Lg)+S^K+1Pe8=e#+I10DGz$+*oo8zlKwWf67O2KeDUT fz??H(ivm6tOQ5Z(S8UyQ>*Vz*IM=jjqKN(lzV8RZ delta 2393 zcmah~S!`QH6n*38Es5iJOYFo>W5@BcouyIRbVIa6qNFWdXiV!?$icXYlUhw|XP#Y9 zL5(S(QUWdIvJ{D;BDPkb0?JQ?)DJ$&CkXn*Ljped;foe3sDu#QJ6=kkfXv5vvz$Bc zo_p`iyqNmyfPKK;+ALvvb@tEP8{KE@J~ntJ`iF5Mv}Zef=5nuhO?VRS^#-Cy(u5%- zO|*@$>Szs1c)U`g-7C#HX4yz}QYuC2wyXor8#+6^$nohoN}UtxtnZfSLY|*^jh&)( z77K`-x{h#M+J|%VhFo=oVu$J>KoITf-S&Loz(=wWCXr!dajH17wq2J&a@su2!YxhMyIa$ zyi1pwZ!#8ww{7bXHQTUFvlcU|`e=bq@q5A18d$rD%r+xvmZ^j3vYO?53yxGHf}#Y2 zVsp*Pvokrhgp0dzJ_woC_ig0MQS37M=%r^*Lc3-E@JF@tE=*7t~W)|_8l#yF+R_xID zR|jUSBBdyAqU{eR*+`99l#?>4p*92WxmLo-7JGYBbwZL-;uO+okBtr}*Vqc}aEBJdDb^A?IIJh4kac@DJcim}t*zO#vWqt)NEt~lytoHP==+b`Ab_075 z-s%Xmy>PW-^}>B*-AXV^*jWks#4UW>}#eX&*e*4gEKTm{}+MedF<)RyluL` zX9}fs zlolaar`3*Sll_O(BgmVFPy8RDvP|G$U73T-ms9pUTn~`4_U^pFoI{JFF(`I;;e1ag z8;4JOB5V$R=n1szR8;c@&R}MY+~x&@JA40mDF{zVw!x2sK;qvK@rGyz>5{3!x* zp0^V;Qlxq2%INy!p-g@zd%M&`;^bP9y2ygmt8_)-k;W7CxYBJpiK7ck=ad*jnV&~h z57o40n=Esl&6ldz_h#4?^Zxhq@HHHZ4WrmgvAxVPisoZjc1`^oQlflxl5TtcEHbOb zRr7I?Cg?QPTvVzrMVnsNDWrS_=lbW(QS8Nsz%wvzK1RkBI6Uw{l8%mMEEe$9q_;)f zR(y(jboe-xrARI(?VNTKZ)Wu=?8BjEy?d@ORi4dm=C9+BZg_PO<_EXI)xllnHFsTJ Ow{#EqhHe_^|9=B88PkgZ diff --git a/backend/app/routers/__pycache__/contact.cpython-314.pyc b/backend/app/routers/__pycache__/contact.cpython-314.pyc index 9d98ca80c98ac042dbf979d5c58edf122114beb5..f4b587b7f61e04c2eb046d0910eae1f3677852d5 100644 GIT binary patch delta 19 ZcmZ2$y4I9Sn~#@^0SJ6pHgYYN0styM1Lpt$ delta 19 ZcmZ2$y4I9Sn~#@^0SHboZsb}l1pqA=1YiIF diff --git a/backend/app/routers/__pycache__/products.cpython-314.pyc b/backend/app/routers/__pycache__/products.cpython-314.pyc index b2fa4c3a4138717d2ee9f91f4b28d1e6a98a9282..1018c7b4331fd423d650d51d896d7bb3f7bc8230 100644 GIT binary patch delta 20 acmX?8b*742n~#@^0SJ6p{%z!Tv;_b^WCd^l delta 20 acmX?8b*742n~#@^0SMOq`@50b(G~zl;s(k9 diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index c1556ff..b942315 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -20,6 +20,11 @@ from app.config import settings router = APIRouter(prefix="/api/auth", tags=["auth"]) +class LoginRequest(BaseModel): + identifier: str # Can be email, username, or phone + password: str + + class ForgotPasswordRequest(BaseModel): email: EmailStr @@ -51,8 +56,8 @@ def register(user: UserCreate, db: Session = Depends(get_db)): @router.post("/login") -def login(email: str, password: str, db: Session = Depends(get_db)): - user = authenticate_user(db, email, password) +def login(request: LoginRequest, db: Session = Depends(get_db)): + user = authenticate_user(db, request.identifier, request.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/app/schemas/__pycache__/contact.cpython-314.pyc b/backend/app/schemas/__pycache__/contact.cpython-314.pyc index 820c952754cc2a31839a47c2ef5d18a39020f433..0bc68cb370c9eafbd391c139e93ad5b946c2101a 100644 GIT binary patch delta 19 YcmeAa=@jA8=HumJ00JMDja>gZ0U>k)r~m)} delta 19 ZcmeAa=@jA8=HumJ0D>Ki8@c{-0stfJ1Q7rL diff --git a/backend/app/schemas/__pycache__/product.cpython-314.pyc b/backend/app/schemas/__pycache__/product.cpython-314.pyc index 3b37ae28b1b6c85a3319eaeeac5da8b5a0340678..9342840ade202459f53f4ab66e76b0c0ab836937 100644 GIT binary patch delta 20 acmdn5zF(bNn~#@^0SJ6p{%z#mA`AdBh6MQl delta 20 acmdn5zF(bNn~#@^0SIjW{oTmDMHm1#^abMp diff --git a/backend/app/schemas/__pycache__/user.cpython-314.pyc b/backend/app/schemas/__pycache__/user.cpython-314.pyc index 450ef46debf4d4fb94aac6d055c86b45ade529f2..800d4cc79960f04dc0c1b7a58db3e9dbc778323a 100644 GIT binary patch delta 1081 zcmZ`%&rcIk5Pq-wy4`kvwY0QLtCY4Bsl{->14M~YLc}NtHKNhb*gy>=Kykaq7=yHu zXp}Uec`=crCURHL{sG?TL4u73J$Ufu)<)C=ab8;^H1Q?#y_wm!lbP?E*P(Jmjw`YQ z!TDv~oBQ%gp5l_nAxfbfnnDInAj6Ur4bF=)D1i+EW}QTRIAQUjdBNZloMD?phLFG! z6eDHFf<|E6s7mn2+a`LpY_%&=Fq41 zaBgHL5X=qlvfox|j)Yn+z}Evbx#$}b)_@^#A43u_Bm+YV{Yql^H%uR-53O!GY<)%_ zTb*_r8@NWtbJ=}ZXpC>Kjjv0rp?X)~Q5%1YcY%|ALhtk1%KwKmS(m1O;Kg)dszgGj zoh2MdbQk*y$4aE#6j?$-E&h$j)59eaHD#7i_Wh!=gp)Bg@?L}|ofQ{k{!wO$sq}{0 zD(-8NhHNK8-K=Q_wN}|Ofhsw)6O81}?Jy$gU0YCQwN_ERS~s2Gj^aaf%eDy|=?X|1 zq8ma4Qg4M(K+!&cLv$SMru_w=-EkW5ykh`IYuQc5Hh9b8Rlsd=0LQ6Qn#cWgSuy}C zQZL?PYez@rH99MM=tsFD#=*8Z8sro3#Rm0Kee*1HGY#fpC?Ab0Yb$X^{mc41uw{an zKYMV?0?k{dH4EWy7Q)voM0>LkezPG&kJFRCXfoc+7NolKW2T$=9ux?cNSE2l0xxp4 zm55gH(Q)-kxYls{Uje>(Q259e(RO)k4VnmGH$dO1L%5sD&TATnJJz)-f~zVCIJ(|e WMQ~NUbj8`JDmb{_{{z8QQ|u3!u>czY delta 808 zcmZ9J%WD%+6vpq(%%qcfX7Xr~)@I0~Nkfq$Rj~MgTEV)gkAPLgD!~Q}*0#=!SQpa9 z2QDPgaC}g3r3(>P)qlfrb?aZn=#lc=`P#KQUj4U0qtJauRtvFR!<+|a9v`ub_^A+2^JZDWmf%Bm}T;lsQ z9nb%~MXr%|no*wsLoR?WsETExZ^f8o;3$`hjAL0LOJs^Tv`n19X&q`pHa1CyX*rM} zGomAxIY*f!jtY;25`7O%YQi_+DZJ9+@f<0M7RzcWC@o?B+}vyxJ_au?vj5vxP&Pv7 zi50GK)#Y;@M=1RyN1J1fJuYv00#3ak(Yl*w8&fWy@w%8H65KJ|*y{3WFT@OqM0!?o zcl%tv-;nVj@HIA8(ETdI|u(*3mk(EsRoI_Y( z&L#5J%q<;+3!mY zfZ>p{>~n4fYSaD`60lFdy*Pkt_MMIN*lJO(ANU_|vJ-GmC*VXU;B+V89&ZzHI95OG zF)&`3=+XKiuh(Fc2c#m72>te-Iq}w*CPHMa{7Q diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index fc38c43..ae1f30a 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -10,10 +10,13 @@ class UserBase(BaseModel): class UserCreate(UserBase): password: str + username: Optional[str] = None + phone: Optional[str] = None class UserUpdate(BaseModel): full_name: Optional[str] = None + username: Optional[str] = None phone: Optional[str] = None address: Optional[str] = None city: Optional[str] = None @@ -39,6 +42,7 @@ class ResetPasswordWithPinRequest(BaseModel): class UserResponse(UserBase): id: int + username: Optional[str] phone: Optional[str] address: Optional[str] city: Optional[str] diff --git a/backend/app/services/__pycache__/auth.cpython-314.pyc b/backend/app/services/__pycache__/auth.cpython-314.pyc index 1a25e92bbab5d98058060e120692ca119e3a808e..4351b98caa6cee1bbde1ad765c8ebd50106a33b4 100644 GIT binary patch delta 814 zcmah{O=}ZD7@ldeX|mbIO`4bw3)#||kQh|(gCZ&lg0|QYRN@D1<7P84Ym-en*%ecS zt@IQ;^e`2AQ1DzlC_RZD^&of&2*t%;;3*QMcu{B8v<+Sy*ynxT&wbw6-8^BP@*VMw z@PMA5+%MHU@Y;7*^zv#6X}Vfg5hl7gZ3dwD9Z-0d>@xGRX`6S7ZQfTt=^r#ZkH_g8 z#lfcakDFTRJA0=g04rbtT!Yi#3T!yyAP;ifZrQ$_bM5kzw1b_Thg=8-edAz1mE%d) z9}xxu!oL9@4MhEYDSM;*q~SjlcjleRzq3JU41h5(H`;f>&Bx|!EkYQktbh5rC`wWRhLXvQtGRrR;y1BP-ftBC_ONYe+5Oc}nPF zGRxT8U1luDV1X=#4s#Vs<*lco7=#9Sb?_}zNHu&M-Y3t)Nlv3lgM191b);aH@R4s` awj^dfSR=n9F}O@((Rq&j{2B5ndh`!h(8#9% delta 530 zcmca*y2_YOn~#@^0SJ;F|IHNO+sKz7z{Z-Io0yq1xl+Kho`q51BLk3_qy7Oztx*P3 zdxXK%5s5P@N9DlsMT$Tpl9@pUK>-Vp2Jt^%0}>4kVGIF`P#GjDh6% Optional[int]: return None -def authenticate_user(db: Session, email: str, password: str) -> Optional[User]: - user = db.query(User).filter(User.email == email).first() +def authenticate_user(db: Session, identifier: str, password: str) -> Optional[User]: + """ + 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): return None return user diff --git a/backend/migrations/008_add_username_to_users.sql b/backend/migrations/008_add_username_to_users.sql new file mode 100644 index 0000000..ca8b5fe --- /dev/null +++ b/backend/migrations/008_add_username_to_users.sql @@ -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)'; diff --git a/deploy-complete-update.bat b/deploy-complete-update.bat new file mode 100644 index 0000000..0ffd1df --- /dev/null +++ b/deploy-complete-update.bat @@ -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 diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index e2b20ad..75e72cd 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -10,7 +10,7 @@ export default function Login() { const navigate = useNavigate() const { setUser, setToken } = useContext(AuthContext) const [formData, setFormData] = useState({ - email: '', + identifier: '', password: '', }) const [loading, setLoading] = useState(false) @@ -36,11 +36,9 @@ export default function Login() { setLoading(true) try { - const response = await api.post('/auth/login', null, { - params: { - email: formData.email, - password: formData.password, - }, + const response = await api.post('/auth/login', { + identifier: formData.identifier, + password: formData.password, }) // Check if user must change password @@ -55,7 +53,7 @@ export default function Login() { setTimeout(() => navigate('/'), 1000) } } catch (error) { - setToast({ type: 'error', message: 'Invalid email or password' }) + setToast({ type: 'error', message: 'Invalid credentials' }) console.error('Login error:', error) } finally { setLoading(false) @@ -144,12 +142,13 @@ export default function Login() {
- +
diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx index dadbc47..43e5624 100644 --- a/frontend/src/pages/Register.jsx +++ b/frontend/src/pages/Register.jsx @@ -10,6 +10,8 @@ export default function Register() { const { setUser, setToken } = useContext(AuthContext) const [formData, setFormData] = useState({ email: '', + username: '', + phone: '', password: '', full_name: '', }) @@ -30,17 +32,17 @@ export default function Register() { try { const response = await api.post('/auth/register', { email: formData.email, + username: formData.username || null, + phone: formData.phone || null, password: formData.password, full_name: formData.full_name, }) setToast({ type: 'success', message: 'Account created successfully! Logging you in...' }) - const loginResponse = await api.post('/auth/login', null, { - params: { - email: formData.email, - password: formData.password, - }, + const loginResponse = await api.post('/auth/login', { + identifier: formData.email, + password: formData.password, }) setToken(loginResponse.data.access_token) @@ -83,6 +85,28 @@ export default function Register() { /> +
+ + +
+ +
+ + +
+