Technical Specifications — UAD Knowledge Base

Audience: Developer / IT Administrator
Stack: Next.js 14 · Firebase · Google APIs · Algolia · Tailwind CSS


Environment Variables

# .env.local (never commit this file)

# Firebase
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
FIREBASE_ADMIN_PRIVATE_KEY=
FIREBASE_ADMIN_CLIENT_EMAIL=

# Google APIs (Service Account)
GOOGLE_SERVICE_ACCOUNT_EMAIL=
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY=
GOOGLE_DRIVE_FOLDER_ID=
GOOGLE_SHEETS_QA_LOG_ID=

# Algolia
NEXT_PUBLIC_ALGOLIA_APP_ID=
NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY=
ALGOLIA_ADMIN_API_KEY=

# App Config
NEXT_PUBLIC_COMPANY_DOMAIN=yourcompany.com
NEXT_PUBLIC_APP_URL=https://kb.yourcompany.com
NEXTAUTH_SECRET=
NEXTAUTH_URL=https://kb.yourcompany.com

Firestore Data Models

Article

interface Article {
  id: string;
  title: string;
  slug: string;
  category: ArticleCategory;
  subcategory?: string;
  summary: string;
  content: string;           // Markdown
  tags: string[];
  relatedArticleIds: string[];
  relatedDocumentIds: string[];
  status: 'draft' | 'published' | 'archived';
  authorId: string;
  reviewedBy?: string;
  lastReviewedAt?: Timestamp;
  publishedAt?: Timestamp;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  viewCount: number;
}

FAQEntry

interface FAQEntry {
  id: string;
  question: string;
  shortAnswer: string;
  detailedAnswer?: string;   // Markdown
  category: FAQCategory;
  tags: string[];
  relatedArticleIds: string[];
  relatedDocumentIds: string[];
  approvedBy: string;
  status: 'draft' | 'published' | 'archived';
  sourceQuestionId?: string; // If promoted from Q&A log
  createdAt: Timestamp;
  updatedAt: Timestamp;
  helpfulVotes: number;
  notHelpfulVotes: number;
}

QASubmission

interface QASubmission {
  id: string;
  submitterName: string;
  submitterEmail: string;
  category: string;
  question: string;
  urgency: 'low' | 'medium' | 'high';
  status: 'new' | 'in_review' | 'answered' | 'promoted_to_faq';
  assignedTo?: string;
  answer?: string;
  answeredBy?: string;
  answeredAt?: Timestamp;
  promotedToFAQId?: string;
  submittedAt: Timestamp;
  updatedAt: Timestamp;
  sheetsRowId?: number;
}

Document

interface Document {
  id: string;
  title: string;
  description: string;
  category: DocumentCategory;
  fileType: 'pdf' | 'docx' | 'xlsx' | 'pptx' | 'link' | 'other';
  driveFileId: string;
  driveWebViewLink: string;
  driveDownloadLink: string;
  tags: string[];
  uploadedBy: string;
  status: 'active' | 'archived';
  createdAt: Timestamp;
  updatedAt: Timestamp;
  downloadCount: number;
}

User

interface User {
  id: string;              // Firebase UID
  email: string;
  displayName: string;
  photoURL?: string;
  role: 'viewer' | 'editor' | 'admin' | 'super_admin';
  department?: string;
  lastLoginAt: Timestamp;
  createdAt: Timestamp;
}

Firestore Security Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    function isCompanyUser() {
      return request.auth != null && 
             request.auth.token.email.matches('.*@yourcompany\\.com');
    }
    
    function getUserRole() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
    }
    
    function isEditor() {
      return isCompanyUser() && getUserRole() in ['editor', 'admin', 'super_admin'];
    }
    
    function isAdmin() {
      return isCompanyUser() && getUserRole() in ['admin', 'super_admin'];
    }
    
    match /articles/{articleId} {
      allow read: if isCompanyUser() && 
                     (resource.data.status == 'published' || isEditor());
      allow write: if isEditor();
    }
    
    match /faq/{faqId} {
      allow read: if isCompanyUser() && 
                     (resource.data.status == 'published' || isEditor());
      allow write: if isEditor();
    }
    
    match /qa_submissions/{submissionId} {
      allow create: if isCompanyUser();
      allow read, update: if isAdmin() || 
                             request.auth.uid == resource.data.submitterId;
    }
    
    match /documents/{documentId} {
      allow read: if isCompanyUser();
      allow write: if isEditor();
    }
    
    match /users/{userId} {
      allow read: if isCompanyUser() && 
                     (request.auth.uid == userId || isAdmin());
      allow write: if isAdmin() || request.auth.uid == userId;
    }
  }
}

API Routes

POST /api/questions

Accepts a new Q&A submission. Writes to Firestore + Google Sheets.

Request body:

{
  name: string;
  email: string;
  category: string;
  question: string;
  urgency: 'low' | 'medium' | 'high';
}

Response:

{
  success: boolean;
  submissionId: string;
  message: string;
}

GET /api/search?q={query}&category={cat}

Returns search results across articles, FAQ, and documents via Algolia.

POST /api/drive/sync

Admin-only. Syncs Google Drive folder contents to Firestore documents collection.

GET /api/analytics/summary

Admin-only. Returns usage stats: views, searches, question volume.


Google Sheets Q&A Log Schema

Sheet name: UAD KB — Q&A Log

Column Header Type
A Submission ID Text
B Timestamp DateTime
C Name Text
D Email Text
E Category Text
F Question Text (long)
G Urgency Low / Medium / High
H Status New / In Review / Answered / FAQ
I Assigned To Text
J Answer Text (long)
K Answered By Text
L Date Answered DateTime
M Notes Text (long)
N Promoted to FAQ Yes / No
O FAQ Entry ID Text

Automation (Apps Script):

  • On new row: Send email to Quality Dept distribution list
  • On Status → "Answered": Send reply email to submitter
  • Weekly Monday 8am: Send summary report (pending count, answered count, high urgency pending)

Algolia Index Schema

Index name: uad_kb_content

{
  "objectID": "string",
  "type": "article | faq | document",
  "title": "string",
  "summary": "string",
  "content": "string (truncated to 10000 chars)",
  "category": "string",
  "tags": ["string"],
  "url": "string",
  "updatedAt": "timestamp"
}

Searchable attributes (priority order):

  1. title
  2. tags
  3. summary
  4. content

Deployment Steps

1. Firebase Setup

npm install -g firebase-tools
firebase login
firebase init hosting
firebase init firestore

2. Environment Configuration

cp .env.local.example .env.local
# Fill in all values

3. Install Dependencies

npm install

4. Development

npm run dev
# Runs at http://localhost:3000

Connect the GitHub repo at vercel.com — auto-deploys on every push to main.

6. Configure Custom Domain

In Vercel → Settings → Domains → add kb.yourcompany.com. Add the CNAME record provided to your DNS registrar.


Performance Targets

Metric Target
Page load (home) < 1.5s
Search results < 200ms
Article load < 800ms
Drive sync < 60s for new files
Q&A email notification < 30s from submission

Monitoring & Maintenance

  • Firebase Console: Database usage, auth users, hosting bandwidth
  • Algolia Dashboard: Search analytics, zero-results queries
  • Google Sheets: Q&A log review (weekly by Quality Dept)
  • Vercel: Deployment logs, function errors

For content management instructions, see the Content Manager Guide.
For project overview, see the Project Roadmap.