Egyptโs premier mobile-first pet community ecosystem & emergency response platform.
Pubzy is a modern, high-performance mobile application engineered to solve critical animal welfare challenges in Egypt. By combining emergency rescue dispatching, lost & found reporting, responsible pet adoption matching, and a community pet marketplace, Pubzy bridges the emotional and operational gap left by generic classified platforms like OLX or Dubizzle.
final_pupzy.mp4
๐ฅ Watch the full Pubzy Mobile Application walkthrough video above!
Pubzy is built around four specialized verticals, each designed with tailored UX flows, spatial logic, and engagement mechanics:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PUBZY PLATFORM โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ โผ
๐จ Emergency Help ๐ Lost & Found ๐ Pet Adoption ๐ Pet Marketplace
Dispatch Board Community Network Matchmaking Classifieds Engine
- 911-Style Dispatch Board: High-priority real-time feed sorted strictly by urgency (
CRITICALโURGENTโMODERATE). - Coordination Signals: Indicates reporter role (
REPORTING- spotted & left,ON_SITE- waiting with animal,CAN_TRANSPORT- ready to move). - Live GPS Coordinates: Returns exact PostGIS latitude/longitude with one-tap native Google Maps deep linking (
maps.google.com/?q={lat},{lng}) viaurl_launcher. - Zero Expiry: Rescue listings are never auto-removed until marked
RESOLVED.
- Dual-Direction Matching: Unifies both
LOST_PET(owners seeking lost pets) andFOUND_STRAY(citizens hosting or spotting strays). - Detailed Descriptors: Filter by species, breed, color, markings, collar/ID tag, date last seen/found, and animal health condition.
- Resolution Tracking: Instant status toggles for
REUNITEDandRESOLVED.
- Privacy-First Matching: Owner contact details are protected behind a multi-step adoption application gate.
- Smart Screening Questionnaire: Evaluates living situation (apartment, yard, farm), prior experience, children/pet compatibility, vet references, and home visit consent.
- Hot Score Ranking: Reddit-inspired engagement algorithm surfaces active, highly-interacted adoption listings organically.
- Zero-Fee Classifieds: Direct buyer-to-seller connections without middleman commission fees or cart friction.
- Supplies & Care: Categorized browsing for
CARE,FOOD,TRANSPORT,ACCESSORIES,GROOMING, andMEDICAL_SUPPLIES. - Instant WhatsApp Link: One-tap interaction generating encrypted
wa.me/direct contact links dynamically. - View-Driven Ranking: Driven by buyer activity signals with automated inactivity pruning after 14 days of zero views.
Pubzy follows Clean Architecture and Domain-Driven Design (DDD) principles across both backend and mobile applications.
graph TB
subgraph Client ["๐ฑ Flutter Mobile App (Dart)"]
UI["UI Components & Views"]
RIVERPOD["Riverpod State Management"]
GQL_CLIENT["GraphQL Client (Ferry / GraphQL Flutter)"]
FB_SDK["Firebase Auth SDK"]
LAUNCHER["url_launcher (Maps & WhatsApp)"]
end
subgraph Infrastructure_Cloud ["โ๏ธ Cloud Services"]
FB_AUTH["Firebase Auth Server"]
R2["Cloudflare R2 (Object Storage)"]
RAILWAY["Railway Application Hosting"]
end
subgraph Backend ["โก NestJS GraphQL Backend (Node.js)"]
GUARD["FirebaseAuthGuard"]
RESOLVERS["GraphQL Resolvers (Code-First)"]
subgraph Services ["Core Services"]
POST_SVC["PostsService"]
FEED_SVC["FeedService"]
RANK_SVC["RankingService"]
VIEW_SVC["ViewsService"]
CONTACT_SVC["ContactService (AES-256)"]
end
DRIZZLE["Drizzle ORM"]
end
subgraph Database_Layer ["๐๏ธ Persistence Layer"]
PG[("PostgreSQL 16 + PostGIS\n(Class Table Inheritance)")]
REDIS[("Redis Cache & Buffer\n(Feed Cache & View Deduplication)")]
end
UI --> RIVERPOD
RIVERPOD --> GQL_CLIENT
GQL_CLIENT -->|"Bearer JWT"| GUARD
FB_SDK -->|"ID Token"| GQL_CLIENT
GUARD -->|"Verify Token"| FB_AUTH
GUARD --> RESOLVERS
RESOLVERS --> Services
Services --> DRIZZLE
Services -->|"Decrypted Phone -> wa.me Link"| LAUNCHER
DRIZZLE --> PG
VIEW_SVC -->|"Deduplicate & Buffer Views"| REDIS
FEED_SVC -->|"Cache Feed Pages"| REDIS
POST_SVC -->|"Presigned Upload URLs"| R2
To eliminate spam and manual "bumping" mechanics (e.g. OLX last-bumped mechanisms), Pubzy utilizes dynamic organic scoring with logarithmic time decay.
Surfaces adoption listings based on active community intent (upvotes, saves, and views):
-
Upvotes (
$\times 3$ ) & Saves ($\times 2$ ): High-intent explicit community signals. -
Views (
$\times 0.1$ ): Low-weight passive traffic signal. -
Time Decay (
$(\text{age_hours} + 2)^{1.5}$ ): Ensures fresh adoption posts gain visibility while older posts naturally decay unless backed by strong engagement.
Ranks product listings by buyer demand without upvote clutter:
-
Saves (
$\times 5$ ): High-intent purchase bookmark signal. -
Views (
$\times 1$ ): Primary organic browsing metric. -
Upvotes: Disabled (
upvote_countremains 0).
Direct PostgreSQL database writes on every post view would create severe write bottlenecks under high traffic. Pubzy implements a high-performance Redis-buffered view pipeline:
sequenceDiagram
autonumber
actor User as Flutter Client
participant API as NestJS GraphQL
participant Redis as Redis Cache
participant Cron as NestJS Cron Worker
participant PG as PostgreSQL 16 DB
User->>API: POST /posts/{postId}/view (Async / Fire-and-Forget)
API->>Redis: SET view_dedup:{postId}:{userId} 1 NX EX 3600
alt View is Duplicate (Within 1 Hour)
Redis-->>API: Key Exists (Lock Active)
API-->>User: 200 OK (Ignored)
else View is New & Unique
Redis-->>API: Key Created
API->>Redis: INCR view_buffer:{postId}
API-->>User: 200 OK (Count Incremented in Buffer)
end
Note over Cron,PG: Every 2-3 Minutes (Batch View Flush Cron)
Cron->>Redis: SCAN view_buffer:*
Redis-->>Cron: Return buffered view counts per postId
Cron->>PG: UPDATE posts SET view_count = view_count + N
Cron->>PG: Recompute effective_score & update last_engaged_at
Cron->>Redis: DEL view_buffer:{postId}
| Post Type | Coordinate Exposure | Location Returned | Primary Action |
|---|---|---|---|
RESCUE |
Full (Exact GPS) | { latitude, longitude } |
Open in Google Maps (maps.google.com/?q=lat,lng) |
LOST |
Full (Exact GPS) | { latitude, longitude } |
Open in Google Maps (maps.google.com/?q=lat,lng) |
ADOPTION |
๐ Masked | City Name + Distance (km) |
Submit Adoption Application Gate |
PRODUCT |
๐ Masked | City Name + Distance (km) |
Request Contact / WhatsApp Link |
- AES-256-GCM Encryption: User phone numbers are stored strictly encrypted at rest inside PostgreSQL.
- On-The-Fly Decryption: Phone numbers are decrypted only when an owner explicitly approves a contact request or application.
- Zero Storage Client-side: The backend computes a formatted
wa.me/{phone}URL dynamically at GraphQL query time. Raw numbers are never stored in client memory or exposed in feed payloads.
Pubzy utilizes Class Table Inheritance (CTI) in PostgreSQL to combine polymorphic feed performance with strong table-level type constraints.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ posts (Base) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id: uuid (PK) โ
โ creator_id: uuid (FK -> users) โ
โ post_type: post_type (ENUM) โ
โ status: post_status (ENUM) โ
โ city_id: uuid (FK -> cities) โ
โ coordinates: geometry(POINT, 4326) โ
โ view_count / upvote_count / save_count โ
โ effective_score: float โ
โ last_engaged_at: timestamptz โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ (1:1 Joined on Detail Screen Only)
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ โผ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ rescue_posts โ โ lost_posts โ โadoption_postsโ โproduct_posts โ
โโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโค
โ post_id (PK) โ โ post_id (PK) โ โ post_id (PK) โ โ post_id (PK) โ
โ species โ โ report_type โ โ pet_name โ โ category โ
โ condition โ โ breed โ โ age_value โ โ condition โ
โ reporter_roleโ โ date_seen โ โ personality โ โ price_amount โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
- Geospatial Queries: PostGIS GIST Index on
posts.coordinatesforST_DWithinspatial radius filtering. - Partial Feed Indexes:
idx_posts_help_feed:(city_id, post_type, urgency, created_at) WHERE status='ACTIVE' AND post_type IN ('RESCUE','LOST')idx_posts_adopt_score:(city_id, effective_score, created_at) WHERE status='ACTIVE' AND post_type='ADOPTION'idx_posts_market_score:(city_id, effective_score, created_at) WHERE status='ACTIVE' AND post_type='PRODUCT'idx_posts_market_category:(city_id, market_category, effective_score) WHERE status='ACTIVE' AND post_type='PRODUCT'
- Automated Counter Triggers: PostgreSQL trigger
user_post_count_triggerautomatically maintains all 5 user activity counters in sync across direct DB operations and AdminJS updates.
| Layer | Technology | Purpose |
|---|---|---|
| Mobile App | Flutter (Dart 3.x) | Cross-platform iOS & Android application |
| State Management | Riverpod 2.x | Reactive state & dependency injection |
| API Paradigm | GraphQL (Code-First NestJS) | Strongly-typed API queries & mutations |
| Backend Framework | NestJS (Node.js & TypeScript) | Modular enterprise microservice backend |
| ORM | Drizzle ORM | Type-safe SQL query builder & migration engine |
| Database | PostgreSQL 16 + PostGIS | Relational data & spatial GIS indexing (SRID 4326) |
| Cache & Buffer | Redis | Feed cache, view deduplication & atomic view counters |
| Authentication | Firebase Auth | Social login (Google & Facebook) via FlutterFire |
| Cloud Storage | Cloudflare R2 | Zero-egress S3-compatible image hosting via presigned URLs |
| Deployment | Docker & Railway | Containerized backend deployment & CI/CD pipeline |
- Flutter SDK:
>=3.19.0 - Node.js:
>=20.x&npm - Docker & Docker Compose (for local Postgres & Redis)
# Start PostgreSQL (with PostGIS extension) and Redis containers
docker run --name pupzy-postgres -e POSTGRES_DB=pupzy -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgis/postgis:16-3.4
docker run --name pupzy-redis -p 6379:6379 -d redis:alpine# Navigate to backend directory
cd backend
# Install dependencies
npm install
# Copy environment template
cp .env-example .env
# Run Drizzle DB migrations & seed city database
npm run db:push
npm run db:seed
# Start NestJS backend development server
npm run start:devThe GraphQL Playground will be accessible at: http://localhost:3000/graphql.
# Navigate to frontend directory
cd frontend
# Get Flutter packages
flutter pub get
# Run on connected device or simulator
flutter runPubzy enforces a Strict No-Abbreviations Policy across all database columns, code symbols, GraphQL types, and variable names for long-term codebase maintainability:
| โ Forbidden Abbreviation | โ Mandatory Full Name |
|---|---|
name_en / name_ar |
name_english / name_arabic |
geom / center_geom |
coordinates / center_point |
firebase_uid |
firebase_user_id |
url / r2_key |
public_url / cloudflare_storage_key |
content_type |
file_content_type |
has_collar |
has_collar_with_identification_tag |
| Role | Contributor | Stack |
|---|---|---|
| Backend & Architecture | Garma | NestJS, PostgreSQL/PostGIS, Drizzle ORM, Redis, GraphQL |
| Mobile Frontend | Matheo Mochles | Flutter, Riverpod, Ferry, GraphQL |
Made with โค๏ธ in Egypt for animal rescue & protection across the nation.
[Pubzy โ Rescue. Adopt. Care. ๐พ]