Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

Frontend Repository: https://github.com/coder2505/doodleFrontend.git
Backend Repository: https://github.com/coder2505/doodleBackend.git

Doodle: Full-Stack Real-Time Collaborative Canvas & Widget System

Welcome to Doodle, a full-stack real-time collaborative Android widget and canvas application. Doodle allows users to create shared rooms, draw or write text on an interactive canvas, and instantly sync updates across all room members' Android home screen widgets powered by Spring Boot, PostgreSQL, and Firebase Cloud Messaging (FCM).


1. Full-Stack System Architecture & End-to-End Flow

Below is the overall end-to-end architecture and messaging flow of Doodle, demonstrating how the Android App, Spring Boot Backend, PostgreSQL Database, and Firebase Cloud Messaging (FCM) interact in real-time.

sequenceDiagram
    autonumber
    actor User as Mobile App User
    participant App as doodleFrontend (Android)
    participant FCM as Firebase Cloud Messaging
    participant BE as doodleBackend (Spring Boot)
    participant DB as PostgreSQL Database

    App->>FCM: 1. Fetch FCM Device Token
    FCM-->>App: Return Token
    App->>BE: 2. POST /login/user/{username}/{fcmToken}
    BE->>DB: 3. Save User & FCM Token
    DB-->>BE: Saved
    BE-->>App: Return JWT Access & Refresh Tokens

    User->>App: 4. Edit Text / Canvas
    App->>BE: 5. POST /widget/text/{payload} (Bearer JWT)
    BE->>DB: 6. Update room.resource payload
    BE->>DB: 7. Query room members & FCM tokens
    DB-->>BE: Return list of user FCM tokens
    BE->>BE: 8. Generate OAuth2 Access Token (doodleapp-firebase.json)
    BE->>FCM: 9. HTTP v1 POST Data Message (FCM Token, Payload)
    FCM-->>App: 10. Deliver silent FCM Push Message
    App->>App: 11. MyFirebaseMessagingService -> Update Glance Widget
Loading

Component Overview

                                  +-------------------------------------------------+
                                  |            Android App (doodleFrontend)         |
                                  |  - Jetpack Compose UI / Canvas Drawing          |
                                  |  - Jetpack Glance (Home Screen Widget)          |
                                  |  - Hilt, Retrofit2, CryptoManager, WorkManager  |
                                  +-----------------------+-------------------------+
                                                          |
                                          HTTP REST API   |   Firebase FCM
                                         (JWT Protected)  |  Push Payload
                                                          v           ^
+------------------------------------+             +------+-----------+--------------+
|        PostgreSQL Database         |             |       Spring Boot Backend       |
|                                    | <=========> |        (doodleBackend)        |
|  - users (UUID PK, fcm_token)      |   Spring    |  - Controllers, Security, JWT |
|  - room (BigInt PK, resource)      |  Data JPA   |  - FCM Service (WebClient)    |
|  - user_room (Composite PK, M:N)   |             |  - Spring Web / Jackson          |
+------------------------------------+             +---------------------------------+

2. Frontend Architecture (doodleFrontend)

The Android application is built natively in Kotlin using modern Jetpack libraries, clean architecture patterns, and robust network resilience mechanisms.

Key Technical Highlights & Features

1. OkHttp Dual-Interceptor Network Engine

Networking is powered by Retrofit 2 and OkHttp, utilizing a specialized dual-interceptor pipeline:

  • HeaderInterceptor: Transparently injects the Authorization: Bearer <access_token> header into all outgoing API requests by fetching valid credentials from encrypted storage.
  • RefreshInterceptor: Manages automatic token renewal without disrupting the UI:
    1. Intercepts 400 BAD_REQUEST or 401 UNAUTHORIZED responses caused by an expired access token (15-minute lifetime).
    2. Synchronously executes a token refresh call (POST /login/refresh) using the securely stored long-lived Refresh Token (365-day lifetime).
    3. Updates local encrypted storage with the new Access Token.
    4. Retries the original failed HTTP request seamlessly, ensuring zero interrupted user sessions.

2. Encrypted Token Storage (CryptoManager & TokenManager)

Sensitive JWT access and refresh tokens are stored securely on-device using Android KeyStore primitives and EncryptedSharedPreferences (CryptoManager), preventing credential extraction from rooted devices or memory dumps.

3. Interactive Jetpack Compose Canvas (DrawCanvas.kt)

  • Built with Jetpack Compose custom drawing primitives and gesture handling (pointerInput).
  • Captures touch coordinates, stroke paths, colors, and text annotations in real time, serializing canvas vector data into optimized payload strings.

4. Jetpack Glance Home Screen Widget (Widget.kt & WidgetReceiver.kt)

  • Declarative home screen app widget constructed with Jetpack Glance (Jetpack Compose for Widgets).
  • Re-renders dynamically when receiving silent FCM pushes, rendering fresh canvas drawings or text directly on the Android home screen.

5. Silent Push Receiver (MyFirebaseMessagingService)

  • Handles background data payloads from Firebase Cloud Messaging.
  • Signals Jetpack Glance (GlanceAppWidget.updateAll()) and schedules background sync tasks (WorkManager) even if the application is killed or inactive.

6. Dependency Injection with Google Hilt

  • Uses @HiltAndroidApp and @AndroidEntryPoint for clean dependency graph management.
  • Provides singletons for OkHttpClient, Retrofit API interfaces, Token Managers, and Repository layers.

3. Backend Architecture (doodleBackend)

The Spring Boot backend serves as the core REST API engine and push notification dispatcher.

Key Technical Highlights & Features

1. Centralized Authorization Interceptor (Interceptor.java & InterceptorConfig.java)

  • WebMvc Handler Interceptor: Implements Spring's HandlerInterceptor registered via InterceptorConfig (excluding public paths /login/**, /swagger-ui/**, and API docs).
  • Request Pre-Handling & User Context: Validates Authorization: Bearer <token> headers on all protected endpoints (/widget/**, /room/**). Validates JWT signature and expiration, extracts the user's UUID subject, and binds it to HttpServletRequest attributes (request.setAttribute("user_id", user_id)), keeping controller endpoints clean and decoupled from token parsing logic.

2. Dual-Token JWT Security Architecture (JwtUtil.java)

  • Access Tokens: Short-lived (15 minutes) HMAC-SHA signed JWT tokens used for authenticating REST requests.
  • Refresh Tokens: Long-lived (365 days) tokens used exclusively for renewing expired access tokens via POST /login/refresh.
  • Environment-based Key Management: Signing keys are securely decoded from environment configurations (SIGNING_KEY).

3. Non-Blocking Firebase HTTP v1 Messaging (WebClient & FCMTokenService)

  • Async Spring WebClient: Executes non-blocking HTTP v1 POST payloads to Firebase Cloud Messaging endpoints (https://fcm.googleapis.com/v1/projects/{project_id}/messages:send).
  • OAuth2 Service Account Authentication: FCMTokenService reads doodleapp-firebase.json service account credentials to generate Google OAuth2 access tokens scoped to https://www.googleapis.com/auth/firebase.messaging.
  • Targeted Room Fanout: On canvas/text payload mutations, the backend queries PostgreSQL for all FCM tokens belonging to active room members and dispatches push data messages asynchronously.

4. Database Architecture & Schema Analysis

The PostgreSQL database (doodleDB) consists of three normalized tables designed to capture user identities, room resources, and membership relationships.

erDiagram
    users {
        uuid user_id PK "gen_random_uuid()"
        varchar user_name "NOT NULL"
        varchar fcm_token "NULLABLE"
    }

    room {
        bigint room_id PK "generated by default as identity"
        varchar room_name "NOT NULL"
        varchar resource "NULLABLE (Payload/Canvas/Text)"
    }

    user_room {
        uuid user_id PK, FK "ON DELETE CASCADE"
        bigint room_id PK, FK "ON DELETE CASCADE"
    }

    users ||--o{ user_room : "joins"
    room ||--o{ user_room : "contains"
Loading

Table Definitions

users Table

CREATE TABLE public.users (
    user_id uuid NOT NULL DEFAULT gen_random_uuid(),
    user_name character varying(255) NOT NULL,
    fcm_token character varying(255),
    CONSTRAINT users_pkey PRIMARY KEY (user_id)
);
  • user_id (UUID): Universally unique primary key automatically generated by PostgreSQL's gen_random_uuid().
  • user_name (VARCHAR(255)): Display name of the user.
  • fcm_token (VARCHAR(255)): Unique device registration token assigned by Firebase Cloud Messaging.

room Table

CREATE TABLE public.room (
    room_id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    room_name character varying(255) NOT NULL,
    resource character varying(255),
    CONSTRAINT room_pkey PRIMARY KEY (room_id)
);
  • room_id (BIGINT): Auto-incrementing identity sequence used as a primary key and room code.
  • room_name (VARCHAR(255)): Name assigned to the doodle room.
  • resource (VARCHAR(255)): Stores the current shared payload (text string or drawing data) displayed on all room members' widgets.

user_room Table (Junction / Bridge Table)

CREATE TABLE public.user_room (
    user_id uuid NOT NULL,
    room_id bigint NOT NULL,
    CONSTRAINT user_room_pkey PRIMARY KEY (user_id, room_id),
    CONSTRAINT user_room_room_id_fkey FOREIGN KEY (room_id) 
        REFERENCES public.room(room_id) ON DELETE CASCADE,
    CONSTRAINT user_room_user_id_fkey FOREIGN KEY (user_id) 
        REFERENCES public.users(user_id) ON DELETE CASCADE
);
  • user_id & room_id: Composite primary key enforcing unique membership.
  • Foreign Keys: Direct references to users(user_id) and room(room_id) with ON DELETE CASCADE.

5. Key Design Decisions & Database Normalization

1. Normalization (3rd Normal Form Compliance)

  • First Normal Form (1NF): Every table column contains atomic, scalar values without nested arrays or repeated groups.
  • Second Normal Form (2NF): The junction table user_room uses a Composite Primary Key (user_id, room_id). Non-key dependencies are eliminated since user metadata resides solely in users and room metadata resides solely in room.
  • Third Normal Form (3NF): Transitive dependencies are eliminated. fcm_token is dependent exclusively on user_id, and resource is dependent strictly on room_id.

2. Primary Key Strategy: UUID vs. BigInt Identity

  • Users (UUID): Utilizing 128-bit UUIDs (gen_random_uuid()) prevents sequential user enumeration attacks, securely embeds user IDs into JWT tokens, and allows safe multi-device identity tracking.
  • Rooms (BIGINT Identity): Utilizing an auto-incrementing BIGINT identity sequence provides clean, concise numeric room codes that users can easily copy, share, and input on mobile screens to join rooms.

3. Cascading Referential Integrity (ON DELETE CASCADE)

  • Both Foreign Keys in user_room enforce ON DELETE CASCADE. When a user deletes their account or a room is destroyed, PostgreSQL automatically cleans up all associated junction records. This eliminates orphan rows and ensures absolute data integrity without requiring redundant application-level cleanup logic.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors