Skip to content

LoneProg/arctiv-cms

Repository files navigation

Arctiv

A secure, full-stack social media management platform that enables clients, managers, and agencies to collaborate without sharing credentials.


Overview

Arctiv solves a fundamental trust problem in social media management: clients need managers to operate their accounts, but handing over passwords is a security risk. Arctiv provides a structured access-grant system where clients retain ownership, managers get scoped permissions, and agencies can oversee teams — all tracked in a persistent database.


Tech Stack

Layer Technology
Framework Next.js 16 (App Router)
Database PostgreSQL via Neon (serverless)
ORM / Query @neondatabase/serverless tagged-template SQL
Auth bcryptjs (password hashing) + jose (JWT, HS256)
Validation Zod
UI shadcn/ui + Tailwind CSS v4
Session HTTP-only cookies (7-day JWT, server-side revocation)

User Roles

Role Description
client Owns social media accounts. Grants/revokes access to managers or agencies. Reviews and approves content drafts.
manager Hired by clients. Creates content drafts for assigned accounts. Participates in the Managers Hub feed.
agency Manages teams of managers. Has a company profile. Appears in the hub alongside managers.

Project Structure

arctiv/
├── app/
│   ├── page.tsx                          # Public landing page
│   ├── login/page.tsx                    # Login page (all roles)
│   ├── register/page.tsx                 # Registration page (role selector)
│   ├── dashboard/
│   │   ├── page.tsx                      # Redirect → role-specific dashboard
│   │   ├── client/page.tsx               # Client dashboard
│   │   ├── manager/page.tsx              # Manager dashboard
│   │   └── agency/page.tsx               # Agency dashboard
│   └── api/
│       ├── auth/
│       │   ├── register/route.ts         # POST /api/auth/register
│       │   ├── login/route.ts            # POST /api/auth/login
│       │   ├── logout/route.ts           # POST /api/auth/logout
│       │   └── me/route.ts              # GET  /api/auth/me
│       ├── users/
│       │   └── profile/route.ts         # GET/PATCH /api/users/profile
│       ├── social-accounts/
│       │   ├── route.ts                  # GET/POST /api/social-accounts
│       │   └── [id]/route.ts            # DELETE   /api/social-accounts/:id
│       ├── access-grants/
│       │   ├── route.ts                  # GET/POST /api/access-grants
│       │   └── [id]/route.ts            # DELETE   /api/access-grants/:id
│       ├── relationships/
│       │   ├── route.ts                  # GET/POST /api/relationships
│       │   └── [id]/route.ts            # PATCH/DELETE /api/relationships/:id
│       ├── content-drafts/
│       │   ├── route.ts                  # GET/POST /api/content-drafts
│       │   └── [id]/route.ts            # GET/PATCH/DELETE /api/content-drafts/:id
│       ├── hub/
│       │   ├── posts/
│       │   │   ├── route.ts              # GET/POST /api/hub/posts
│       │   │   └── [id]/
│       │   │       ├── route.ts          # DELETE  /api/hub/posts/:id
│       │   │       ├── like/route.ts     # POST    /api/hub/posts/:id/like
│       │   │       └── comments/route.ts # GET/POST /api/hub/posts/:id/comments
│       │   └── managers/route.ts        # GET /api/hub/managers (public directory)
│       ├── messages/
│       │   ├── conversations/route.ts   # GET/POST /api/messages/conversations
│       │   └── [conversationId]/route.ts # GET/POST /api/messages/:conversationId
│       └── notifications/route.ts       # GET/PATCH /api/notifications
├── lib/
│   ├── db.ts           # Neon SQL client singleton
│   ├── auth.ts         # bcrypt helpers + JWT sign/verify + jti hashing
│   ├── session.ts      # Cookie management + DB session validation
│   ├── validate.ts     # All Zod schemas
│   └── api.ts          # ok/err response helpers + central error handler
├── types/
│   └── index.ts        # All TypeScript interfaces (DB rows, API shapes, JWT)
├── components/
│   └── logout-button.tsx  # Client component — hard-navigates to clear cookie
├── middleware.ts        # JWT-based route protection + role enforcement
└── scripts/
    └── 001_schema.sql  # Full database migration (idempotent)

Database Schema

All tables are created by scripts/001_schema.sql. The migration is idempotent (CREATE TABLE IF NOT EXISTS, DO $$ BEGIN ... EXCEPTION WHEN duplicate_object).

Tables

users

Core identity table. One row per account regardless of role.

Column Type Notes
id UUID PK gen_random_uuid()
email TEXT UNIQUE Login identifier
password_hash TEXT bcrypt, 12 rounds
role user_role ENUM client, manager, agency
is_active BOOLEAN Soft-disable accounts

sessions

Server-side session store. Enables token revocation without waiting for JWT expiry.

Column Type Notes
token_hash TEXT UNIQUE SHA-256 of JWT jti
ip_address INET First IP from x-forwarded-for
expires_at TIMESTAMPTZ 7 days from creation

profiles

Extended public info for all roles. Single row per user.

Notable columns: specialties TEXT[], availability, agency_name, team_size (manager/agency specific fields colocated in one table).

social_accounts

Social media pages owned by clients (or agencies). Stores an encrypted OAuth access token field (access_token_enc) for future OAuth integration.

Platforms: instagram, facebook, twitter, linkedin, tiktok.

access_grants

The core trust primitive. Maps a social_account → a granted_to user, with active/revoked status. Enforces UNIQUE(social_account_id, granted_to_id) so grants are not duplicated.

client_manager_relationships

Tracks the hiring lifecycle between a client and a manager: pendingactivedismissed.

conversations + conversation_participants + messages

Normalized messaging schema. Conversations are N-party (supports group chats). Messages track is_read per row.

hub_posts + hub_post_likes + hub_post_comments

LinkedIn-style feed for managers and agencies. Likes are deduplicated with UNIQUE(post_id, user_id). likes_count is a denormalized counter updated on insert/delete of likes.

content_drafts

Content workflow table. A manager creates a draft for a client's social account. Status flow: draftpending_approvalapproved | rejectedpublished. Includes rejection_note for feedback.

notifications

In-app notification log. Typed via notification_type ENUM covering content lifecycle, access changes, relationship events, and messages.

ENUMs

ENUM Values
user_role client, manager, agency
social_platform instagram, facebook, twitter, linkedin, tiktok
access_status active, revoked
relationship_status pending, active, dismissed
content_status draft, pending_approval, approved, rejected, published
notification_type content_pending, content_approved, content_rejected, access_granted, access_revoked, hire_request, hire_accepted, dismissed, new_message, report_ready

Triggers

set_updated_at() trigger fires on BEFORE UPDATE for: users, profiles, social_accounts, client_manager_relationships, content_drafts, hub_posts.


Authentication

Flow

  1. RegisterPOST /api/auth/register validates input with Zod, hashes password with bcrypt (12 rounds), inserts into users + profiles, signs a JWT, stores a session row, sets an HTTP-only cookie.
  2. LoginPOST /api/auth/login verifies password, issues a new JWT, stores a new session row, sets cookie.
  3. Session validation — Every protected route calls requireSession() which: reads the cookie → verifies JWT signature → looks up token_hash in sessions table → confirms not expired. Both the JWT and the DB session must be valid.
  4. LogoutPOST /api/auth/logout deletes the session row from the DB, then returns a 303 redirect response with Set-Cookie: maxAge=0 to expire the cookie at the browser level. The LogoutButton component uses window.location.href (not router.push) to perform a hard navigation so the browser processes the Set-Cookie header before the middleware runs again.

JWT Structure

{
  "sub": "<user_id>",
  "role": "client | manager | agency",
  "jti": "<uuid>",
  "iat": 1234567890,
  "exp": 1235172690
}

The jti is stored as SHA-256(jti) in the sessions table for revocation lookups.

Middleware

middleware.ts runs on all /dashboard/*, /login, and /register routes:

  • Unauthenticated requests to /dashboard/* → redirect to /login?next=<path>
  • Authenticated requests to /login or /register → redirect to role-specific dashboard
  • Authenticated requests to the wrong role's dashboard → redirect to own dashboard
  • GET /dashboard (bare) → redirect to /dashboard/<role>

API Reference

All endpoints return { success: true, data: ... } on success or { success: false, error: "...", details?: {...} } on failure.

Auth

Method Path Auth Description
POST /api/auth/register No Create account. Body: email, password, role, full_name
POST /api/auth/login No Login. Body: email, password
POST /api/auth/logout Yes Delete session, expire cookie, redirect to /login
GET /api/auth/me Yes Returns current user + profile

Users

Method Path Auth Description
GET /api/users/profile Yes Get own profile
PATCH /api/users/profile Yes Update profile fields

Social Accounts

Method Path Auth Description
GET /api/social-accounts Yes List own social accounts
POST /api/social-accounts Yes Connect a new account
DELETE /api/social-accounts/:id Yes Remove account (owner only)

Access Grants

Method Path Auth Description
GET /api/access-grants Yes List grants (as grantor or grantee)
POST /api/access-grants Yes Grant access to a manager/agency
DELETE /api/access-grants/:id Yes Revoke a grant (grantor only)

Relationships

Method Path Auth Description
GET /api/relationships Yes List hire relationships
POST /api/relationships Yes Hire a manager (client only)
PATCH /api/relationships/:id Yes Accept hire request (manager only)
DELETE /api/relationships/:id Yes Dismiss manager (client only)

Content Drafts

Method Path Auth Description
GET /api/content-drafts Yes List drafts (manager sees own; client sees drafts for them)
POST /api/content-drafts Yes Create draft (manager only)
GET /api/content-drafts/:id Yes Get single draft
PATCH /api/content-drafts/:id Yes Update draft or submit for review
DELETE /api/content-drafts/:id Yes Delete draft (manager, own only)

Content review (approve/reject) is done via PATCH /api/content-drafts/:id with { action: "approved" | "rejected", rejection_note? } — client role only.

Managers Hub

Method Path Auth Description
GET /api/hub/managers Yes Browse manager/agency profiles
GET /api/hub/posts Yes Feed of hub posts (paginated)
POST /api/hub/posts Yes Create a post (manager/agency only)
DELETE /api/hub/posts/:id Yes Delete own post
POST /api/hub/posts/:id/like Yes Toggle like on a post
GET /api/hub/posts/:id/comments Yes List comments
POST /api/hub/posts/:id/comments Yes Add a comment

Messages

Method Path Auth Description
GET /api/messages/conversations Yes List conversations
POST /api/messages/conversations Yes Start a new conversation
GET /api/messages/:conversationId Yes List messages in a conversation
POST /api/messages/:conversationId Yes Send a message

Notifications

Method Path Auth Description
GET /api/notifications Yes List notifications (unread first)
PATCH /api/notifications Yes Mark as read ({ all: true } or { notification_ids: [...] })

Environment Variables

Variable Required Description
DATABASE_URL Yes Neon PostgreSQL connection string (set by Neon integration)
JWT_SECRET Yes Minimum 32-character secret for signing JWTs
NODE_ENV Auto Set to production by Vercel to enable secure cookies

Design System

  • Primary color: Deep navy (oklch(0.26 0.07 255))
  • Accent color: Ice blue (oklch(0.70 0.14 225))
  • Neutrals: Off-white background, slate borders, muted gray text
  • Dark mode: Full dark theme via .dark CSS class with matching token set
  • Typography: Geist Sans (body) + Geist Mono (code)
  • Components: shadcn/ui component library throughout

Known Limitations / Future Work

  • OAuth token encryption (access_token_enc) column exists but encryption/decryption is not yet implemented — tokens are stored as plain text until an encryption key is introduced.
  • Real-time messaging (WebSockets / SSE) is not implemented; the messages API is polling-based.
  • File/media uploads for content drafts and hub posts store URLs only — a Blob storage integration (e.g. Vercel Blob) would be needed for actual uploads.
  • Email notifications (hire requests, content approvals) are not yet wired — the in-app notifications table is fully functional but no email delivery service is connected.
  • No rate limiting on auth endpoints yet.

About

A social media account management for managers and clients and agencies to conect and collaborate without sharing passwords or credentials

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors