Skip to content
Merged

Dev #13

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6cb6f1c
Merge pull request #7 from IASSCMS/main
moonlander101 May 8, 2025
73a1117
Added docker-compose for db, and dummy.py to generate dummy data
moonlander101 May 8, 2025
d28b26e
Updated requirements.txt and gitignore
moonlander101 May 8, 2025
2221ae4
Added new app for supplier requests
moonlander101 May 8, 2025
b392baf
Added swagger ui, and updated settings.py to use env variables
moonlander101 May 8, 2025
0a5f98b
Updated order.models to have OrderDetails
moonlander101 May 8, 2025
ed7b116
Added route to export swagger.json
moonlander101 May 8, 2025
32262bc
Added new model OrderProduct to add multiple products to order
moonlander101 May 8, 2025
ee7d2cb
Updated migrations
moonlander101 May 8, 2025
5572d6b
Updated migrations and Models
moonlander101 May 8, 2025
406e989
Added dummy script to generate dummy data
moonlander101 May 8, 2025
e74c91b
Update README
moonlander101 May 8, 2025
34904b9
Update README.md
moonlander101 May 8, 2025
b92b40e
Updated View to filter orders by warehouse_id
moonlander101 May 8, 2025
bbd72be
Updated swagger
moonlander101 May 8, 2025
b545a6c
Updated Supplier_Request model to store data related to quality, upda…
moonlander101 May 8, 2025
762c3e9
Added new view to get supplier requests by supplier_id
moonlander101 May 8, 2025
3281cba
Added View to update Status
moonlander101 May 8, 2025
139d6c6
Added New route to get metrics from supplier requests, and updated du…
moonlander101 May 8, 2025
b0a65e4
Added more metric data
moonlander101 May 8, 2025
b33296d
added new view to filter orders by user_id
moonlander101 May 9, 2025
97b4fb0
Added kafka producer to send events, updated patch request
moonlander101 May 9, 2025
05800b9
Small bug fixes for kafka
moonlander101 May 9, 2025
572f560
Added new view to fetch supplier-requests
moonlander101 May 10, 2025
10990ae
Updated endpoint mapping, and added status filter for requesting supp…
moonlander101 May 10, 2025
12c739d
Refined the status filter
moonlander101 May 10, 2025
fd41714
Blockchain changes to make it functional
moonlander101 May 10, 2025
7fd11c6
Updated views to incorporate blockchain on order creation
moonlander101 May 10, 2025
f149fd7
Removed unit_price from post request to create Supplier Request, inst…
moonlander101 May 10, 2025
112fafd
Added new filter method to filter supplier_requests by status when hi…
moonlander101 May 10, 2025
3afd304
Updated ipfs compose file
moonlander101 May 10, 2025
b70ac8d
Merge branch 'main' into dev
moonlander101 May 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,43 @@ Open your browser at: [http://localhost:4200](http://localhost:4200)

---

### 7. Start the Database with Docker Compose
To start the database, use the provided `docker-compose.yaml` file:
```bash
cd blocktrack_backend
sudo docker-compose up -d
```
This will start the PostgreSQL database required for the backend.

### 8. Environment Variables
Ensure the following environment variables are set for the backend:

```env
DATABASE_NAME=blocktrack_db
DATABASE_USER=blocktrack_user
DATABASE_PASSWORD=securepassword
DATABASE_HOST=localhost
DATABASE_PORT=5432
SECRET_KEY=your_secret_key_here
DEBUG=True
ALLOWED_HOSTS=*
```

### 9. Dummy Data
Run the dummy.py as follows:
```bash
# On Linux/MacOS
python manage.py shell < dummy.py

# On Windows
python manage.py shell < dummy.py
```

---

## 🔌 API Endpoints

| Method | URL | Description |
|--------|-------------------------------|--------------------------------|
| POST | `/api/create-order/` | Create order + file + IPFS |
| GET | `/api/read-order/<order_id>/` | Retrieve order from blockchain |
Run the django app and visit `/swagger` to view the swagger-UI docs

---

Expand Down
1 change: 1 addition & 0 deletions blocktrack_backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ env/
.vscode/
.DS_Store
*.pem
.env
24 changes: 15 additions & 9 deletions blocktrack_backend/blocktrack_backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from dotenv import load_dotenv
load_dotenv()


from pathlib import Path

Expand All @@ -20,12 +24,13 @@
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-b^0a637n!kbfd340ul=p@p^3w-17v+ni5bw^(7#&5_br)^z0pj'

SECRET_KEY = os.getenv('SECRET_KEY', 'your-default-secret-key')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv('DEBUG')

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]


# Application definition
Expand All @@ -41,6 +46,8 @@
'orders',
'corsheaders',
'django_filters',
'supplier_request',
'drf_yasg'
]

MIDDLEWARE = [
Expand Down Expand Up @@ -81,14 +88,13 @@
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'blocktrack_db',
'USER': 'blockuser',
'PASSWORD': 'blockpass',
'HOST': 'localhost',
'PORT': '5432',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT'),
}
}

REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
Expand Down
24 changes: 23 additions & 1 deletion blocktrack_backend/blocktrack_backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,29 @@
from django.contrib import admin
from django.urls import path, include

from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
openapi.Info(
title="Your API",
default_version='v1',
),
public=True,
permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('orders.urls')),
path('api/v0/', include('orders.urls')),
path('api/v0/', include('supplier_request.urls')),

path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path(
'swagger.json',
schema_view.without_ui(cache_timeout=0),
name='schema-swagger-ui-json'
),
]
24 changes: 24 additions & 0 deletions blocktrack_backend/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
blocktrack_postgres:
image: postgres:15
container_name: blocktrack_postgres
restart: always
environment:
POSTGRES_USER: blockuser
POSTGRES_PASSWORD: blockpass
POSTGRES_DB: blocktrack_db
ports:
- "15433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data

adminer:
image: adminer
container_name: blocktrack_adminer
restart: always
ports:
- "8080:8080"
depends_on:
- blocktrack_postgres
volumes:
postgres_data:
49 changes: 49 additions & 0 deletions blocktrack_backend/dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from supplier_request.models import SupplierRequest
from orders.models import Order, OrderDetails, OrderProduct
from django.utils import timezone
import random

statuses = ['pending', 'accepted', 'received', 'returned', 'rejected']

for _ in range(10):
SupplierRequest.objects.create(
supplier_id=random.randint(1, 5),
created_at=timezone.now(),
expected_delivery_date=timezone.now() + timezone.timedelta(days=random.randint(1, 10)),
product_id=random.randint(100, 105),
count=round(random.uniform(1, 100), 2),
status=random.choice(statuses),
received_at=timezone.now() if random.choice([True, False]) else None,
warehouse_id=random.randint(1, 3),
unit_price=round(random.uniform(10, 500), 2),
quality=random.randint(0, 10) if random.choice([True, False]) else None,
is_defective=random.choice([True, False, None])
)

# Orders and OrderDetails dummy data
order_statuses = ['Pending', 'Accepted', 'Shipped', 'Delivered', 'Cancelled']
for i in range(5):
order = Order.objects.create(
user_id=i,
status=random.choice(order_statuses),
blockchain_tx_id=f"tx_{i:04}",
ipfs_hash=f"QmHash{i:04}"
)

OrderDetails.objects.create(
order=order,
warehouse_id=random.randint(1000, 9999),
nearest_city=f"City-{i}",
latitude=f"{6.9 + i:.4f}",
longitude=f"{79.8 + i:.4f}"
)

for j in range(3):
OrderProduct.objects.create(
order=order,
product_id=100 + j,
count=random.randint(1, 10),
unit_price=round(random.uniform(10.0, 100.0), 2)
)

print("Dummy data generation complete.")
34 changes: 34 additions & 0 deletions blocktrack_backend/orders/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from kafka import KafkaProducer
from kafka.errors import KafkaError
import logging
import json

logger = logging.getLogger(__name__)

try:
kafka_producer = KafkaProducer(
bootstrap_servers='localhost:9092',
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
retries=5,
acks='all' # Wait for all replicas to acknowledge
)
logger.info("Kafka producer initialized successfully")
except KafkaError as e:
kafka_producer = None
logger.error(f"Failed to initialize Kafka producer: {str(e)}")

def send_to_kafka(topic, data):
"""Helper function to send messages to Kafka with error handling"""
if not kafka_producer:
logger.warning("Kafka producer not available, skipping message")
return False

try:
future = kafka_producer.send(topic, data)
# Wait for the message to be delivered
record_metadata = future.get(timeout=10)
logger.info(f"Message sent to {record_metadata.topic}:{record_metadata.partition}:{record_metadata.offset}")
return True
except KafkaError as e:
logger.error(f"Failed to send message to Kafka: {str(e)}")
return False
28 changes: 28 additions & 0 deletions blocktrack_backend/orders/migrations/0002_orderdetails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.2 on 2025-05-08 06:01

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('orders', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='OrderDetails',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('product_id', models.IntegerField()),
('count', models.IntegerField()),
('warehouse_id', models.IntegerField()),
('address', models.CharField(max_length=255)),
('zipcode', models.CharField(max_length=20)),
('city', models.CharField(max_length=100)),
('country', models.CharField(max_length=100)),
('order_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='orders.order')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 5.2 on 2025-05-08 08:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('orders', '0002_orderdetails'),
]

operations = [
migrations.RemoveField(
model_name='order',
name='id',
),
migrations.AlterField(
model_name='order',
name='order_id',
field=models.AutoField(primary_key=True, serialize=False),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Generated by Django 5.2 on 2025-05-08 12:09

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('orders', '0003_remove_order_id_alter_order_order_id'),
]

operations = [
migrations.RenameField(
model_name='orderdetails',
old_name='address',
new_name='nearest_city',
),
migrations.RemoveField(
model_name='orderdetails',
name='city',
),
migrations.RemoveField(
model_name='orderdetails',
name='count',
),
migrations.RemoveField(
model_name='orderdetails',
name='country',
),
migrations.RemoveField(
model_name='orderdetails',
name='product_id',
),
migrations.RemoveField(
model_name='orderdetails',
name='zipcode',
),
migrations.AddField(
model_name='orderdetails',
name='latitude',
field=models.CharField(default=None, max_length=32),
preserve_default=False,
),
migrations.AddField(
model_name='orderdetails',
name='longitude',
field=models.CharField(default=None, max_length=32),
preserve_default=False,
),
migrations.AlterField(
model_name='order',
name='status',
field=models.CharField(choices=[('Pending', 'Pending'), ('Accepted', 'Accepted'), ('Shipped', 'Shipped'), ('Delivered', 'Delivered'), ('Cancelled', 'Cancelled')], max_length=50),
),
migrations.AlterField(
model_name='orderdetails',
name='order_id',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_details', to='orders.order'),
),
migrations.CreateModel(
name='OrderProduct',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('product_id', models.IntegerField()),
('count', models.IntegerField()),
('unit_price', models.FloatField()),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_products', to='orders.order')),
],
),
]
Loading
Loading