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 | 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):
titletagssummarycontent
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
5. Deploy to Vercel (Recommended)
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.