Skip to main content

Tech Stack & Concepts

Tech Stack

  • Frontend + Backend: Next.js v15.5.0 (App Router)

  • Database: PostgreSQL v16.9 + Prisma v6.16.2

  • Styling: Tailwind CSS v4

  • API: Next.js API routes

  • Mobile: React Native 0.81.4 + Expo 54.0.7

  • Deployment: Vercel / Asura Hosting (soon)


Database Schemas (Prisma)

The single source of truth for the database structure is the prisma/schema.prisma file. It defines all models, relations, and indexes.

Diagram 1: E-commerce & Order Processing

Diagram 2: Product Catalog & Inventory

Diagram 3: User Interaction & Reviews

Diagram 4: Site Content & System

More details

Click to expand/collapse the detailed database structure

generator client {
provider = "prisma-client-js"
output = "./generated/prisma"
previewFeatures = ["fullTextSearchPostgres"]
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
slug String @default(cuid()) @id @db.VarChar(100)
created_at DateTime @default(now())
email String @unique @db.VarChar(255)
password_hash String? @db.VarChar(255)
display_name String @db.VarChar(100)
is_google_auth Boolean @default(false)
is_admin Boolean @default(false)
password_reset_token String? @unique @db.VarChar(100)
password_reset_expires DateTime?
email_verified Boolean? @default(false)
email_verification_token String? @unique
email_verification_expires DateTime?
orders Order[]
wishlist Product[]
wishlistSets Set[] @relation("UserWishlistSets")
reviews Review[]

@@index([email])
@@index([is_admin])
@@index([email_verified])
@@index([email_verified, created_at])
@@index([created_at])
}

model RequestLog {
slug Int @id @default(autoincrement())
identifier String
type String // "review", "checkout", "auth"
created_at DateTime @default(now())
description String @default("")

@@index([identifier, type])
@@index([identifier, type, created_at])
@@index([created_at])
}

model TimedoutIp {
slug String @id // The IP address
created_at DateTime @default(now())
timedout_until DateTime // The timestamp when the timeout expires

@@index([timedout_until])
@@index([created_at])
}

model Config {
slug String @id @default("general") @db.VarChar(50)
tos String? @default("") @db.Text
about_us String? @default("") @db.Text
mission String? @default("") @db.Text
partners_description String? @default("") @db.Text
connect_description String? @default("") @db.Text
delivery_policies String[] @default([])
checkout_enable_cod Boolean @default(true)
// checkout_enable_credit_card Boolean @default(false)
// checkout_enable_paypal Boolean @default(false)
}

model Category {
slug String @id @db.VarChar(100)
name String @db.VarChar(100)
img String? @default("") @db.VarChar(255)
is_featured Boolean @default(false)
created_at DateTime @default(now())

product Product[]

@@index([is_featured])
@@index([created_at])
}

model Brand {
slug String @id @db.VarChar(100)
name String @unique @db.VarChar(100)
description String? @db.Text
created_at DateTime @default(now())

products Product[]

@@index([name])
@@index([created_at])
}

// The parent product, holds shared data
model Product {
created_at DateTime @default(now())
slug String @id @db.VarChar(100)
name String @db.VarChar(100)
category String @db.VarChar(100)
items_sold Int @default(0) @db.SmallInt // Total sold across all variants
featured_promotion Boolean @default(false)
top_selling Boolean @default(false)
is_new Boolean @default(false)
images String[] @default([])
average_rating Float @default(0) @db.DoublePrecision
review_count Int @default(0)
description String @default("") @db.Text
secret_description String? @default("") @db.Text
brand_slug String? @db.VarChar(100)

// DENORMALIZED
min_price Float @default(0) @db.DoublePrecision
max_price Float @default(0) @db.DoublePrecision
max_discount Int @default(0) @db.SmallInt

search_vector Unsupported("tsvector")? // search by name/slug for now

categoryRef Category @relation(fields: [category], references: [slug], onDelete: Cascade)
wishlistedBy User[]
relatedProducts Product[] @relation("ProductRelations")
relatedTo Product[] @relation("ProductRelations")
reviews Review[]
variants ProductVariant[] // A product has many variants
brand Brand? @relation(fields: [brand_slug], references: [slug], onDelete: Restrict)

@@index([category])
@@index([brand_slug])
@@index([featured_promotion])
@@index([top_selling])
@@index([is_new])
@@index([name])
@@index([created_at])
@@index([items_sold])
@@index([average_rating])
@@index([min_price])
@@index([max_price])
@@index([max_discount])

@@index([category, name])
@@index([category, brand_slug])
@@index([category, top_selling])
@@index([category, created_at])
@@index([category, items_sold])
@@index([category, average_rating])
@@index([category, min_price])
@@index([category, max_price])
@@index([category, max_discount])
}

model ProductVariant {
slug String @id @db.VarChar(150) // e.g., "headphones-white-large"
product_slug String @db.VarChar(100)
name String @db.VarChar(100) // e.g., "White, Large"
price Float @db.DoublePrecision
discount Int @default(0) @db.SmallInt
quantity Int @default(0) @db.SmallInt
created_at DateTime @default(now())
description String? @db.Text
preferred_img_index Int? @default(0) @db.SmallInt

product Product @relation(fields: [product_slug], references: [slug], onDelete: Cascade)
orderItems OrderItem[]
setComponents SetComponent[]

@@index([product_slug])
@@index([price])
@@index([quantity])
@@index([name])
@@index([discount])
@@index([product_slug, price])
@@index([product_slug, discount])
}

model Review {
slug String @id @default(cuid())
created_at DateTime @default(now())
updated_at DateTime @updatedAt
rate Decimal @default(0) @db.Decimal(2, 1)
comment String? @db.VarChar(255)
product_slug String? @db.VarChar(100)
set_slug String? @db.VarChar(100)
user_slug String @db.VarChar(100)

// Reply functionality
parent_review_slug String? @db.VarChar(150)
is_reply Boolean @default(false)

// Relations
product Product? @relation(fields: [product_slug], references: [slug], onDelete: Cascade)
set Set? @relation(fields: [set_slug], references: [slug], onDelete: Cascade)
user User @relation(fields: [user_slug], references: [slug], onDelete: Cascade)

// Self-referential relation for replies
parent_review Review? @relation("ReviewReplies", fields: [parent_review_slug], references: [slug], onDelete: Cascade)
replies Review[] @relation("ReviewReplies")

@@index([user_slug])
@@index([created_at])
@@index([product_slug, updated_at, created_at])
@@index([set_slug, updated_at, created_at])
@@index([parent_review_slug])
@@index([product_slug, is_reply])
@@index([set_slug, is_reply])
}

model Set {
slug String @id @db.VarChar(100)
name String @db.VarChar(100)
images String[] @default([])
made_by String @db.VarChar(100)
description String @default("")
tags String[] @default([])
created_at DateTime @default(now())
price Float @default(0) @db.DoublePrecision
discount Float @default(0) @db.DoublePrecision
items_sold Int @default(0) @db.SmallInt
featured_promotion Boolean @default(false)
top_selling Boolean @default(false)
is_new Boolean @default(false)
average_rating Float @default(0) @db.DoublePrecision
review_count Int @default(0)

components SetComponent[]
orderItems OrderItem[]
relatedProducts Set[] @relation("SetRelations")
relatedTo Set[] @relation("SetRelations")
wishlistedBy User[] @relation("UserWishlistSets")
reviews Review[]

@@index([made_by])
@@index([name])
@@index([created_at])
@@index([price])
@@index([discount])
@@index([items_sold])
@@index([featured_promotion])
@@index([top_selling])
@@index([is_new])
}

model SetComponent {
set_slug String @db.VarChar(100)
product_variant_slug String @db.VarChar(150)
quantity Int @db.SmallInt

set Set @relation(fields: [set_slug], references: [slug], onDelete: Cascade)
variant ProductVariant @relation(fields: [product_variant_slug], references: [slug], onDelete: Cascade)

@@id([set_slug, product_variant_slug])
@@index([set_slug, quantity])
}

model Team {
slug String @id @db.VarChar(100)
name String @db.VarChar(100)
role String? @db.VarChar(100)
img String? @db.VarChar(255)
created_at DateTime @default(now())

@@index([created_at])
}

model Partner {
slug String @id @db.VarChar(100)
img String @db.VarChar(255)
created_at DateTime @default(now())

@@index([created_at])
}

model Gallery {
created_at DateTime @default(now())
updated_at DateTime @updatedAt
slug String @id @db.VarChar(100)
name String @db.VarChar(100)
img String @db.VarChar(255)

@@index([created_at])
@@index([updated_at])
}

model Theme {
slug String @id @default("general") @db.VarChar(50)
theme_string_obj Json @default("{\"primary\":\"blue\",\"secondary\":\"violet\"}")
header_text_color String? @default("text-black") @db.VarChar(100)
img String? @default("") @db.VarChar(255)
}

model Order {
slug String @id @db.VarChar(100)
created_at DateTime @default(now())
name String @db.VarChar(100)
email String @db.VarChar(255)
address String @db.VarChar(255)
city String @db.VarChar(100)
region String? @db.VarChar(100)
postal_code String? @db.VarChar(20)
notes String? @db.VarChar(500)
payment_method String @db.VarChar(50)
shipping_fee Decimal @db.Decimal(10, 2)
sub_total Decimal @db.Decimal(10, 2)
phone String @db.VarChar(20)
status String @db.VarChar(20)
admin_note String? @db.Text
items_qty Int @db.SmallInt

discount_code String? @db.VarChar(50)
discount_amount Decimal @default(0) @db.Decimal(10, 2)
idempotency_key String? @unique @db.VarChar(100)

user User @relation(fields: [email], references: [email], onDelete: Cascade)
orderItems OrderItem[]
discountCodeRef DiscountCode? @relation("OrderDiscountCode", fields: [discount_code], references: [slug])

@@index([created_at])
@@index([email])
@@index([created_at, email])
@@index([status])
@@index([discount_code])
@@index([email, status])
}

model OrderItem {
slug String @id @default(cuid())
order_slug String @db.VarChar(100)
product_variant_slug String? @db.VarChar(150)
set_slug String? @db.VarChar(100)
item_type String @db.VarChar(20)
quantity Int @db.SmallInt
unit_price Decimal @db.Decimal(10, 2)

order Order @relation(fields: [order_slug], references: [slug], onDelete: Cascade)
variant ProductVariant? @relation(fields: [product_variant_slug], references: [slug], onDelete: Cascade)
set Set? @relation(fields: [set_slug], references: [slug], onDelete: Cascade)

@@unique([order_slug, product_variant_slug, set_slug])
@@index([order_slug])
@@index([product_variant_slug])
@@index([set_slug])
@@index([item_type])
}

model DiscountCode {
slug String @id @db.VarChar(50)
created_at DateTime @default(now())
expires_at DateTime?
discount_type String @db.VarChar(20)
discount_value Decimal @db.Decimal(10, 2)
max_uses Int? @db.SmallInt
used_count Int @default(0) @db.SmallInt
minimum_order_amount Decimal? @db.Decimal(10, 2)
is_active Boolean @default(true)
orders Order[] @relation("OrderDiscountCode")

@@index([slug])
@@index([created_at])
}

model CustomTransaction {
slug String @id @default("general")
amount Decimal @db.Decimal(10, 2)
}


Folder Structure

This project uses the Next.js App Router, which organizes the application files within the src/app directory, this section shows some of the project's files.

Click to expand/collapse details of the folder structure
  • Root Directory (/)
.
├── mobile
│   ├── app
│   │   ├── (drawer)
│   │   │   ├── _layout.tsx
│   │   │   └── (tabs)
│   │   │   ├── account.tsx
│   │   │   ├── index.tsx
│   │   │   ├── _layout.tsx
│   │   │   ├── sets.tsx
│   │   │   ├── store.tsx
│   │   │   └── tos.tsx
│   │   ├── _layout.tsx
│   │   └── +not-found.tsx
│   ├── app-env.d.ts
│   ├── app.json
│   ├── assets
│   │   ├── fonts
│   │   │   └── SpaceMono-Regular.ttf
│   │   └── images
│   │   ├── adaptive-icon.png
│   │   ├── anonymous.png
│   │   ├── favicon.png
│   │   ├── icon.png
│   │   ├── image-placeholder.png
│   │   └── splash-icon.png
│   ├── babel.config.js
│   ├── cesconfig.jsonc
│   ├── components
│   │   ├── ContactBox.tsx
│   │   ├── Footer.tsx
│   │   ├── ItemsSlider.tsx
│   │   ├── Logo.tsx
│   │   ├── MediaDisplay.tsx
│   │   ├── NewTag.tsx
│   │   ├── StarRating.tsx
│   │   ├── Typography.tsx
│   │   └── Underline.tsx
│   ├── constants
│   │   ├── config.ts
│   │   ├── language.ts
│   │   └── types
│   │   ├── data.d.ts
│   │   └── general.ts
│   ├── eslint.config.js
│   ├── global.css
│   ├── hooks
│   │   └── useApi.ts
│   ├── metro.config.js
│   ├── nativewind-env.d.ts
│   ├── package.json
│   ├── prettier.config.js
│   ├── styles
│   │   └── colors.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── utils
│   ├── api.ts
│   └── functions.ts

├── README.md

└── web
├── jsconfig.json
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── prisma
│   └── schema.prisma
├── public
│   └── icon.png
├── setup-files
│   └── manage-users.js
└── src
├── actions
│   ├── authActions.js
│   └── reviewActions.js
├── app
│   ├── about
│   │   └── page.js
│   ├── account
│   │   ├── layout.js
│   │   ├── orders
│   │   │   ├── page.js
│   │   │   └── [slug]
│   │   │   └── page.js
│   │   ├── page.js
│   │   └── wishlist
│   │   └── page.js
│   ├── admin
│   │   ├── components
│   │   │   ├── AdminHeader.js
│   │   │   ├── AutoSlugifyButton.js
│   │   │   ├── Backup.js
│   │   │   ├── CustomTransaction.js
│   │   │   ├── Dashboard.js
│   │   │   ├── DiscountCalculator.js
│   │   │   ├── forms
│   │   │   │   ├── BrandForm.js
│   │   │   │   ├── CashierForm.js
│   │   │   │   ├── CategoryForm.js
│   │   │   │   ├── CodesForm.js
│   │   │   │   ├── GalleryForm.js
│   │   │   │   ├── GeneralForm.js
│   │   │   │   ├── PartnersForm.js
│   │   │   │   ├── ProductForm.js
│   │   │   │   ├── SetForm.js
│   │   │   │   ├── TeamForm.js
│   │   │   │   └── ThemesForm.js
│   │   │   ├── HtmlEditor.js
│   │   │   ├── ImageList.js
│   │   │   ├── NavigationTabs.js
│   │   │   ├── OrdersQuickStats.js
│   │   │   ├── OrderView.js
│   │   │   ├── ProductOptions.js
│   │   │   ├── RelatedItemsSection.js
│   │   │   ├── ReviewView.js
│   │   │   ├── SearchProduct.js
│   │   │   ├── SpamManagement.js
│   │   │   └── TableDisplay.js
│   │   ├── hooks
│   │   │   ├── useDebouncedSearch.js
│   │   │   ├── useEntityData.js
│   │   │   └── useFormSubmit.js
│   │   ├── layout.js
│   │   ├── page.js
│   │   └── utils
│   │   ├── api-helpers.js
│   │   ├── form-helpers.js
│   │   └── image-helpers.js
│   ├── api
│   │   ├── auth
│   │   │   ├── me
│   │   │   │   └── route.js
│   │   │   └── route.js
│   │   ├── backup
│   │   │   └── route.js
│   │   ├── cleanup
│   │   │   └── route.js
│   │   ├── custom-transactions
│   │   │   └── route.js
│   │   ├── healthcheck
│   │   │   └── route.js
│   │   ├── pages-data
│   │   │   └── [page]
│   │   │   └── route.js
│   │   ├── tables
│   │   │   ├── route.js
│   │   │   └── [table]
│   │   │   └── [slug]
│   │   │   └── route.js
│   │   └── user
│   │   ├── orders
│   │   │   ├── [order]
│   │   │   │   └── route.js
│   │   │   └── route.js
│   │   └── wishlist
│   │   ├── route.js
│   │   └── [slug]
│   │   └── route.js
│   ├── checkout
│   │   ├── layout.js
│   │   └── page.js
│   ├── contact
│   │   └── page.js
│   ├── error.js
│   ├── layout.js
│   ├── loading.js
│   ├── login
│   │   ├── layout.js
│   │   └── page.js
│   ├── not-found.js
│   ├── page.js
│   ├── sets
│   │   ├── page.js
│   │   └── [slug]
│   │   └── page.js
│   ├── store
│   │   ├── page.js
│   │   └── [slug]
│   │   └── page.js
│   └── tos
│   └── page.js
├── assets
│   └── header-bg.png
├── components
│   ├── account-components
│   │   ├── MenuLink.js
│   │   ├── OrderActions.js
│   │   ├── Orders.js
│   │   ├── SignOutButton.js
│   │   ├── WishlistActions.js
│   │   └── Wishlist.js
│   ├── home-components
│   │   ├── HomeListItems.js
│   │   ├── HomePageSlider.js
│   │   ├── Partners.js
│   │   ├── PromotedComponent.js
│   │   ├── Promotions.js
│   │   ├── ScrollControls.js
│   │   └── ScrollDots.js
│   ├── others-components
│   │   ├── CartSidebar.js
│   │   ├── ContactBox.js
│   │   ├── CopyBtn.js
│   │   ├── ExpandableGallery.js
│   │   ├── FloatingCartButton.js
│   │   ├── Footer.js
│   │   ├── HeaderAccount.js
│   │   ├── HeaderForm.js
│   │   ├── Header.js
│   │   ├── Invoice.js
│   │   ├── Logo.js
│   │   ├── MediaDisplay.js
│   │   ├── MobileNav.js
│   │   ├── MultiRangeSlider.js
│   │   ├── NavLink.js
│   │   ├── NavWrapper.js
│   │   ├── OpenCartBtn.js
│   │   ├── SetsPagnination.js
│   │   ├── Spinner.js
│   │   ├── Stars.js
│   │   ├── SystemTimeChecker.js
│   │   └── ThemeScript.js
│   └── store-components
│   ├── ExpandableWrapper.js
│   ├── ImageSelect.js
│   ├── NewTag.js
│   ├── ProductCard.js
│   ├── ProductCardVariantsStatus.js
│   ├── ProductDescription.js
│   ├── ProductForm.js
│   ├── RelatedProducts.js
│   ├── RelatedSets.js
│   ├── ReviewsForm.js
│   ├── ReviewsItem.js
│   ├── ReviewsList.js
│   ├── ReviewsReplyForm.js
│   └── StoreFilterOptions.js
├── context
│   ├── AuthContext.js
│   ├── ConfirmModal.js
│   └── WishlistContext.js
├── helpers
│   ├── config.js
│   ├── functions.js
│   ├── language-ar.js
│   ├── language.js
│   └── server-functions.js
├── hooks
│   ├── useIsMobile.js
│   └── useOutsideClick.js
├── lib
│   ├── auth.js
│   ├── backup.js
│   ├── data.js
│   ├── db.js
│   ├── email.js
│   ├── get-ip.js
│   ├── pages-data.js
│   ├── rate-limiter-db.js
│   ├── review.js
│   ├── session.js
│   └── wishlist.js
└── styles
├── globals.css
├── react-paginate.css
└── tos.css

Last updated on September 20, 2025 by Ayman.