From 0ef971cec47d9c98dcfa64b012640a879623a4fc Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Fri, 14 Aug 2026 18:58:18 +0530 Subject: [PATCH] initial commit --- .forgejo/workflows/beta.yml | 68 ++++++++ .forgejo/workflows/prod.yml | 47 +++++ .gitignore | 1 + Dockerfile | 22 +++ api/__init__.py | 0 api/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 156 bytes api/__pycache__/admin.cpython-312.pyc | Bin 0 -> 200 bytes api/__pycache__/apps.cpython-312.pyc | Bin 0 -> 395 bytes api/__pycache__/models.cpython-312.pyc | Bin 0 -> 197 bytes api/__pycache__/serializers.cpython-312.pyc | Bin 0 -> 1780 bytes api/__pycache__/views.cpython-312.pyc | Bin 0 -> 2835 bytes api/admin.py | 3 + api/apps.py | 5 + api/migrations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 167 bytes api/models.py | 3 + api/serializers.py | 25 +++ api/tests.py | 3 + api/views.py | 39 +++++ customer-website-backend.service | 21 +++ customerwebsitebackend/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 175 bytes .../__pycache__/settings.cpython-312.pyc | Bin 0 -> 3417 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 2425 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 693 bytes customerwebsitebackend/asgi.py | 16 ++ customerwebsitebackend/settings.py | 161 ++++++++++++++++++ customerwebsitebackend/urls.py | 31 ++++ customerwebsitebackend/wsgi.py | 16 ++ db.sqlite3 | Bin 0 -> 204800 bytes manage.py | 22 +++ orders/__init__.py | 0 orders/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 159 bytes orders/__pycache__/admin.cpython-312.pyc | Bin 0 -> 203 bytes orders/__pycache__/apps.cpython-312.pyc | Bin 0 -> 404 bytes orders/__pycache__/models.cpython-312.pyc | Bin 0 -> 4956 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 0 -> 3681 bytes orders/__pycache__/views.cpython-312.pyc | Bin 0 -> 9566 bytes orders/admin.py | 3 + orders/apps.py | 5 + orders/migrations/0001_initial.py | 72 ++++++++ orders/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 4885 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 170 bytes orders/models.py | 59 +++++++ orders/serializers.py | 42 +++++ orders/tests.py | 3 + orders/views.py | 159 +++++++++++++++++ products/__init__.py | 0 products/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161 bytes products/__pycache__/admin.cpython-312.pyc | Bin 0 -> 205 bytes products/__pycache__/apps.cpython-312.pyc | Bin 0 -> 410 bytes products/__pycache__/models.cpython-312.pyc | Bin 0 -> 2850 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 0 -> 1356 bytes products/__pycache__/views.cpython-312.pyc | Bin 0 -> 2422 bytes products/admin.py | 3 + products/apps.py | 5 + products/migrations/0001_initial.py | 45 +++++ products/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2568 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 172 bytes products/models.py | 35 ++++ products/serializers.py | 14 ++ products/tests.py | 3 + products/views.py | 34 ++++ requirements.txt | 5 + test.txt | 1 + 67 files changed, 971 insertions(+) create mode 100644 .forgejo/workflows/beta.yml create mode 100644 .forgejo/workflows/prod.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 api/__init__.py create mode 100644 api/__pycache__/__init__.cpython-312.pyc create mode 100644 api/__pycache__/admin.cpython-312.pyc create mode 100644 api/__pycache__/apps.cpython-312.pyc create mode 100644 api/__pycache__/models.cpython-312.pyc create mode 100644 api/__pycache__/serializers.cpython-312.pyc create mode 100644 api/__pycache__/views.cpython-312.pyc create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/migrations/__init__.py create mode 100644 api/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 api/models.py create mode 100644 api/serializers.py create mode 100644 api/tests.py create mode 100644 api/views.py create mode 100644 customer-website-backend.service create mode 100644 customerwebsitebackend/__init__.py create mode 100644 customerwebsitebackend/__pycache__/__init__.cpython-312.pyc create mode 100644 customerwebsitebackend/__pycache__/settings.cpython-312.pyc create mode 100644 customerwebsitebackend/__pycache__/urls.cpython-312.pyc create mode 100644 customerwebsitebackend/__pycache__/wsgi.cpython-312.pyc create mode 100644 customerwebsitebackend/asgi.py create mode 100644 customerwebsitebackend/settings.py create mode 100644 customerwebsitebackend/urls.py create mode 100644 customerwebsitebackend/wsgi.py create mode 100644 db.sqlite3 create mode 100755 manage.py create mode 100644 orders/__init__.py create mode 100644 orders/__pycache__/__init__.cpython-312.pyc create mode 100644 orders/__pycache__/admin.cpython-312.pyc create mode 100644 orders/__pycache__/apps.cpython-312.pyc create mode 100644 orders/__pycache__/models.cpython-312.pyc create mode 100644 orders/__pycache__/serializers.cpython-312.pyc create mode 100644 orders/__pycache__/views.cpython-312.pyc create mode 100644 orders/admin.py create mode 100644 orders/apps.py create mode 100644 orders/migrations/0001_initial.py create mode 100644 orders/migrations/__init__.py create mode 100644 orders/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 orders/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 orders/models.py create mode 100644 orders/serializers.py create mode 100644 orders/tests.py create mode 100644 orders/views.py create mode 100644 products/__init__.py create mode 100644 products/__pycache__/__init__.cpython-312.pyc create mode 100644 products/__pycache__/admin.cpython-312.pyc create mode 100644 products/__pycache__/apps.cpython-312.pyc create mode 100644 products/__pycache__/models.cpython-312.pyc create mode 100644 products/__pycache__/serializers.cpython-312.pyc create mode 100644 products/__pycache__/views.cpython-312.pyc create mode 100644 products/admin.py create mode 100644 products/apps.py create mode 100644 products/migrations/0001_initial.py create mode 100644 products/migrations/__init__.py create mode 100644 products/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 products/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 products/models.py create mode 100644 products/serializers.py create mode 100644 products/tests.py create mode 100644 products/views.py create mode 100644 requirements.txt create mode 100644 test.txt diff --git a/.forgejo/workflows/beta.yml b/.forgejo/workflows/beta.yml new file mode 100644 index 0000000..68484e9 --- /dev/null +++ b/.forgejo/workflows/beta.yml @@ -0,0 +1,68 @@ +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: 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 customer-website-backend.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 + + # ── Install Host Dependencies ────────────────────────────── + sudo apt-get update + sudo apt-get install -y python3-pip python3-venv python3-dev libpq-dev build-essential + + # ── Extract codebase to target directory ─────────────────── + sudo mkdir -p /home/ubuntu/customer_website_backend + sudo chown -R ubuntu:ubuntu /home/ubuntu/customer_website_backend + tar -xzf /tmp/app.tar.gz -C /home/ubuntu/customer_website_backend + + # ── Set up python virtual environment & dependencies ──────── + cd /home/ubuntu/customer_website_backend + mkdir -p logs + python3 -m venv venv + ./venv/bin/pip install --upgrade pip + ./venv/bin/pip install -r requirements.txt + + # ── Configure environment variables ───────────────────────── + echo "DEBUG=0" > .env + # Add database configs as fallback, or simple settings + echo "DB_USER=vignesh" >> .env + echo "DB_PASSWORD=vtechnosoft@123A" >> .env + echo "DB_HOST=127.0.0.1" >> .env + echo "DB_PORT=5432" >> .env + + # ── Run Database Migrations ───────────────────────────────── + ./venv/bin/python manage.py migrate --noinput + + # ── Configure and restart Systemd service ─────────────────── + sudo mv /tmp/customer-website-backend.service /etc/systemd/system/customer-website-backend.service + sudo systemctl daemon-reload + sudo systemctl enable customer-website-backend + sudo systemctl restart customer-website-backend + + # ── Cleanup tmp files ─────────────────────────────────────── + rm -f /tmp/app.tar.gz + EOF diff --git a/.forgejo/workflows/prod.yml b/.forgejo/workflows/prod.yml new file mode 100644 index 0000000..1ef7ce3 --- /dev/null +++ b/.forgejo/workflows/prod.yml @@ -0,0 +1,47 @@ +name: Build and Deploy Production + +on: + push: + branches: + - main + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: https://github.com/actions/checkout@v4 + + - name: Install dependencies + run: | + apk add --no-cache aws-cli docker-cli + + - name: Log in to Amazon ECR + run: | + aws ecr get-login-password --region ap-south-2 | docker login --username AWS --password-stdin 764709663363.dkr.ecr.ap-south-2.amazonaws.com + + - name: Build and Push Docker Image + run: | + docker build -t 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:latest -t 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:${{ github.sha }} . + docker push 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:latest + docker push 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:${{ github.sha }} + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + steps: + - name: Deploy to Production EC2 + run: | + apk add --no-cache openssh-client + mkdir -p ~/.ssh + echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }} << 'EOF' + command -v aws &> /dev/null || sudo snap install aws-cli --classic || (sudo apt-get update && sudo apt-get install -y unzip && curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip" && unzip -q awscliv2.zip && sudo ./aws/install && rm -rf awscliv2.zip ./aws) + aws ecr get-login-password --region ap-south-2 | sudo docker login --username AWS --password-stdin 764709663363.dkr.ecr.ap-south-2.amazonaws.com + sudo docker pull 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:latest + sudo docker stop customer-website-backend || true + sudo docker rm customer-website-backend || true + sudo docker run -d --name customer-website-backend -p 8000:8000 --restart always 764709663363.dkr.ecr.ap-south-2.amazonaws.com/customer-website-backend:latest + sudo docker image prune -a -f + EOF diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5e96db --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d94f81e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Expose port +EXPOSE 8000 + +# Start server +CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "customerwebsitebackend.wsgi:application"] diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/__pycache__/__init__.cpython-312.pyc b/api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d936429ffccf16b70a4fcbb696f3de51c6d83f0 GIT binary patch literal 156 zcmX@j%ge<81Rn(Jvq1D?5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!vewVY&rQ`Y%S_Kp zEzZzS&n(F(P0~*;EiM5H7nP?b6=#;DCM70kr{<;TCl+Mt$H!;pWtPOp>lIY~;;_lh cPbtkwwJTx;n#Bmj#URE2KczG$)vkyGXb>Y1 T7lRldm>C%v?=dJ8u>m;%M|?As literal 0 HcmV?d00001 diff --git a/api/__pycache__/apps.cpython-312.pyc b/api/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0a8220e76251590f9367cd3ccb027ad0fcbf696 GIT binary patch literal 395 zcmXv}%}T>S5S~d=ZJQQb@CCdGxim*FB8Y;wg4bM@B)cZ5`7=LCP9pRHy!9ExNAU$D z;K`G>LiEy;vq?Iz-+bTBe7iFr)9Dx}w)5xJE#+@1_A%&5b4p|b95{v$p@j$v+yGbB zz*T|oYS(8em1Ycnq%g1^U8d<(65j=$+m$=SUNkcz8xUY22TS2-sg+`=(iU{KTD8+) zX=wG3vDl6TWA&J^DB)!&`Gm2D(hmEICi5FK^7Y-B=_ipe9|JEIxo>(w;g^o-mU%(f z*^_Ycpb(Dj-iw%<^a-6bU!+wg7j8i#k{=SR;ZvWzTxXMgWl1yYd}YU8vXHh+F5ek- m$=b##vEA^zoAn|)A}ROIj|sLaB7VWi?*Zccb@T_6TGv0Bxm@J{ literal 0 HcmV?d00001 diff --git a/api/__pycache__/models.cpython-312.pyc b/api/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0df0b1cffcf981dc2ccfbb11b848515bd1714e0 GIT binary patch literal 197 zcmX@j%ge<81Rn(Jvn+x1V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zYn zx%nxnImLdOOt&~wvJ&&s^Yv1aikN|tD;Yk6)cms4&&bbB)i29T&r2=N&`-}S$tX?I zPcAJk0SXtDrzRC=mZT;nCTFMSrRXOXWa>jq)GMg`#bE$JfVg%x15aR644^U z@;1T>5vs}PqS1dJd~hnL8!cLWe0BiW`cbcB>)(HKAx~sNB zN?bjnVQ{;!vx5jK2pAPa5fQ1XMM|ST7O;qg18Eq5^1n4>=W8Cw`$FGGK@C$^AobY3 zIM~y$x{V6)4b6zNw|G+bN!j*%LYIg6t}GZ5ORQ7dC#G9>Hkd}qqoz#>W@DD)xa(%o zvFdAvKzUOCs=&Yt>Rp7BQ3Z%pkjwB?KM4h8Lo}q*ci-%J{vPoyLt$DI0;^j!!W2@o zY=>zL%k$PmfOvXv?n>D1Nb{T?w=+9c?bq=Jq~0RGzQD(B_V{hh06} z9t&~1WF%;YXOZP>f-??U7Qa)m=mKw}vo+vA4|Aq(b14LWSO*R)${DY$$HzBxNphzB zc_%p&Bxm+k4(~qqkCQ7~ce|-vd##ho1*zPge@I?@`QSM9)z)HHpKX8J(PsmF_L+RF zf3WrG-?FGI3V$Nbd)k;X(Rat4EEyV0!GQ41RSL5KM#ow|@=LgC)ywXD$*ud;F3wv` zzcL@r1+R~ZahNhN^P{~G2l_oeIF^TfALel;!Bg#{{_MF;Z$JRNo4sjZFC{?`PSD&5 bx^aSxSMr1~@#8n0sawI+t-lZ~!kGU7I24ag literal 0 HcmV?d00001 diff --git a/api/__pycache__/views.cpython-312.pyc b/api/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a2827151ea0b34ab610a0ac7b900b5dd0873871 GIT binary patch literal 2835 zcmb7GO-vhC5PoZ~@%o3F0_HDn>MDW64KWloX;W2EAy68L7z!j2R-%>hKFpH!Pu^Px zfry$z4j>_sRFz1iN@-6`M5Gd_Qm(o8VhbcYD^L$T<(7u@l2d2aHa4ysHSaJxZ)V=S zpZR8Hf3L515*SU5-_3dI2>BByrC}>FdnsVni9!@XBXJ=t#4TZqfOCsxjf-K?PjkbSqAad*t|k*aAQ;j(<(^OsaQ53G2RQ}#l@rGn9dlW`hTxlY*x zWokrI7b%MtHZM>##j)27WSvkF78DY;Q0tsz$`6p35Vk48Z4$0qumr3|L#2N_K-5xU zE&`AD+^V$dWKuX;)nGSlB39+_WrN=1Q+$)Z%BO$g`86w^t~NY)LN=-BHpi z7R9Oxx*27Z&ACgO^7pa0Q9RE?#Y))Yy|PV-tMJrNl9j)eo&vTI#zTqd5yKv*oJV4G z(T6YQXT^|E#n02xDClO=nzj%S7`|7)!SML39*7~ST)qKeQw)(uZcv8LVpTdAfoxmgx2{~@g>2u2ZBHm8 zh4#QX2?gwiB+H3NoXWD{l;wC*No&ZvW%+tKq7^g@foVwnnwFgJPb?UX<;0SsT7+}T zSsR!Wlyx(Muox}G_RdzeW3aOX{J~p z+3X9}fM|H|X~$S{M%C!Qat%*q=GrsUJ!BYstH(`?jyC34vM7+}r8;!es_*9;;2tVr z^#Ey&daz_=?j`Hoes+~tB?QF++D6dU$T-FNqgcxJ38vbfXOe(z|; z-?urq)p%yv4$#fF`5x5Yug`ni*TZ+i&$~|Exx9Y$`>UIM&->2Yna{Nbv#r6u9JY4n z9~V2?Iiv)Wq0b^AgCU1i$AQuyH<@_RTkn}6APsp zx@3{}J%J@=Le(hOX+X%#JM3(&ji&vbt@1u{-@&U0UTakP3a|UU1}xutAN~E(Rk8D{ z`yFk91nePp1e9eMvg0_#jTe2zwDzY{&@QUGg2Til`xwo!C$JzA6VBLzOiZ&A$eGOn z`*<~OQ|TJG1>s?|T?g{txXpWg>sRhx$$2}o-p-$55A|OcA1!WqzgQMmoq5T<=2&s8 zx}Lil^S&bwM(&T~eBD`J_ruGZv8VAT@h#ur^6=_~eAmfew4b$H*XeB6>CJ^_$8rNx z*@3C8uJH2kw);rl-Ly8cGO`-N^4^{~I*@5Qx8)hkNP{NOLjj3(;Pnn6@gYHg8J_dJ zKdsNwgsw*M)fnEU@HoB5eTbx7!9T(&5{wZ?H^mBmD%)`?(|#H%_$yJex%QeJwxMDf zNApmi-gpm+i!RSFs66vYcFk~bXwCBPP0YcBbLDkjXNM9B@-C6b-m_P*}9ADt2RS<+fNN|^Q l?~>!Yu*uC&Da}c>D`Ev2%Lv59AjU^#Mn=XWW*`dy`7A3w literal 0 HcmV?d00001 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..9d49b09 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,25 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from orders.models import CustomerProfile + +class UserRegisterSerializer(serializers.ModelSerializer): + phone = serializers.CharField(write_only=True, required=False, allow_blank=True) + address = serializers.CharField(write_only=True, required=False, allow_blank=True) + password = serializers.CharField(write_only=True) + + class Meta: + model = User + fields = ['username', 'email', 'password', 'phone', 'address'] + + def create(self, validated_data): + phone = validated_data.pop('phone', '') + address = validated_data.pop('address', '') + password = validated_data.pop('password') + + user = User.objects.create_user( + username=validated_data['username'], + email=validated_data.get('email', ''), + password=password + ) + CustomerProfile.objects.create(user=user, phone=phone, default_address=address) + return user diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..567c238 --- /dev/null +++ b/api/views.py @@ -0,0 +1,39 @@ +from rest_framework import status, permissions +from rest_framework.response import Response +from rest_framework.views import APIView +from django.contrib.auth.models import User +from .serializers import UserRegisterSerializer + +class UserRegisterView(APIView): + permission_classes = [permissions.AllowAny] + + def post(self, request): + serializer = UserRegisterSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response({"message": "User registered successfully"}, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class UserProfileView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def get(self, request): + user = request.user + profile = getattr(user, 'customer_profile', None) + return Response({ + "username": user.username, + "email": user.email, + "phone": profile.phone if profile else "", + "address": profile.default_address if profile else "" + }) + + def put(self, request): + user = request.user + profile = getattr(user, 'customer_profile', None) + user.email = request.data.get('email', user.email) + user.save() + if profile: + profile.phone = request.data.get('phone', profile.phone) + profile.default_address = request.data.get('address', profile.default_address) + profile.save() + return Response({"message": "Profile updated successfully"}) diff --git a/customer-website-backend.service b/customer-website-backend.service new file mode 100644 index 0000000..c0a6b1f --- /dev/null +++ b/customer-website-backend.service @@ -0,0 +1,21 @@ +[Unit] +Description=Customer Website Backend Service +After=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/customer_website_backend +EnvironmentFile=/home/ubuntu/customer_website_backend/.env +ExecStart=/home/ubuntu/customer_website_backend/venv/bin/gunicorn customerwebsitebackend.wsgi:application \ + --bind 0.0.0.0:8000 \ + --workers 3 \ + --access-logfile /home/ubuntu/customer_website_backend/logs/gunicorn-access.log \ + --error-logfile /home/ubuntu/customer_website_backend/logs/gunicorn-error.log \ + --log-level info \ + --capture-output + +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/customerwebsitebackend/__init__.py b/customerwebsitebackend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/customerwebsitebackend/__pycache__/__init__.cpython-312.pyc b/customerwebsitebackend/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..509472c3a2699c8828c6eb3bc065c82072452014 GIT binary patch literal 175 zcmX@j%ge<81g{0_vq1D?5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!^3%`A&rQ`Y%S_Kp zEzZzS&n(F(P0~*;EiM5H7nP?b6=#;DCM70kr{<;L5{{40%*!l^kJl@x{Ka9Do1ape ZlWJGQ3N)S(h>JmtkIamWj77{q767y>F311? literal 0 HcmV?d00001 diff --git a/customerwebsitebackend/__pycache__/settings.cpython-312.pyc b/customerwebsitebackend/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6870b4e2b695428ea1099c03e62e50977afbcef2 GIT binary patch literal 3417 zcmb7GOLN=S6$T&TQ=~|}Ov$!NMY3h8Avm^Ux9+5_Arh1jQzS!xR*gk55bqTQ86X${ z)B~Hc>u$Ph7hYwRO!`l{k*Aq4b63iAy6}#2rkkvK08tW6%TpH;0o-%WJ&*4^F8&dV zMJTv#P5yC@Pf*mqoW=PyF?jju3lBy8l|qzAArJDZ9?dIyJ(QQiUhMl0c+RyKK6?(| zzZ)#}TpPn2kiUW^29Qq-BEJ}FQewDCp|P($Vg#N6ct%lB97n)Ge&p~XUwae}ioWzW zJz@-vi*Xb~aU4Jsc9fz};-y#dh!ZG@CP6BRrri6qd!JD}O~05xvtklm6DPsm6#S-9 zN}K^5b7)=x&EhP&j&8v78t_sO_f2(9n-}NZxaZNr-+yr)Ip8pl(wA$r2p%q?PtX!t zcK>dn+h_&dL7A6v@%qtnW>tO!phXIMa;vGQO>9|8t7)cpbR*s9n3k?#;{b1)iiNl3 z#tYm+>9(Qo;f6&=BI~$?4cWpdy?vBkL9U9dj5MW{HZ9q(29hi3jtNTB0~-(NyRf$i z1~lEkX{80WG}%)0RvP}TT?MQwD*hCl!I53dYMZ~w<&fSm=`#!G_8YpE!>t^s%UOC` zX_&cl?{jDBtzDen>8NU2RZJ_b?_5SFLm0qSjP$;&cCh*X*fQ|WkfAqDA!R(oUzM$0 zdu@P$(lT+QW8m!JXZxM|hj;KFnyYv2A3nLaZ8w(H<<8;K0~_N<54IQY9b~fmtyNWi z(rzv3okdf~jFY&5O-tG_WDOta#tV{qcP*KU)>iS}fkmPX-7t5tjId#n5Fmp(4a+0} z-Eb*BxvkjKXHL-#y=587Htiy88|i}E_QHT|l1aB{4lN0)u>n3pHZwkZbyUs!QnZ1;f?l-jUk+q13f6(`3V8=k z`~HaV1yomTIOk>jB)G=ppR(lw@fWxnPeM$!D!FgLN>$)0W!_%-04fJ&Q=#Q{8<2I= zMuG_M$Q{+XD0j4d36lAh3ZwJB9RcFtJ>w+-w!F@jS>i7O$TQ!chV(k~82WrR>2O0ObIaLX#Es5^6}e zsjHY~9rD`QVMP`SY>}y#1c|8&8*EwN@{E&SDPLlEp5-$h`_YGnEA6DrHK=P6KCJ2in8Qb9RJ$Psj#z6MsR6rd~JIb0$vjuILeSwd% za0F&my&2g{EKVOEJdVyohd6on?p0Bet zl62A`ZLmxMEC|o5EKd@{B&RP(tj+_MD@a9-Efp@8kUMTObAtpW2|f!Xi3Hnl_^Qgb z;RD!7NRpf*#-T%Mb#)(;KpQ}8StPW^@T}A4B*L@#8Y{TojuqIo`Z|d*rBY>!El3*` zUf@ZbEAs-7fB?gRCy`CAP$;omOpPVuwMs>h>a|k7QZACPz;0Gc01X7O#jkUYd}&Z< zBn+4_j#+@#jj#%Zw^gYXq-RWt17-!rPprh0*XztWE9EN%78F9_B(VY$A_N#n+}&T7 zNx*p%MDQHa#^oLBB)G|NB^DIKA;W@HtO0DG`ze{tgIdWA$hk|9B*+)cIT#|?saa#% zfhxjt;5jQnX@5)o1QhD$a&8yC`EvVEoY>sWH5F^Ovz@zo7Ji6){vAfQkA4n)uE9r% ziXR&rpx$W`a~JM?pU2}l^+r7Y|D-6-+-ZOsPy8kH=f|(3i?5=K|BNoZj;_9nuD*`m zeHFdi4fW$xyyodf`T;5&>yDiyCcCk>k=UfS8-KeHj(dSKOC_@1@qUP!W<9;cWN$ik zGM;`*`Iq8-Utl_N8jg+y`o37q+YR>@sOjGxQwh*I7Vk~cy+rD7pTA11oR0YtAT#F5 z%v0fsfff4sc!6KJlPGOL?^nT6L31vAE)Bi zz){y|D0w`UhfR(>f7IIz_7l|Dtv=;laR^RaAU?lvOwInATKMti?QfXxZ_+<3tbFt3 y_Y1k+%};vi<=)&vFPZ91m3lLCy~K1cKG~aF?xj9;mNN^dHz(rWKShipApT!cwsAfH literal 0 HcmV?d00001 diff --git a/customerwebsitebackend/__pycache__/urls.cpython-312.pyc b/customerwebsitebackend/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e91c27fd7b2b8ed767339e56d069ce5dcb3faec9 GIT binary patch literal 2425 zcmaKtPfXiZ9LJr+2?_b1&;cv$SO`%_7CfS^n`j`6(2W5rw1rOdrbQ+(i9?)3wgbYK zX}2BQ6IYI%*mcs59FcahcZL@B8`vo`3H>@>ei8 zO28A3{oMG&LlFOlP| zn!2~P-4tR+yWvvl=#3#i~|uKN=(>@NgyIai4SeWZ6KmU zi76X#2Z-2E;;xPO2#EMlV%kQ048+(_A~7IpA$sCuW-j?JI%IPW!_8EAt_23!VWj$; z(}?f3582lCzQk~?9flL0aqO`X!Okb_fxwD&g8o)>MU&XGdrsa7`AA zTt=kCNNlYwoNR#%rwW6h+O0_M98t@$#zM&^<~)YC#0aK7hOBQSmLJ58K&j4F4%=Nz zvBRA3Y)g9DtyEZ1#8o2OLX{QPp(xZg>tfF!iR{5&PRVzf4o4j=P?au-c~+p9u2iQ4 z>%%C++c!~@S`5$1x349d?Hbpj3^#tucdpe4W(O%FD4{Nwt+_m5S9i=VQpSj7y`VoAhB8R>l9Sg4 zIoLOm8l;RMZ4P<+N#E-}PFk_{7na;N<=Ja1bj-caiRR{0a@gjP$L(Aj9SxC^gV@N` z%|jwj-=G_;K41c7J08tV8lh@~Y1P{Em3B)KxP1Zr?>79Fvnw=3fkd9WaBHOUfdFUc zxGfx(MUL+@*~YOne*{-a6x<*Z;8%_?e6R+~If9&CIVbJ zA4Ee44Pi7y&=7^eAVuiCL87G@BaB!^^$kBv;qt>RZi&AUZ$L-9-Jt4l7pNny)`A;P z)i|l%-G6sQj@f;bXrHOTA+Fx!MpfoJCvUw49!_18T^7=i+5vy!a~NN{91h1TV)_*^ zbw%u45uaZX+bXerMaViK|K}mxzF#Mve|Ea~V)3;2qNtHG*r)jBlx#`KKG3rzHCxiM z4=*=*&S#+GVjv{(vJh z%534Rtj`wI*@8B^eDQsc+_nlqx)mk;g`Qqf(<@qf^>V65KEqH_NiChV_0*D@TGCR* zi$;%pi~~7^T34tQomy9^b&c9k9y2|1-^w?wB=TozJ&{)vc`b48&-*=c6;qV5u&FGR z^o31zVN+Z9R@pu1ku@BGk1f6|=-Hy0Eo#{>FGqXiQylnI$!{t7H9fzj=C`!`BV`XU zR;7fJEGWr)da|G<3tDpdqTD0*FqTzvYcI=sZcWXtX}Pa1f9jDxzRrz@oxk{n6zt6Z DT@;p# literal 0 HcmV?d00001 diff --git a/customerwebsitebackend/__pycache__/wsgi.cpython-312.pyc b/customerwebsitebackend/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4122cca6d7d0f50c9c86cc607cf51031b8735fb0 GIT binary patch literal 693 zcmZ`%&ubGw6rS0QP1)F@VtaKEF9F?YFCIh$p*5svil!puvYG6>Y$ndmEIX6L^ddd^ zC-@h5kp3xNDhk4$6j2Z_N^U(l+cdO?`VRAc@xAYRGxK3(B|y%8Uwu7%hY|W-CJU>a z2D6?E-Xe}Vh-2q#KY5JpnXM-uKx7=t8ZmWz{?VkvOa?XoPBB4$=-)$O`@j~r>Lb09)F{`90lv~t6R zA$=j?HZcIfz}n2*qm=8|gnYJ6JZCV z9=%w^|H>t8m`#tD@6Vs%LB8rnyPt;4hCBZF7~{WQ6<2205DtEz&9BIN6C4Cz(Ar0| acC;QIZ9M&qo_$-r`DyjmK`p=RK>q;lSJGVo literal 0 HcmV?d00001 diff --git a/customerwebsitebackend/asgi.py b/customerwebsitebackend/asgi.py new file mode 100644 index 0000000..9867c5e --- /dev/null +++ b/customerwebsitebackend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for customerwebsitebackend 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', 'customerwebsitebackend.settings') + +application = get_asgi_application() diff --git a/customerwebsitebackend/settings.py b/customerwebsitebackend/settings.py new file mode 100644 index 0000000..157a24c --- /dev/null +++ b/customerwebsitebackend/settings.py @@ -0,0 +1,161 @@ +""" +Django settings for customerwebsitebackend 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 + +# 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-x4vu3x(e^g*(3x@2bzc$l$ux#5zee95b!2w)-vn*la@pn#ou!s' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'rest_framework', + 'rest_framework_simplejwt', + 'corsheaders', + 'products', + 'orders', + 'api', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + '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 = 'customerwebsitebackend.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 = 'customerwebsitebackend.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/6.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# 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/' + + +# Email +# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration + +MAILERS = { + 'default': { + 'BACKEND': 'django.core.mail.backends.console.EmailBackend', + }, +} + +# REST Framework and CORS Configuration +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ) +} + +CORS_ALLOW_ALL_ORIGINS = True + +from datetime import timedelta +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': False, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': 'django-insecure-dummy-signing-key-for-local-development', + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', +} diff --git a/customerwebsitebackend/urls.py b/customerwebsitebackend/urls.py new file mode 100644 index 0000000..6734a08 --- /dev/null +++ b/customerwebsitebackend/urls.py @@ -0,0 +1,31 @@ +from django.contrib import admin +from django.urls import path +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from api.views import UserRegisterView, UserProfileView +from products.views import CategoryListView, ProductListView, ProductDetailView +from orders.views import CartView, CartItemDetailView, CheckoutView, PaymentSuccessView, OrderHistoryView, OrderDetailView + +urlpatterns = [ + path('admin/', admin.site.urls), + + # Auth APIs + path('api/v1/customer/auth/register/', UserRegisterView.as_view(), name='register'), + path('api/v1/customer/auth/login/', TokenObtainPairView.as_view(), name='login'), + path('api/v1/customer/auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/v1/customer/profile/', UserProfileView.as_view(), name='profile'), + + # Product APIs + path('api/v1/customer/categories/', CategoryListView.as_view(), name='categories'), + path('api/v1/customer/products/', ProductListView.as_view(), name='products'), + path('api/v1/customer/products//', ProductDetailView.as_view(), name='product-detail'), + + # Cart & Checkout APIs + path('api/v1/customer/cart/', CartView.as_view(), name='cart'), + path('api/v1/customer/cart/items//', CartItemDetailView.as_view(), name='cart-item-detail'), + path('api/v1/customer/cart/checkout/', CheckoutView.as_view(), name='checkout'), + path('api/v1/customer/cart/payment-success/', PaymentSuccessView.as_view(), name='payment-success'), + + # Orders + path('api/v1/customer/orders/', OrderHistoryView.as_view(), name='order-history'), + path('api/v1/customer/orders//', OrderDetailView.as_view(), name='order-detail'), +] diff --git a/customerwebsitebackend/wsgi.py b/customerwebsitebackend/wsgi.py new file mode 100644 index 0000000..d7a1326 --- /dev/null +++ b/customerwebsitebackend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for customerwebsitebackend 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', 'customerwebsitebackend.settings') + +application = get_wsgi_application() diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..08ce8a76b338a93d4e37d27456b7c6676daa0147 GIT binary patch literal 204800 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 literal 0 HcmV?d00001 diff --git a/orders/__pycache__/admin.cpython-312.pyc b/orders/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf6fcb2079a933de4de495902ee3e5af8b75f295 GIT binary patch literal 203 zcmX@j%ge<81n&jwv+RNNV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zZS zi7C06d48HqxA;=B67$mY^^)`RN{TX*ikN{4Rx*4BsrzNEpOK%Ns$Z6wo|jsjp`V^v zl2MwZpIlm80u(MPPfaS$EJ;mDOwLZtOVQ6SN=Yp$)(0D`S5Wzj!zMRBr8Fniu80F@ W79$WBgBTx}85tSxF(?$V0XYByq%`XQ literal 0 HcmV?d00001 diff --git a/orders/__pycache__/apps.cpython-312.pyc b/orders/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdad3764898a0e4720614808a8948329452688d2 GIT binary patch literal 404 zcmXv~%}T>S5S~fWTAKzH`vP8sT$-~Yf+%>aM8?p?{@iVpoJ8mYc_CW3e`(b?q=ILeDi%f^D(pY<~V(j@xAvry_We?iA{9YWO*pb0w_>SAwwS#47dbp z%z>H=UrpWT8@1(jzLapW**+`F^CG`VqKPhR#I;!NO0ob3K2q=vioRJJuGtK(IAvUD zXVWcOHFmA0M@SxIjF6^JNLJ7)ReV6mT@|G3ily=kEY*zFu@@H^^X`)t!-KJy0_ie$#ZU~ghRNP?lm;)?EF(1xpgKC1DQl_WsiaA8aaPxvWj6K^_$& tW#j4!+Gs1*Hae26dMA3_Q@$gmI)E5T_%RXj0}g(>h{NZjKai<){R3I+VJiRt literal 0 HcmV?d00001 diff --git a/orders/__pycache__/models.cpython-312.pyc b/orders/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6c8dbcd19cdc1778c1afbf0fbe0da35d12243a5 GIT binary patch literal 4956 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 J9QN6x{|9nQ`=|f_ literal 0 HcmV?d00001 diff --git a/orders/__pycache__/serializers.cpython-312.pyc b/orders/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80b9197c72a921f936a17b95e7a74016dc201e1b GIT binary patch literal 3681 zcmbVPUu+vi8lPRS?X}~?wd0hgrA@h}a7%S|N?id}TZ9@Y&}(R@LEIjv)y6wbobK9Y zc1^Kqg@y7^BqYKM>0`wcRY2+^kDzCy{|lEpR`Itp z-+VJW-+aH{H($OQ9*z=tMw4$Zu7(Kt0Xyv{*lKKi2aOeC5lga(Bk7VvEZGX!vJ=n) zj-o40P!BpGJp_7UDR$UVbyXrVIZdqK60t&5T1>VV?Y>t7ep`%LrIT zx3P=~%OS9gZDToH9o9zf;#M??54n^TOuJB{%)Kk2PT|TalX+Z4`MgIR9(<8ml<|mo zp$6t9&askJ$$RHJ)APP_zEoPBjXE?|2qn5?5nYxE4Or4A@+y7_9$f)Va53ihdL>u| zI#yv}wWD|3Z?CpgdPI1yfcL>w*;ndOE3`!Pp=wxDd9v+HcfUFCBEqO?8Kt6Kg--Mm zD|l4&`h01vM>clB!d+izKv(7gAspON`4f4dR?}pfepkVtzFdH zh!t{KT7>uD1KQp>t>GF_@jSU9&D@aLDCk%U#0{A%F10W4KxuB##aa9Jv%T=*v3cql z?IWvAY!QfFgAthGJ^-Rl*5k?dFTZ>FTE4k2-GIM%`j(m&?#yU0wi9d^8Zrz$i=arL zK(Bc0`JzYXDSH-An<3x%SQlP!Vhbh99~;9tcs%SNiajXAk>TdqP+MU7ai0RQM7|xF z_;_+{BvU`Vp3s)}G!t4Qq20`_C63i!SdUL#eXALtYQ(2*#~-Ud_b){b{zdwkpxjWU zV8&k?m+ixGhm)_tNEgRGCj~28f%t7!cK8O2gj^Q@z9}8PshIopNHwH|`3Q!fdl)$k zonscO458Dvt9J0WW7lZ^iaxUf43h#60)QQt4++K?f=Ixd!SLbnvT+oHlc_7<3ko{&f+eps->LOC#IdD+Y0zBYW9F~SY516*i{(wvLY>3APK3m)O-?xSF zcae5=@?AU$U;BLY%keM9zuf!9-oH<*C1-EPXK$&qtqc$ygb;E{>_%h&_6T+}CG}NKMyy2oFK(e)<*J4lBpIJ{HSw7TE9%&?x+)f^?pFuKDA(y9+$}dSi zmvMnU)^xaBhLL_QFRGv#cr3}1;XZA_Q4b>Lx3p9G>_UR&qN3@{4U7A9v{MUoNnRUn zU2JK%&}@pF54oiZ%TvMnTB1E%aLjpXRG8i8QfqVDUbDNl__Uu5RFMrthUV?P7-BEo zuK2dNQh2mdEO>sl;Bk+^1z0G~8^ww<2gOb}Z!%V(Y>PaC=etcFIRPg7;n&F{)sIlt5-Xckql9G;7HFd# z6{sCr^iUb|^PK6FDn$=K?wMZ2f(njN*X5D2ZRV*3MV&|*TVN>+9&LlA z(_r#P__#iF(g4ZmIzc~y8QcHZyJef79sCZA;I zVRgJdT?52V0ZEeXkcYn~+1n(0hdj2aM5WBNJB8IrC@*A6vzA8qVDx=FCNxg#Xy)9_aWEhB#uw_ zJnz_JW6YN2dUucHXJ(%F@4WBx{+>VMe{ec&1jyU^O1k!n1p0%9J)o%y7?(rj#WuM1-_8Volp3wzNHBXL57Ok#6SZBcWLfH2;4jneAW?6|Tv$ATRk!U(0%Zbdi9OhKv4N0DX zTM6zh!$)6zDs#bPE33S_nr4tfO#8QcQD0H#H zngF{!N;BeYTp5pRtDtcl18Le_YsMhMFVh)Xap|I z@nMrTJ)CviP}c+(9DggX16v}BUX-k_RgosE>Y>*nB+K36ZdT?v32hb(3&yNr0#VEQzH?BrM6x7V-7c(Jp)A*IF{_KVU{LgB%^Gy6QL*Uln{IWr<^;LHjfYLN3#zjv zr1(Y@`&4-`ro_TLZN~=-mENC~WQBI%lMObeqKo<>(@rcg!ph7$>etB)VtEU&4+yNo z#f9?=m*2iRd1bQb`d*HI=f<6(rcKL>#Fy)>&?n7M-)$0Dmp}GLSOd@(p$e;`iRxqg!B;_e4@Qs zv?MsCwOS+DR=ulTZwb|vj2qq|6XYy8XMBg8<-#rFs%=KY&}c$bh4*J;(@H{_qo^Fm z0E7)7wI<|fEUqNZNUBMqG(+b-72FL@XO!SnW_DV%_2rVQrsAPf&KVeVZFKM&P@_0rWJC}gdiU*m^Mr}QG?AX!h;I_f&_>p7LKRxp1_$WoSreMX1YGRXQ zv!h`Y@N(>o6c*Uwr)#ku9(>x14Bvr%514Ajc|`#`)6@^oG9onsE|P`^{7gnx=AAkq z;3JMGgG1015z4-vrOCy~k}p*7g|4NFz8!!K-u6ppm$Hl5Qpd(Z$Hwcn+r!0Fy~po4jxQUr__2(-dN;6cWC!_t$F|W8#xFt~@bR!!6{6AU zSXzokRa-Qg2HT#(dq*@1ikZ?%C}NrFdQ~2tRZdBut#Po3qS{(>KBDndOqL~C(;UbLn)cjFNi6v6Z;lQ(0B{SUM^UB9C))E0Ut(K5xR18k4B^4OEAM_G*zR zh}L=(%<@@dR4;{onjri$`mxpb187I^ zYCcAAb_SdjNenwREJFljsHpie1Nf*zAl-_O=!sB7Pt`0+DM^vEQU3%a;0K{;WpJk& zW=^X-LpFpv8GEUNHRH^zGT;8Rwhn+CN05I7>;tlFFj{|vAW-u57QDSB@5X|6Iq zVZ;hG)pa<1|8Kf(8K-+;ZcMxBe!OGajOt6j4=ikbZtWb#>YB@~S=|HJ$p|nRu^u&0 z18uB0-`PX~HKiWH7PG*fQwNs?Vs&1$pSNFdG6l-_4dwlNi>`fnVc(-w>x*g7u#Mcc zZ5!TeygS4J9|w105T-o6d7Wgdxhir} zD5N%gv$qgUx;2Ctq+v`rv(IZ^CWxZjJ<;+JL?WZ4`lk8_W>HX3I~X;KU#cyVXaO2X zgiKNw`AhP1!-7Q#RtuZ%R&TYmc7+(rEZVY`tO=qi#7Ce70cUyqui?(B^l7qZ?0>T! zg{(EXq1vi;1u5c71A!C|M+4n*+^lFDN1FGR#N*IF%||#r31^SHaH@Gf5HCJ=PxBB< zKn$sCQ1c#>Xs;Wnx_=zGaEk{WNTS_tpjw?e{dQ)BB)X*yl(i^`MvSuNsve0gAM@3F z7KPT%E}r)W`cC~O*EmIoli3n2Yzijipp>40Kooafb<3v`Gc$?llPsKsXmP%0q9Va? zk%9zUV7ewjB{5!wJ)ATG6}tZ`BdQ=_2$Z%jNm5Ikv z^1cDR)}KLJ83WLNBac2H_qvXJ)^>UD>fS4RuMd{`hYI~e54wj6T}S?l*~briL!4@6 z3B2gn=&q`k8A$2DYJqQuP^mhUj1o&lV`+%g6)iIM(Aa#2&cx;*s|cAt2>$7-P!+-v z6BDpdm%W&3o1IQ5Q5GxDYdP3p0wf9MgYwK_RRz75AL4*!ZK!a!=8~fRus~gNQ=jxu z)wW8IzAbuScr^OP*bmePL&<&MUV=R?Y%@W4-vgW}W<=Hf3NoOH3VM0Er8c#{* zsEv)QB|)hj2SjA3ZH;Pzyl4D0#j~TC+Yq)eO{Fq1C2XrX!P=Br;Quz(#bu#Kk>RV+ zH;|10lW|0FO)8|>Y*aMJnARCiw#A3A{sYiTOb51K%%0EYcyyB|!AuZ^O&#u2aQ`#RX?2p{{zMFTv z`}^+oSBI_)mAd;2-TnE^`->sB};vJZw{?jqKfi;nY-3oVejD0gfs z`vX^nD?)kQ_J{4=mtQZohyTNB>a?v8liR*bOji4&)d>zpF1dmQR}g~-+$2b7G%oo) z$GaFiOjcrtp9T;1lfUoZc4&?9cL5Ig_zlaqcz&oXjZWV|lpz~Kl|f_il+(AuZ#UUB ze-A6h;ipp-_+G@{Ydsp9OG83sVm2NJKz-`N2ikW8)0G^I@+_|}i8UIiE~|`rh=x*p zY}%?Wvk#u6?msrmV>D|Iz6sYX;=iS5s23nlk>#Tpdo#)!;R*9W+S4EOBOwC|Ibn?& za~F_vsLd8pqJb*5v(_HI1%gkA(9Nis&wESHvoz!!Y~%D8v{Jn~paZDmHUgwqvJo1h zZG>8nEBLLaZ8h_1jc2B(5;QG|49(~JDw+^H17AF*5|Dz3O^ZPW@j<<6*v{}+gLHgL z4dKz?&?gaI%Rxw3faqJFanR;&t4*2VYeV9JW$)%Yo>otWtv`k?WpsZ4TfJR1FunGJ zqIYXfcp|k534o$+C}+b2Dje&!&ZWbPhf8gtLR;v%|BmH1j$b?SZJ}b@SZ)ju*x#Eo zU2v5926CqR!n(5Hyl6XbyWpr~K!1?;zgBd;o)=z!wCW;mp9WKPfRW#@JKyut7wx6t zNMRU3x&bln^dD#;zirtz!Wlm&9Pn`(!!>FM1Mz%QWLfyhXqTEGg41-E(;xwB;!mgl z2_(3WpmT5tjBkd{Jp~A;ZHPFK(zCWvXHi` zrR5-*sUI!~A@ze*txZU^5BTb8(wxtE+m@MBEG~)g(c&0*;_#f9qd1b5Z^Q7aK95mx%48aZ0_Lirub?>!>p(Q$*0{X*iFFYxj{t+Ot6*sZ zi-D3SRPcnZ@BW*i8$)?dsOWjA+|qzHfK)4~pMzh;TBE zYIhoC$bJgHXBi^MjcZzm6~8mB(?IGO*>K$o!JjBV>HZJ0;l`@6Ebw&-$2ZH7S8KBP z36?~oW>)Asjh{e;r(h^87k3ixFlqv)R#)C{$qzFfX6jp)4W{q@1s@Ai{R>q{liXj z*SYNgG2Y$J0UxLD!)zKATt*qPpTX~03U00{I93e%)fNafmFN_OZ&YV9^tATP6LC=O zc(P;w9@(E?w7(U!HI$yg|5;G{BBPq&GnbT-*&jZ%lquUagf`V)_m>0hPYSqGs;lx1 zRUW9cMTDd8BExhUOBymKlGzP*irJesEWu-8Nc+ULkN!C{gY!h>1Tb)GIqoYm@Fm&& zCE4^987z{)ugFVZlHPwN_Eml@wuR5w3txgZa9MR*;r`@*61Xtz_-{V|3S0mH literal 0 HcmV?d00001 diff --git a/orders/admin.py b/orders/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/orders/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/orders/apps.py b/orders/apps.py new file mode 100644 index 0000000..384ab43 --- /dev/null +++ b/orders/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OrdersConfig(AppConfig): + name = 'orders' diff --git a/orders/migrations/0001_initial.py b/orders/migrations/0001_initial.py new file mode 100644 index 0000000..e491fa1 --- /dev/null +++ b/orders/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# Generated by Django 6.1 on 2026-08-14 13:03 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('products', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Cart', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='cart', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='CartItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('price_at_add', models.DecimalField(decimal_places=2, max_digits=10)), + ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.cart')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')), + ], + ), + migrations.CreateModel( + name='CustomerProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('phone', models.CharField(blank=True, max_length=20)), + ('default_address', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('razorpay_order_id', models.CharField(blank=True, max_length=100, null=True)), + ('razorpay_payment_id', models.CharField(blank=True, max_length=100, null=True)), + ('total_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('confirmed', 'Confirmed'), ('shipped', 'Shipped'), ('delivered', 'Delivered'), ('cancelled', 'Cancelled')], default='pending', max_length=20)), + ('shipping_address', models.TextField()), + ('placed_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='OrderItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField()), + ('unit_price', models.DecimalField(decimal_places=2, max_digits=10)), + ('tracking_number', models.CharField(blank=True, max_length=100, null=True)), + ('carrier', models.CharField(blank=True, max_length=100, null=True)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.order')), + ('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.product')), + ], + ), + ] diff --git a/orders/migrations/__init__.py b/orders/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/orders/migrations/__pycache__/0001_initial.cpython-312.pyc b/orders/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b3114b89917b501222c6a054a72358996f1e94b GIT binary patch literal 4885 zcmcgwPf**~8JFaLvT?vBZ1`tz$Qpu8fP`SjX*(T=fh1i*vcVyXHyf?69!A8L97#?V z+U}p3J@mM@(p#pd?chW9wDeSZYG!(oCY}-VW;)%Op19E_ozCp3-;?YBiIbS=G(Jf0 zJ^kMIzVG+@zNaVu_1G~V1;63eFILkn6!j0%sr_8l#WYB#y#d#B z0Oz-GU9Z|h&8xkyj;^-V89P~L?9{7c!m;{1m78n2Ei*Z1xn+~F=YnU>)q0!-j>uWi ziZN2hqt^2A16;IOxPdyj_kjUd5|HY>m9D$T@LL>opUvy zP0tav>9wMXy~-ryez<&ce|4eHLY}FIET1}{VxtxEE;IlA1~R`9b|Xh*xBm_74hVGl z^nT?DgGazdEo>u|5__H193>3>o)*r4%EJe|7_snmRNkx#57jqnVcxz6+6up$Dm~kP z!fXTebMA-=zjkjsz}HyAYMpPb0{uVk)+2THqVDpA z{cO&8_7%^$G97El=tbD;XJ|?6QvN;{d`H2xvYYC4GFlZsE7q#S>x`ZHSAT90&~Q5g#8bYvLvgBllTI{zABvKH8=*@ z79{y0#|r}Xm5Kr}Wj|vr>+lRaOJEH(U zBFYOYW{J;NHB`Xt{Sq%}qPDh6P9(6lM?lMbDX(EaIG#nYC~;z!yy&mt90YW!!MAm&8i#wCJPb-hVe1f-}V#5wG#GPfS6Q8-dZ+WY$}~_!7Zth zs>ubU+)(6YF^{mTxFSnvmmFkZRo7CUm+sk%<&l)rR&YzzSK^_9R5eN~oex;H!I2sz*lUSIk_WqOQLxt{!;;@u zf-DA>zzlBD6h3>8%#})oC1A?~>M0^LT-L;Ozde7%x&deB$g(f=n~Qd0mb22CC%$R`T+UlMbZPr%sDZRtK-KPb38iJY*j=}< z%1b#JGr|&Pt-65^4eUux&Zj1)rg7U1S%va`fUZg!$|1#`t;O zsTIBo@}|*envG!Rr_^ErTz}c{hIorx%JD-dv40xzr&vX zjn8CH8|>+CS#xm07@T;@PVTuJZSF5;_xzMMOdj_dZ0}C6{fFRtdVB1f2WETBXpe0< zpG5Qwr%OtuUDX5HPOwK0pWk?3hR++}^V@Be@Qfa~e0X##T?t>(11T`3cMflyFgu5h z&fzWhpS*wcn#n6h@`{=K&`5r0CKrw5qP`;O>M#F8Q5rqV{L{WYr>L%L%pOGr=a@aO ztF8IDi}m{US`T^J{Wed#^`7L$g4vTadXn40N>54;Ozi~2df)h?6K3DI(Ko(5sozXj z`fllg1fJ*d3A201=pOnPMa9>MhbhOb18$3sj~(E3=nm-wKXL2?-`D%&j~2}SxX~Yf z+J8Y0BzJ-#y?1P5+3X!NddIe%m0p`wV5)&kDCo*Mu%yx-5SGY|urzD21j!rp60QB9 zb=M)SLvLg0VA?+I`H3F*sio)`DY`KuJht7Z-?&)`&+CEo%L(u#r7vVEk&pGj9V<1@ zkkmY5gw8x3-%dRdDj|CkWYZAIrXizi=<)LR;&-B%m@yJFX5zY$xNatH8;RTcash}_ z=}8h*_a!T;)Jq8uRug}@_~(meYTiiAo2ffS>W-O0MhfYxzp8|Otp`5eYheiuO+dqK z`a-Mwt6wNwZLf;5xYOQ1B&rQ`Y%S_Kp zEzZzS&n(F(P0~*;EiM5H7nP?b6=#;DCM70kr{<;T=NF}<78UE~W~LV= item.quantity: + item.product.stock -= item.quantity + item.product.save() + + # Clear Cart + items.delete() + + return Response({ + "message": "Order initiated", + "order_id": order.id, + "razorpay_order_id": razorpay_order_id, + "amount": float(total_amount) + }) + +class PaymentSuccessView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + order_id = request.data.get('order_id') + payment_id = request.data.get('razorpay_payment_id') + + try: + order = Order.objects.get(id=order_id, user=request.user) + except Order.DoesNotExist: + return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND) + + order.status = 'confirmed' + order.razorpay_payment_id = payment_id + order.save() + + return Response({"message": "Payment verified and order confirmed"}) + +class OrderHistoryView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def get(self, request): + orders = Order.objects.filter(user=request.user).order_by('-placed_at') + serializer = OrderSerializer(orders, many=True) + return Response(serializer.data) + +class OrderDetailView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def get(self, request, pk): + try: + order = Order.objects.get(id=pk, user=request.user) + except Order.DoesNotExist: + return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND) + serializer = OrderSerializer(order) + return Response(serializer.data) diff --git a/products/__init__.py b/products/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/products/__pycache__/__init__.cpython-312.pyc b/products/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebaaab7c2e7da615b402c9d243c197d789e642a3 GIT binary patch literal 161 zcmX@j%ge<81n&jwvq1D?5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!a?sDn&rQ`Y%S_Kp zEzZzS&n(F(P0~*;EiM5H7nP?b6=#;DCM70kr{<;T7Zl~ElqQ!H>&M4u=4F<|$LkeT g{^GF71&ZdR+7+<^4P*r3Vi4maGb1Bo5i^hl0M{TX1poj5 literal 0 HcmV?d00001 diff --git a/products/__pycache__/admin.cpython-312.pyc b/products/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e3fb001c50624af28cd7a2bb8d666fe58a4ea4c GIT binary patch literal 205 zcmX@j%ge<81n&jwv+RNNV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zZS zi7C06d48HqxA;=B67$mY^^)`RN{TX*ikN{4Rx*4BsrzNCpOK%Ns$Z6wo|jsjp`V^v zl2MwZpIlm80u(MPPfaS$EJ;mDOwLZtOVKYV%1s96i$RPJ%#4hT_ZSq4*nk`Wf_yd7 literal 0 HcmV?d00001 diff --git a/products/__pycache__/apps.cpython-312.pyc b/products/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9b3a64c62c5a82097e84de70fa1379868ae3d37 GIT binary patch literal 410 zcmXv~J5Iwu5S_6d2yx6u0(u%GH}M4`Bm@#&M2jg_j(2e!{KIR5Tp^?!fG%f19EA%Y zOH@=;AO)IK%*M_XZ{FLPr*%y(aZF?m95_Z0ql*X{TmaW* zz;%JIdei4>m0@?jl(4bhI?b}PG`R@_zbQ9}tyt_5nS%fqIk*}}SFbc%ueUGrl$U-X z8*kmsT0OFjy31JN#e%WA$5>2#k>Y*E?n*CeD~8H1uuyZBLuZo4!nqHFM97H~gvF#B zJANq(dd?rjScZidd;YCRxU-(*cv&U~S(U5*ufQN8Hwk9&ZXP_H=lykML@S$o>Lo!s uP_|s%qA{9^tr{(2%kHpQ_$1#Usm>tx34U}${D7n14aCFet3RN$a{U9IYGeuk literal 0 HcmV?d00001 diff --git a/products/__pycache__/models.cpython-312.pyc b/products/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b75eaeaee359f50f374a36825b37fcb614a1ae77 GIT binary patch literal 2850 zcmbtWO>7%Q6rQ!c_S#;@KM5s&5R)bWr&S`L0uEKxk~GkkM$iNf7Id}oOq@;DKb>8t zbrcC7B4Hz-;eg69NJyk=gu;m)Im0DZEhQSYCr-Hm2~hO{Z)R=hM@cy_l4sw%`R2`= zH*db@UqYb(g4P=Qc=ituLcj7sqset)ClA6Mgb|iZl#!B>#ATQ1%D9tm3AxZ)2)maM z_7G`SX)NCR-g7&0QWi)Vklz15dW*h8B5rW-D{qv_dbD)9D#JN$xVBHfoeMf z<=ux0ib(h%63RW7gH5VsG%aEOl9CMJz!FNfl+i6I8J<9iAX7$ko1}BJxQzjQS7L#T zeoHe+Hf_&LCfw8yTIMZejT;5R1W$+TWDZOXSe5VQK8Xr~=!_9BL*q#L`i zJMBt(ik^i0B-Ge<=qj;w7N`#mVzGdgqqdf#m{8`)>KQVgkeQ-sT+}oc&@{NVg2`o7 z({2=Wvp&;0Pv}(6A`UWd7N~AAMHn)&>APqLTtZLI4bS8+loQAgtnd^+)%o<+Ttq% z8?A|oLOVcByP!+e^8rLBqxedZ_g_hO&?1@@69+r1)2?SW;s|&>hg%~n7LnGRvV9G) z;$B3bLq07^23mA|D}4d)stA|M1L@(JPPs|SU1zdY$n!v3`aDT^;7iPD=Fw@|V&1u% z;_KJ6l&M>mrdfPlJRUF6PHrF$-NN2YauY3}vQ9zwxP-RCt+$Jx79Y0vta!fjf8&49 zK3E?8?9xLeRPj~RWxS#Ee?9#`=@-E|5GNip9)TUc&2dqhK=e77PDtV)cP07)j4Zx1 zF5F{2aM-4rwh(D1TV2!2k^E`aovai$ly>1pB0~8j!#nN=^N-Bvbj%BA^G=k#9M3x*e2c%f$B=NruJUV}!LDksYsIPsPnAXwXx7i%w`;*s&_+%!dp9E| ztC5qdqqWG(rAu4UzU32}(Y|W5Z}q)ebhvc+Z;wkJ{2f8JtGMNXzXFon;UqBMm2cQ= zO-lS8;=K2~k0V4PA4h>elsvG|l24;QPb1Nj$J3vTy=%TE*#QBeT*VLx97J=hee5m0N4g#{Jd5VZY_8x-45K!UfzA=FsI1;tzU7J7+9*K%HZJl@u zD%wOni(^3f?Zwta8}q&E&>2%2yq0h=e=e&*6(lw!D=ZG5#OKz+~I7O+tCR_Ys45cMIWqFox=!z^`r}CoEJJHWcR> zj3+vdB!$3OjuI#%S4dI3Pv#rBFfnpr^deJI^{Pr=0;}{abW9y}g5^+zoTo;L(9?WI zhSHOoV{U7%z?Ao|z0)9(jGiW1ftt)eNp9JWHi(IpqDI~}a#_kVgmSWFp;6dy(#Q}8 z6-ZGcE>gEyppeIPSsa}5x8Xc*LE^)F$h#BpvwnsS*d-h<&u@m~)o^^Jef@(Uum5m; zxUVGU<4i6J8U&R^$Q4N`5k$~w~VgRiIlRBD55w(4QykKD4|+F8k(&&bX&)Wpht-6dx#ngZzdUgNz(73mVRwB zi8NJY<{y!ncGk+Bz%dKUf?(Wt1Am8cae~1|WVYf)tQzvR%sk~GjlIZAi=?4M3Op#5J`dHN`hw8qtY^|+Xx}5H%d)_ZKl&>3&Iwf^xkdJA$UR9HMhi#)xEbV;m zVxGlS7P*!oiBF~8bQ?_SB8aOp=L^U6B7cj?ya;*Zgq$+IW|=bUI1uMJGVeGIb-4ia zF~@lsyFt3c0Ul|nO%_OV!)E~%?@y8p)Hv&z9?7;Bc!C&8Ek zG2L_X%b+DubD(8=0+10CO6;LCLW~Ojg+QjE5H&RSK8}yo&S35YF>MoqM;67De9-#X z8UtqbeP-LVGIFX*ngQH9<6f6hi!kCBiesh-W9~7TdJlx3H;KC{3u}3XmMXPVJP^#y3|BHR@7;@Rp!JzkOj(w$Ng61hfNM+RRkKv z!&GiQ1EL6G1Q`I4UsD1w?gKpsK<^F%of%E6sS`3+sk!_iFpw`OJ0ZnrjVrJLBBy@h zh7yWQMeFU8GOft57+2Lr-2x&QzG literal 0 HcmV?d00001 diff --git a/products/__pycache__/views.cpython-312.pyc b/products/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..878359fa12a394f3760f8ef8627736e341c276d3 GIT binary patch literal 2422 zcmcgtL2MIO5dG`j*z0)hb>h^!goyM&5GJi-B%rE9L8wSjsf{S0s@luS@qU~&yLR}0 zLuzCc4jj@`$tmrrJr=hisGBh|u3{*mZpz8JIU^^K1i3;i`602=RA?NF?i-k%h<(P$ zhnl0HnfV9Ju~1V1%`9jhA21dz_iYy1xsv)4rKLn5*J+b7yT*ge5@oKl+KL|{(% zv=+G(PSI0IZvog1LWv<*#7I!FArGqc*Nx0yWtQ+X@dQ1CVI*nFkZHOx(dP$gT`)3j zu_Oig*wc?~?%^A6J{E$s&CP1fv+vURk`lcvlY6)ZI3QgYqx3Bnp^7s2oCCWg_oDw zEC)71f{yYEkcVVP)w=4muTFQ>6TW(4b7D(9v8|qaaCJv3ZAlY5lQZ4PvOihgIx^== zMRqhcap;dlu_isVIrzs6iH;=9I5hrsYKazk65V@0@gHqT1GdCYf{k~CH~HU5 zJROPC8T=R7$v5kH+4xZ%@o6$XaY&kqrT0>V#A~oA+#n@+{w7nwLLk>-Fef>!vf3g* zG#ATElRL|GkmNnvLk!rpR@1B6VTmYOl-HQO{A zzO7$b%kF4LH#+N`u2%82N>@AMYiBk;|H0eVF0U!SX7n9xdSi8cb<=&cilVE(WVA^1 zJU{l``DghG%->Hv%b)vQ6vp1`iK3ocyZTBdd3{X{Q8r&nGjx%SBPk%kQpYqT_-Ljh zIf7&s$t02xpBM>-O(Z0k6&ytXcJ1U4rnjVGPf|wo{y{u*2-yFBDUKV&WeQ2`a1WRG@tJN=8yXuUu&TL9s>ddx!77O8U(Dn-< z8kjFl1UUw|Y~F$#rYkLWn_<}cWqsoCPC2Z_42x8t4Q&A%_Ed)DDM&((p$-pwN{F5y zbI>?=*h<3326?MdZPr_5YoQz+K>!6Atff)M?iYUixq*5e5N6Z6G~NmWaX2`y<@5ap zcY&RQGgyoHCqQ0_f*|aY$_sMx1$k$e%L;wH) literal 0 HcmV?d00001 diff --git a/products/admin.py b/products/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/products/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/products/apps.py b/products/apps.py new file mode 100644 index 0000000..864c43e --- /dev/null +++ b/products/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProductsConfig(AppConfig): + name = 'products' diff --git a/products/migrations/0001_initial.py b/products/migrations/0001_initial.py new file mode 100644 index 0000000..0434a81 --- /dev/null +++ b/products/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 6.1 on 2026-08-14 13:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('slug', models.SlugField(blank=True, max_length=100, unique=True)), + ('is_active', models.BooleanField(default=True)), + ('sort_order', models.IntegerField(default=0)), + ], + options={ + 'verbose_name_plural': 'Categories', + 'ordering': ['sort_order', 'name'], + }, + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('stock', models.IntegerField(default=0)), + ('sku', models.CharField(max_length=100, unique=True)), + ('image_url', models.URLField(blank=True, max_length=500, null=True)), + ('description', models.TextField(blank=True)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='products.category')), + ], + ), + ] diff --git a/products/migrations/__init__.py b/products/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/products/migrations/__pycache__/0001_initial.cpython-312.pyc b/products/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e29964a0d152fd173425705c467b1bb1136d701a GIT binary patch literal 2568 zcmb7GPfXiZ7`L6o`9nw@poFB5(2g;jjqs<_v~+E^0)??rx&iGWnq-;0M=&^c`fStE z%Q~rt9eZmgP1=sD$blm}jU&=7wiGGRyR6bq+%mx~y=>2R(xz+HbvfAId*AQ<-uL~! z?>*<&?(Pspe&an~Z{7A&)bIA7_4752lX+r1rT_(Knaa`R&B#pAlk*h4IWJ8y)B*)O z4=Lba{g`1 z+*pUX^s95=9WYkmUuaY4s-#=07MNWB?z{YZIC!IV7T0iKR@7lZMZ1`hz&vIa=Xfs; zi-xWWc?5|M;I0yqiXz$-w&5-gY7j}HEZv7VyaUm?szE^!ix7M5=P@85@iaN@;ypoz zN%zzxxmbsW-Eqzftn5FxwA zaHtui1T}J%^tSISlw<>mG7dO!Ng+ESTR+ThNKghE_NyiL*71LVRAA(F?9(M(hR1)| zC?Cl!50C9#j_u2g&_@8wlYC{uEasu+<^rUGq-)rx>1uu(d$etXXPi_}uT2vq3kpy$ zkSv{&JA}kwpDs67uVTnD?gCKDBgu({ryVyAH5C>_ouu!IuBdkf5da)AO2Ag%^#z=Q z5FZZYTlET*+jsRT0Q73w2T`1Z9WIDHNltBz9XPWFPHiK86E=Q$s<( zAweLQvmrxbhXvt|AkHgkS!ym{gZFgT?4Bbz ztw}}b4ny-Qf>J^GoHQv5wnU|%Vg{_^fFlC=?qL7ywbj{cbJuZ5Ey3ou=m~W~!lj`{cplVDrIlUf@nQ^|{UyJiroZnxq# zs3oVYTHTHgei2vyWb8z|( zpXY4kw6{ALY7D*O0dDWNX8d*6`%MULC${a#voH{zW_ei&pC5VP7>hZ*mKcD9>jP)~cx)lbaaAP+f55Li$k?Y xtY?AC?n%9hmdMcl=c1*^qv4@x`h=zE^S@DJr|c-rJ_^;i5sMr7lOh9$;BS2>k8=P3 literal 0 HcmV?d00001 diff --git a/products/migrations/__pycache__/__init__.cpython-312.pyc b/products/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6745109df07e79aa93779304781af572ea6a12c GIT binary patch literal 172 zcmX@j%ge<81n&jwvq1D?5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!^48DD&rQ`Y%S_Kp zEzZzS&n(F(P0~*;EiM5H7nP?b6=#;DCM70kr{<;T7Zl~ElqQ!H>*r>s7bTWt=I0gb r$H!;pWtPOp>lIY~;;_jDD#%H-D`Ewj%?QNBAjU^#Mn=XWW*`dy45BT3 literal 0 HcmV?d00001 diff --git a/products/models.py b/products/models.py new file mode 100644 index 0000000..c1054ff --- /dev/null +++ b/products/models.py @@ -0,0 +1,35 @@ +from django.db import models +from django.utils.text import slugify + +class Category(models.Model): + name = models.CharField(max_length=100) + slug = models.SlugField(max_length=100, unique=True, blank=True) + is_active = models.BooleanField(default=True) + sort_order = models.IntegerField(default=0) + + class Meta: + verbose_name_plural = "Categories" + ordering = ['sort_order', 'name'] + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + def __str__(self): + return self.name + +class Product(models.Model): + category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') + title = models.CharField(max_length=255) + price = models.DecimalField(max_digits=10, decimal_places=2) + stock = models.IntegerField(default=0) + sku = models.CharField(max_length=100, unique=True) + image_url = models.URLField(max_length=500, blank=True, null=True) + description = models.TextField(blank=True) + is_active = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.title diff --git a/products/serializers.py b/products/serializers.py new file mode 100644 index 0000000..75fe9e0 --- /dev/null +++ b/products/serializers.py @@ -0,0 +1,14 @@ +from rest_framework import serializers +from .models import Category, Product + +class CategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + fields = ['id', 'name', 'slug', 'is_active', 'sort_order'] + +class ProductSerializer(serializers.ModelSerializer): + category_name = serializers.CharField(source='category.name', read_only=True) + + class Meta: + model = Product + fields = ['id', 'category', 'category_name', 'title', 'price', 'stock', 'sku', 'image_url', 'description', 'is_active'] diff --git a/products/tests.py b/products/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/products/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/products/views.py b/products/views.py new file mode 100644 index 0000000..74a0d61 --- /dev/null +++ b/products/views.py @@ -0,0 +1,34 @@ +from rest_framework import generics, permissions +from rest_framework.views import APIView +from rest_framework.response import Response +from .models import Category, Product +from .serializers import CategorySerializer, ProductSerializer +from django.db.models import Q + +class CategoryListView(generics.ListAPIView): + queryset = Category.objects.filter(is_active=True) + serializer_class = CategorySerializer + permission_classes = [permissions.AllowAny] + +class ProductListView(generics.ListAPIView): + serializer_class = ProductSerializer + permission_classes = [permissions.AllowAny] + + def get_queryset(self): + queryset = Product.objects.filter(is_active=True) + category_slug = self.request.query_params.get('category', None) + search_query = self.request.query_params.get('search', None) + + if category_slug: + queryset = queryset.filter(category__slug=category_slug) + if search_query: + queryset = queryset.filter( + Q(title__icontains=search_query) | + Q(description__icontains=search_query) + ) + return queryset + +class ProductDetailView(generics.RetrieveAPIView): + queryset = Product.objects.filter(is_active=True) + serializer_class = ProductSerializer + permission_classes = [permissions.AllowAny] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..83b6931 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django>=6.0 +djangorestframework +djangorestframework-simplejwt +django-cors-headers +gunicorn diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +test