diff --git a/backend/apps/transaction/__init__.py b/backend/apps/transaction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/transaction/admin.py b/backend/apps/transaction/admin.py new file mode 100644 index 0000000..753a012 --- /dev/null +++ b/backend/apps/transaction/admin.py @@ -0,0 +1,42 @@ +from django import forms +from django.contrib import admin + +from .models import Transaction +from .validate_field import validate_confirmations, validate_from_user + +# Register your models here. + + +# * TransactionModelForm +class TransactionModelForm(forms.ModelForm): + class Meta: + model = Transaction + fields = "__all__" + + def clean(self): + cleaned_data = super().clean() + confirmations = cleaned_data.get("confirmations") + from_user = cleaned_data.get("from_user") + if len(cleaned_data) == 0: + return + if not validate_confirmations(confirmations): + self.add_error("confirmations", "Confirmations value 1 or 0") + if not validate_from_user(from_user): + self.add_error("from_user", "Please input valid wallet address") + + +# !TransactionalAdmin +class TransactionalAdmin(admin.ModelAdmin): + form = TransactionModelForm + list_display = [ + "from_user", + "confirmations", + "value", + "adopted_pet_slug", + "created", + "modified", + ] + list_display_links = ["from_user", "confirmations"] + + +admin.site.register(Transaction, TransactionalAdmin) diff --git a/backend/apps/transaction/apps.py b/backend/apps/transaction/apps.py new file mode 100644 index 0000000..9a073d0 --- /dev/null +++ b/backend/apps/transaction/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TransactionConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.transaction" diff --git a/backend/apps/transaction/migrations/0001_initial.py b/backend/apps/transaction/migrations/0001_initial.py new file mode 100644 index 0000000..a8f9203 --- /dev/null +++ b/backend/apps/transaction/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# Generated by Django 5.0.1 on 2024-03-18 12:37 + +import django_extensions.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Transaction", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ( + "from_user", + models.CharField(max_length=100, verbose_name="from user"), + ), + ("confirmations", models.IntegerField(verbose_name="confirmations")), + ("value", models.CharField(max_length=100, verbose_name="value")), + ( + "adopted_pet_slug", + models.CharField(max_length=100, verbose_name="adopted slug"), + ), + ], + options={ + "verbose_name": "Transaction", + "verbose_name_plural": "Transactions", + }, + ), + ] diff --git a/backend/apps/transaction/migrations/__init__.py b/backend/apps/transaction/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/transaction/models.py b/backend/apps/transaction/models.py new file mode 100644 index 0000000..255021d --- /dev/null +++ b/backend/apps/transaction/models.py @@ -0,0 +1,22 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django_extensions.db.models import TimeStampedModel + +from .validate_field import validate_confirmations, validate_from_user + + +# !Transaction +class Transaction(TimeStampedModel): + from_user = models.CharField(_("from user"), max_length=100) + confirmations = models.IntegerField( + _("confirmations"), validators=[validate_confirmations] + ) + value = models.CharField(_("value"), max_length=100) + adopted_pet_slug = models.CharField(_("adopted slug"), max_length=100) + + class Meta: + verbose_name = "Transaction" + verbose_name_plural = "Transactions" + + def __str__(self): + return f"{self.from_user} -- {self.value} -- {self.value}" diff --git a/backend/apps/transaction/serializers.py b/backend/apps/transaction/serializers.py new file mode 100644 index 0000000..691f29b --- /dev/null +++ b/backend/apps/transaction/serializers.py @@ -0,0 +1,26 @@ +import re + +from rest_framework import serializers + +from .models import Transaction +from .validate_field import validate_confirmations, validate_from_user + + +# !TransactionSerializer +class TransactionSerializer(serializers.ModelSerializer): + from_user = serializers.CharField(validators=[validate_from_user]) + confirmations = serializers.CharField(validators=[validate_confirmations]) + + def validate(self, attrs): + from_user = attrs.get("from_user") + confirmations = attrs.get("confirmations") + + if not validate_from_user(from_user): + raise serializers.ValidationError("Please input valid wallet address") + if not validate_confirmations(confirmations): + raise serializers.ValidationError("Confirmations value 1 or 0") + return attrs + + class Meta: + model = Transaction + fields = "__all__" diff --git a/backend/apps/transaction/tests.py b/backend/apps/transaction/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/apps/transaction/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/apps/transaction/urls.py b/backend/apps/transaction/urls.py new file mode 100644 index 0000000..93e8c11 --- /dev/null +++ b/backend/apps/transaction/urls.py @@ -0,0 +1,6 @@ +from django.urls import path + +from .views import TransactionView + +app_name = "transaction" +urlpatterns = [path("", TransactionView.as_view(), name="transactions")] diff --git a/backend/apps/transaction/validate_field.py b/backend/apps/transaction/validate_field.py new file mode 100644 index 0000000..b9dc54d --- /dev/null +++ b/backend/apps/transaction/validate_field.py @@ -0,0 +1,17 @@ +import re + +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + + +# ?validate_confirmations +def validate_confirmations(value): + if int(value) > 1: + return False + return value + + +# ?validate_from_user +def validate_from_user(value): + pattern = re.compile("^0x[a-fA-F0-9]{40}$") + return bool(pattern.match(f"{value}")) diff --git a/backend/apps/transaction/views.py b/backend/apps/transaction/views.py new file mode 100644 index 0000000..0c2f079 --- /dev/null +++ b/backend/apps/transaction/views.py @@ -0,0 +1,22 @@ +from django.shortcuts import render +from rest_framework.response import Response +from rest_framework.views import APIView + +from .serializers import TransactionSerializer + +# Create your views here. + + +# !TransactionView +class TransactionView(APIView): + serializer_class = TransactionSerializer + + def get(self, request): + return Response({"message": "Transaction successfull"}) + + def post(self, request, format=None): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) diff --git a/backend/config/base.py b/backend/config/base.py index 6b52b3c..4276288 100644 --- a/backend/config/base.py +++ b/backend/config/base.py @@ -69,6 +69,7 @@ "apps.pet", "apps.upload", "apps.order", + "apps.transaction", ] # !Installed Apps diff --git a/backend/config/urls.py b/backend/config/urls.py index a5d5518..32a0ec5 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -55,6 +55,9 @@ urlpatterns += [path("upload/", include("apps.upload.urls", namespace="upload"))] urlpatterns += [path("pets/", include("apps.pet.urls", namespace="pet"))] urlpatterns += [path("orders/", include("apps.order.urls", namespace="order"))] + urlpatterns += [ + path("transactions/", include("apps.transaction.urls", namespace="transaction")) + ] # *Settings Debug diff --git a/backend/schema.yml b/backend/schema.yml index 86a8320..32ff3b1 100644 --- a/backend/schema.yml +++ b/backend/schema.yml @@ -4,21 +4,9 @@ info: version: 1.0.0 description: This project purpose creating ecommerce api for business company paths: - /orders/: - get: - operationId: orders_retrieve - description: Order - tags: - - orders - security: - - cookieAuth: [] - - basicAuth: [] - - {} - responses: - '200': - description: No response body + /orders/create-checkout-session: post: - operationId: orders_create + operationId: orders_create_checkout_session_create description: Order tags: - orders @@ -207,6 +195,19 @@ paths: responses: '204': description: No response body + /transactions/create-checkout-session: + post: + operationId: transactions_create_checkout_session_create + description: Order + tags: + - transactions + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + description: No response body /upload/image/: post: operationId: upload_image_create diff --git a/frontend/contract-ui/contracts/PetLocal.json b/frontend/contract-ui/contracts/PetLocal.json index ecb6341..d974812 100644 --- a/frontend/contract-ui/contracts/PetLocal.json +++ b/frontend/contract-ui/contracts/PetLocal.json @@ -1 +1 @@ -{"abi":"[{\"type\":\"constructor\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"initialPetIndex\"}]},{\"type\":\"function\",\"name\":\"addPet\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]},{\"type\":\"function\",\"name\":\"addToCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"_petId\"},{\"type\":\"string\",\"name\":\"_petName\"},{\"type\":\"string\",\"name\":\"_petColor\"},{\"type\":\"uint256\",\"name\":\"_petPrice\"},{\"type\":\"string\",\"name\":\"_petPhoto\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"adoptPet\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"adoptIdx\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"allAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPetsByOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getCartItems\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"tuple[]\",\"components\":[{\"type\":\"uint256\",\"name\":\"id\"},{\"type\":\"string\",\"name\":\"name\"},{\"type\":\"string\",\"name\":\"color\"},{\"type\":\"uint256\",\"name\":\"price\"},{\"type\":\"string\",\"name\":\"photo\"}]}]},{\"type\":\"function\",\"name\":\"getCartLength\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"owner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"ownerAddressToPetList\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"address\"},{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"petIdxToOwnerAddress\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"petIndex\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"removeCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"index\"}],\"outputs\":[]}]","address":"0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512","network":"localhost","deployer":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} +{"abi":"[{\"type\":\"constructor\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"initialPetIndex\"}]},{\"type\":\"function\",\"name\":\"addPet\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]},{\"type\":\"function\",\"name\":\"addToCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"_petId\"},{\"type\":\"string\",\"name\":\"_petName\"},{\"type\":\"string\",\"name\":\"_petColor\"},{\"type\":\"uint256\",\"name\":\"_petPrice\"},{\"type\":\"string\",\"name\":\"_petPhoto\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"adoptPet\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"adoptIdx\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"allAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPetsByOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getCartItems\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"tuple[]\",\"components\":[{\"type\":\"uint256\",\"name\":\"id\"},{\"type\":\"string\",\"name\":\"name\"},{\"type\":\"string\",\"name\":\"color\"},{\"type\":\"uint256\",\"name\":\"price\"},{\"type\":\"string\",\"name\":\"photo\"}]}]},{\"type\":\"function\",\"name\":\"getCartLength\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"owner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"ownerAddressToPetList\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"address\"},{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"petIdxToOwnerAddress\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"petIndex\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"removeCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"index\"}],\"outputs\":[]}]","address":"0x5FbDB2315678afecb367f032d93F642f64180aa3","network":"localhost","deployer":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} diff --git a/frontend/contract-ui/helpers/init_moralis.js b/frontend/contract-ui/helpers/init_moralis.js index f9b65f1..798ec4a 100644 --- a/frontend/contract-ui/helpers/init_moralis.js +++ b/frontend/contract-ui/helpers/init_moralis.js @@ -5,6 +5,7 @@ import { useMoralis } from "react-moralis"; const { web3,account,Moralis } = useMoralis(); // export some needed package to abroad +//* useMoralisInit export default function useMoralisInit (){ if(web3 != null && account != null && Moralis != null){ return { web3,account,Moralis }; diff --git a/frontend/contract-ui/helpers/init_stripe.js b/frontend/contract-ui/helpers/init_stripe.js index d3725d2..6689d90 100644 --- a/frontend/contract-ui/helpers/init_stripe.js +++ b/frontend/contract-ui/helpers/init_stripe.js @@ -1,4 +1,4 @@ -// ?getStripePublishableKey +// *getStripePublishableKey const getStripePublishableKey = () => { // Initialize Stripe.js return Stripe(import.meta.env.VITE_VUE_PUBLISHABLE_KEY) diff --git a/frontend/contract-ui/src/components/Petitem.jsx b/frontend/contract-ui/src/components/Petitem.jsx index a72766f..52af427 100644 --- a/frontend/contract-ui/src/components/Petitem.jsx +++ b/frontend/contract-ui/src/components/Petitem.jsx @@ -62,8 +62,12 @@ export function PetItem({checkIsAuthenticated,isAuthenticated,contract,account,i const addToCart = async (e) => { const is_auth = isAuthenticated const is_exits_in_cart = await checkCartItems(e.target.getAttribute('pet-id')) + const is_adopted = await checkIsAdopted(e.target.getAttribute('pet-index')) - if (is_auth && contract != null && !is_exits_in_cart){ + if(is_auth && contract != null && is_adopted){ + toast.info('This pet already buyed') + } + else if(is_auth && contract != null && !is_exits_in_cart){ const pet_slug = e.target.getAttribute('pet-slug') const pet = await getPet(pet_slug) // let overrides = { @@ -77,10 +81,10 @@ export function PetItem({checkIsAuthenticated,isAuthenticated,contract,account,i // } const signer = getSigner(account) - const result = await contract.connect(signer).addToCart(pet.id,pet.name,"red",parseInt(pet.price),pet.pet_photo_link) + const result = await contract.connect(signer).addToCart(pet.id,pet.name,pet.color,parseInt(pet.price),pet.pet_photo_link) toast.success('Added to cart successfully') } - else{ + else if(is_auth && contract != null && is_exits_in_cart){ toast.error('You have already added to cart') } } @@ -99,6 +103,20 @@ export function PetItem({checkIsAuthenticated,isAuthenticated,contract,account,i return is_exists } + // ?checkIsAdopted + const checkIsAdopted = async (pet_index) => { + const signer = getSigner(account) + const adopted_pets = await contract.connect(signer).getAllAdoptedPetsByOwner() + let is_adopted = false + adopted_pets.map((pet) => { + if(pet.toString() == pet_index){ + is_adopted = true + return + } + }) + return is_adopted + } + //return jsx to client return ( @@ -140,7 +158,7 @@ export function PetItem({checkIsAuthenticated,isAuthenticated,contract,account,i { !isAdmin && isAuthenticated ? ( -