✨ Major Features Added: - AI Chat with conversation memory and university-specific knowledge base - Multi-tenant university support with white-label capabilities - Professional admin interface for knowledge base management - Advanced database schema with Prisma ORM - Comprehensive documentation and guides - Modern Next.js 15 + React 19 architecture - Bilingual support (English/Arabic) - Role-based access control - Real-time chat interface with loading states 🔧 Technical Improvements: - Fixed all linter errors and TypeScript issues - Cleaned up codebase and removed legacy files - Added comprehensive .gitignore - Updated README with detailed setup instructions - Optimized database schema and migrations - Enhanced error handling and user experience 📚 Documentation: - AI Conversation Memory Guide - AI Enhancement Summary - Developer Guide - User Guide - Complete setup and deployment instructions 🚀 Ready for GitHub deployment and production use!
818 lines
17 KiB
Markdown
818 lines
17 KiB
Markdown
# White-Label University Portal - Developer Guide
|
|
|
|
## 📚 Table of Contents
|
|
|
|
1. [Architecture Overview](#architecture-overview)
|
|
2. [Getting Started](#getting-started)
|
|
3. [API Documentation](#api-documentation)
|
|
4. [Database Schema](#database-schema)
|
|
5. [Multi-Tenant Architecture](#multi-tenant-architecture)
|
|
6. [Performance Optimization](#performance-optimization)
|
|
7. [Deployment Guide](#deployment-guide)
|
|
8. [Testing](#testing)
|
|
9. [Contributing](#contributing)
|
|
10. [Troubleshooting](#troubleshooting)
|
|
|
|
---
|
|
|
|
## 🏗️ Architecture Overview
|
|
|
|
### Technology Stack
|
|
|
|
- **Frontend**: Next.js 14, React 19, TypeScript, Tailwind CSS
|
|
- **Backend**: Next.js API Routes, Prisma ORM
|
|
- **Database**: PostgreSQL (primary), Redis (caching)
|
|
- **AI**: OpenRouter AI integration
|
|
- **CDN**: Multi-provider support (AWS S3, Cloudflare, Cloudinary)
|
|
- **Deployment**: Vercel, Docker, Kubernetes
|
|
|
|
### System Architecture
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ Frontend │ │ API Layer │ │ Database │
|
|
│ (Next.js) │◄──►│ (Next.js) │◄──►│ (PostgreSQL) │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
│ │ │
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ CDN Layer │ │ Cache Layer │ │ AI Services │
|
|
│ (Multi-CDN) │ │ (Redis) │ │ (OpenRouter) │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### Multi-Tenant Design
|
|
|
|
The platform uses a **database-per-tenant** approach with shared application code:
|
|
|
|
- **Data Isolation**: Each university's data is completely isolated
|
|
- **Shared Infrastructure**: Common codebase and infrastructure
|
|
- **Dynamic Configuration**: University-specific settings and branding
|
|
- **Scalable Architecture**: Horizontal scaling support
|
|
|
|
---
|
|
|
|
## 🚀 Getting Started
|
|
|
|
### Prerequisites
|
|
|
|
- **Node.js**: 18.x or higher
|
|
- **PostgreSQL**: 14.x or higher
|
|
- **Redis**: 6.x or higher
|
|
- **Git**: Latest version
|
|
- **Docker**: (optional, for containerized development)
|
|
|
|
### Installation
|
|
|
|
1. **Clone the Repository**
|
|
```bash
|
|
git clone https://github.com/your-org/university-portal.git
|
|
cd university-portal
|
|
```
|
|
|
|
2. **Install Dependencies**
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
3. **Environment Setup**
|
|
```bash
|
|
cp .env.example .env.local
|
|
# Edit .env.local with your configuration
|
|
```
|
|
|
|
4. **Database Setup**
|
|
```bash
|
|
npx prisma generate
|
|
npx prisma db push
|
|
npx prisma db seed
|
|
```
|
|
|
|
5. **Start Development Server**
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
```env
|
|
# Database
|
|
DATABASE_URL="postgresql://user:password@localhost:5432/university_portal"
|
|
|
|
# Redis
|
|
REDIS_HOST="localhost"
|
|
REDIS_PORT="6379"
|
|
REDIS_PASSWORD=""
|
|
REDIS_DB="0"
|
|
|
|
# AI Services
|
|
OPENROUTER_API_KEY="your-api-key"
|
|
OPENROUTER_BASE_URL="https://openrouter.ai/api/v1"
|
|
|
|
# CDN Configuration
|
|
AWS_ACCESS_KEY_ID="your-access-key"
|
|
AWS_SECRET_ACCESS_KEY="your-secret-key"
|
|
AWS_REGION="us-east-1"
|
|
AWS_S3_BUCKET="your-bucket"
|
|
|
|
# Application
|
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
NEXTAUTH_SECRET="your-secret"
|
|
NEXTAUTH_URL="http://localhost:3000"
|
|
```
|
|
|
|
---
|
|
|
|
## 📡 API Documentation
|
|
|
|
### Authentication
|
|
|
|
All API endpoints require university context via headers:
|
|
|
|
```http
|
|
X-University-Id: university-id
|
|
X-University-Slug: university-slug
|
|
```
|
|
|
|
### Core Endpoints
|
|
|
|
#### Universities
|
|
|
|
```http
|
|
GET /api/universities
|
|
POST /api/universities
|
|
GET /api/universities/[slug]
|
|
PUT /api/universities/[slug]
|
|
DELETE /api/universities/[slug]
|
|
```
|
|
|
|
#### Content Management
|
|
|
|
```http
|
|
GET /api/content
|
|
POST /api/content
|
|
GET /api/content/[id]
|
|
PUT /api/content/[id]
|
|
DELETE /api/content/[id]
|
|
```
|
|
|
|
#### Programs
|
|
|
|
```http
|
|
GET /api/programs
|
|
POST /api/programs
|
|
GET /api/programs/[id]
|
|
PUT /api/programs/[id]
|
|
DELETE /api/programs/[id]
|
|
```
|
|
|
|
#### Domains
|
|
|
|
```http
|
|
GET /api/domains
|
|
POST /api/domains
|
|
GET /api/domains/[id]
|
|
PUT /api/domains/[id]
|
|
DELETE /api/domains/[id]
|
|
POST /api/domains/[id]/validate
|
|
POST /api/domains/[id]/renew-ssl
|
|
```
|
|
|
|
#### Deployments
|
|
|
|
```http
|
|
GET /api/deployments
|
|
POST /api/deployments
|
|
GET /api/deployments/[id]
|
|
POST /api/deployments/[id]/execute
|
|
POST /api/deployments/[id]/rollback
|
|
```
|
|
|
|
### Response Format
|
|
|
|
All API responses follow a consistent format:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
// Response data
|
|
},
|
|
"message": "Operation completed successfully",
|
|
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Error message",
|
|
"code": "ERROR_CODE",
|
|
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🗄️ Database Schema
|
|
|
|
### Core Models
|
|
|
|
#### University
|
|
```prisma
|
|
model University {
|
|
id String @id @default(uuid())
|
|
slug String @unique
|
|
name String
|
|
shortName String?
|
|
domain String?
|
|
subdomain String?
|
|
branding Json
|
|
contact Json
|
|
features Json
|
|
ai Json
|
|
status UniversityStatus @default(ACTIVE)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
assets UniversityAsset[]
|
|
content UniversityContent[]
|
|
programs AcademicProgram[]
|
|
knowledge AIKnowledgeBase[]
|
|
domains DomainConfig[]
|
|
deployments DeploymentConfig[]
|
|
cdnConfig CDNConfig?
|
|
cdnAssets CDNAsset[]
|
|
}
|
|
```
|
|
|
|
#### UniversityContent
|
|
```prisma
|
|
model UniversityContent {
|
|
id String @id @default(uuid())
|
|
universityId String
|
|
title String
|
|
content String
|
|
contentType ContentType
|
|
language Language @default(ENGLISH)
|
|
isPublished Boolean @default(false)
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
university University @relation(fields: [universityId], references: [id], onDelete: Cascade)
|
|
}
|
|
```
|
|
|
|
#### AcademicProgram
|
|
```prisma
|
|
model AcademicProgram {
|
|
id String @id @default(uuid())
|
|
universityId String
|
|
title String
|
|
description String
|
|
level ProgramLevel
|
|
duration String
|
|
fees Json?
|
|
requirements Json?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
university University @relation(fields: [universityId], references: [id], onDelete: Cascade)
|
|
}
|
|
```
|
|
|
|
### Domain Management
|
|
|
|
#### DomainConfig
|
|
```prisma
|
|
model DomainConfig {
|
|
id String @id @default(uuid())
|
|
universityId String
|
|
type DomainType
|
|
domain String
|
|
subdomain String?
|
|
sslStatus SSLStatus @default(PENDING)
|
|
sslExpiryDate DateTime?
|
|
dnsStatus DNSStatus @default(PENDING)
|
|
isActive Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
university University @relation(fields: [universityId], references: [id], onDelete: Cascade)
|
|
sslConfig SSLConfig?
|
|
dnsConfig DNSConfig?
|
|
analytics DomainAnalytics[]
|
|
}
|
|
```
|
|
|
|
### Enums
|
|
|
|
```prisma
|
|
enum UniversityStatus {
|
|
ACTIVE
|
|
INACTIVE
|
|
SUSPENDED
|
|
}
|
|
|
|
enum ContentType {
|
|
ABOUT
|
|
NEWS
|
|
EVENTS
|
|
RESEARCH
|
|
CAMPUS
|
|
}
|
|
|
|
enum Language {
|
|
ENGLISH
|
|
ARABIC
|
|
}
|
|
|
|
enum ProgramLevel {
|
|
UNDERGRADUATE
|
|
POSTGRADUATE
|
|
PHD
|
|
}
|
|
|
|
enum DomainType {
|
|
SUBDOMAIN
|
|
CUSTOM_DOMAIN
|
|
}
|
|
|
|
enum SSLStatus {
|
|
PENDING
|
|
ACTIVE
|
|
EXPIRED
|
|
ERROR
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🏢 Multi-Tenant Architecture
|
|
|
|
### Data Isolation
|
|
|
|
The platform ensures complete data isolation between universities:
|
|
|
|
```typescript
|
|
// Data isolation middleware
|
|
export async function validateUniversityAccess(request: NextRequest): Promise<UniversityContext> {
|
|
const university = getUniversityFromHeaders(request);
|
|
|
|
if (!university) {
|
|
throw new Error('University context required');
|
|
}
|
|
|
|
// Verify university exists and is active
|
|
const dbUniversity = await prisma.university.findUnique({
|
|
where: { id: university.id, status: 'ACTIVE' },
|
|
});
|
|
|
|
if (!dbUniversity) {
|
|
throw new Error('University not found or inactive');
|
|
}
|
|
|
|
return university;
|
|
}
|
|
```
|
|
|
|
### University Context
|
|
|
|
```typescript
|
|
// University provider for React components
|
|
export function UniversityProvider({ children, initialUniversity }: UniversityProviderProps) {
|
|
const [university, setUniversity] = useState<University | null>(initialUniversity);
|
|
|
|
// Load university data based on cookies or headers
|
|
const loadUniversity = async () => {
|
|
const response = await fetch('/api/universities/current');
|
|
const data = await response.json();
|
|
setUniversity(data);
|
|
};
|
|
|
|
return (
|
|
<UniversityContext.Provider value={{ university, setUniversity }}>
|
|
{children}
|
|
</UniversityContext.Provider>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Dynamic Branding
|
|
|
|
```typescript
|
|
// Dynamic branding component
|
|
export function DynamicBranding() {
|
|
const { university } = useUniversity();
|
|
|
|
if (!university) return null;
|
|
|
|
const { branding } = university;
|
|
|
|
return (
|
|
<div style={{
|
|
'--primary-color': branding.primaryColor,
|
|
'--secondary-color': branding.secondaryColor,
|
|
} as React.CSSProperties}>
|
|
<img src={branding.logo} alt={university.name} />
|
|
<h1>{university.name}</h1>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ⚡ Performance Optimization
|
|
|
|
### Caching Strategy
|
|
|
|
```typescript
|
|
// Redis caching implementation
|
|
export class CacheManager {
|
|
async getUniversity(slug: string): Promise<any> {
|
|
const cacheKey = `university:${slug}`;
|
|
|
|
// Try cache first
|
|
const cached = await this.client.get(cacheKey);
|
|
if (cached) {
|
|
return JSON.parse(cached);
|
|
}
|
|
|
|
// Query database
|
|
const university = await prisma.university.findUnique({
|
|
where: { slug },
|
|
include: { assets: true, programs: true }
|
|
});
|
|
|
|
// Cache result
|
|
if (university) {
|
|
await this.client.setEx(cacheKey, 1800, JSON.stringify(university));
|
|
}
|
|
|
|
return university;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Query Optimization
|
|
|
|
```typescript
|
|
// Optimized database queries
|
|
export class QueryOptimizer {
|
|
async getPrograms(universityId: string, options: any) {
|
|
const { page = 1, limit = 10, search } = options;
|
|
const skip = (page - 1) * limit;
|
|
|
|
const where = {
|
|
universityId,
|
|
isActive: true,
|
|
...(search && {
|
|
OR: [
|
|
{ title: { contains: search, mode: 'insensitive' } },
|
|
{ description: { contains: search, mode: 'insensitive' } },
|
|
],
|
|
}),
|
|
};
|
|
|
|
// Parallel queries for better performance
|
|
const [programs, total] = await Promise.all([
|
|
prisma.academicProgram.findMany({ where, skip, take: limit }),
|
|
prisma.academicProgram.count({ where }),
|
|
]);
|
|
|
|
return { programs, total, page, totalPages: Math.ceil(total / limit) };
|
|
}
|
|
}
|
|
```
|
|
|
|
### CDN Integration
|
|
|
|
```typescript
|
|
// Multi-provider CDN support
|
|
export class CDNManager {
|
|
async uploadAsset(file: File, path: string): Promise<CDNAsset> {
|
|
const config = await this.getCDNConfig();
|
|
|
|
let cdnUrl: string;
|
|
|
|
switch (config.provider) {
|
|
case 'AWS_S3':
|
|
cdnUrl = await this.uploadToS3(file, path, config);
|
|
break;
|
|
case 'CLOUDFLARE':
|
|
cdnUrl = await this.uploadToCloudflare(file, path, config);
|
|
break;
|
|
case 'CLOUDINARY':
|
|
cdnUrl = await this.uploadToCloudinary(file, path, config);
|
|
break;
|
|
default:
|
|
throw new Error('Unsupported CDN provider');
|
|
}
|
|
|
|
return { originalPath: path, cdnUrl, optimizedUrls: {} };
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Deployment Guide
|
|
|
|
### Environment Setup
|
|
|
|
1. **Production Environment**
|
|
```bash
|
|
# Set production environment variables
|
|
export NODE_ENV=production
|
|
export DATABASE_URL="postgresql://..."
|
|
export REDIS_URL="redis://..."
|
|
```
|
|
|
|
2. **Database Migration**
|
|
```bash
|
|
npx prisma migrate deploy
|
|
npx prisma generate
|
|
```
|
|
|
|
3. **Build Application**
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
### Docker Deployment
|
|
|
|
```dockerfile
|
|
# Dockerfile
|
|
FROM node:18-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
COPY package*.json ./
|
|
RUN npm ci --only=production
|
|
|
|
COPY . .
|
|
RUN npm run build
|
|
|
|
EXPOSE 3000
|
|
|
|
CMD ["npm", "start"]
|
|
```
|
|
|
|
```yaml
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
services:
|
|
app:
|
|
build: .
|
|
ports:
|
|
- "3000:3000"
|
|
environment:
|
|
- DATABASE_URL=${DATABASE_URL}
|
|
- REDIS_URL=${REDIS_URL}
|
|
depends_on:
|
|
- postgres
|
|
- redis
|
|
|
|
postgres:
|
|
image: postgres:14
|
|
environment:
|
|
POSTGRES_DB: university_portal
|
|
POSTGRES_USER: ${DB_USER}
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
|
|
redis:
|
|
image: redis:6-alpine
|
|
```
|
|
|
|
### Vercel Deployment
|
|
|
|
1. **Connect Repository**
|
|
- Link your GitHub repository to Vercel
|
|
- Configure environment variables
|
|
|
|
2. **Deploy**
|
|
```bash
|
|
vercel --prod
|
|
```
|
|
|
|
3. **Custom Domain**
|
|
- Add custom domain in Vercel dashboard
|
|
- Configure DNS records
|
|
|
|
---
|
|
|
|
## 🧪 Testing
|
|
|
|
### Unit Tests
|
|
|
|
```typescript
|
|
// Example unit test
|
|
import { describe, it, expect } from 'vitest';
|
|
import { CacheManager } from '../lib/cache';
|
|
|
|
describe('CacheManager', () => {
|
|
it('should cache and retrieve university data', async () => {
|
|
const cache = new CacheManager(config);
|
|
const university = { id: '1', name: 'Test University' };
|
|
|
|
await cache.set('university:test', university);
|
|
const retrieved = await cache.get('university:test');
|
|
|
|
expect(retrieved).toEqual(university);
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
```typescript
|
|
// Example integration test
|
|
import { describe, it, expect } from 'vitest';
|
|
import { createMocks } from 'node-mocks-http';
|
|
import handler from '../pages/api/universities';
|
|
|
|
describe('/api/universities', () => {
|
|
it('should return universities list', async () => {
|
|
const { req, res } = createMocks({
|
|
method: 'GET',
|
|
headers: { 'x-university-id': 'test-university' },
|
|
});
|
|
|
|
await handler(req, res);
|
|
|
|
expect(res._getStatusCode()).toBe(200);
|
|
expect(JSON.parse(res._getData())).toHaveProperty('data');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Load Testing
|
|
|
|
```typescript
|
|
// Example load test
|
|
import { LoadTester } from '../lib/loadTesting';
|
|
|
|
const config = {
|
|
name: 'API Load Test',
|
|
duration: 300,
|
|
users: 50,
|
|
targetRPS: 10,
|
|
scenarios: [
|
|
{
|
|
name: 'Get Universities',
|
|
weight: 100,
|
|
requests: [
|
|
{
|
|
method: 'GET',
|
|
url: '/api/universities',
|
|
expectedStatus: 200,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const tester = new LoadTester(config);
|
|
const results = await tester.run();
|
|
```
|
|
|
|
---
|
|
|
|
## 🤝 Contributing
|
|
|
|
### Development Workflow
|
|
|
|
1. **Fork the Repository**
|
|
```bash
|
|
git clone https://github.com/your-username/university-portal.git
|
|
cd university-portal
|
|
```
|
|
|
|
2. **Create Feature Branch**
|
|
```bash
|
|
git checkout -b feature/your-feature-name
|
|
```
|
|
|
|
3. **Make Changes**
|
|
- Follow the coding standards
|
|
- Add tests for new features
|
|
- Update documentation
|
|
|
|
4. **Submit Pull Request**
|
|
```bash
|
|
git push origin feature/your-feature-name
|
|
# Create PR on GitHub
|
|
```
|
|
|
|
### Coding Standards
|
|
|
|
- **TypeScript**: Strict mode enabled
|
|
- **ESLint**: Airbnb configuration
|
|
- **Prettier**: Consistent formatting
|
|
- **Conventional Commits**: Standard commit messages
|
|
|
|
### Code Review Process
|
|
|
|
1. **Automated Checks**
|
|
- Linting and formatting
|
|
- Type checking
|
|
- Unit tests
|
|
- Integration tests
|
|
|
|
2. **Manual Review**
|
|
- Code quality review
|
|
- Security review
|
|
- Performance review
|
|
|
|
3. **Approval**
|
|
- At least 2 approvals required
|
|
- All checks must pass
|
|
|
|
---
|
|
|
|
## 🔧 Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Database Connection**
|
|
```bash
|
|
# Check database connection
|
|
npx prisma db push
|
|
|
|
# Reset database
|
|
npx prisma migrate reset
|
|
```
|
|
|
|
2. **Redis Connection**
|
|
```bash
|
|
# Check Redis connection
|
|
redis-cli ping
|
|
|
|
# Clear cache
|
|
redis-cli flushall
|
|
```
|
|
|
|
3. **Build Issues**
|
|
```bash
|
|
# Clear Next.js cache
|
|
rm -rf .next
|
|
npm run build
|
|
```
|
|
|
|
### Debug Mode
|
|
|
|
```bash
|
|
# Enable debug logging
|
|
DEBUG=* npm run dev
|
|
|
|
# Database queries
|
|
DEBUG=prisma:query npm run dev
|
|
|
|
# Redis operations
|
|
DEBUG=redis npm run dev
|
|
```
|
|
|
|
### Performance Monitoring
|
|
|
|
```typescript
|
|
// Monitor query performance
|
|
const queryOptimizer = getQueryOptimizer();
|
|
const stats = queryOptimizer.getQueryStats();
|
|
console.log('Query Statistics:', stats);
|
|
|
|
// Monitor cache performance
|
|
const cache = getCacheInstance();
|
|
const cacheStats = await cache.getStats();
|
|
console.log('Cache Statistics:', cacheStats);
|
|
```
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
### Getting Help
|
|
|
|
- **Documentation**: This developer guide
|
|
- **Issues**: GitHub Issues
|
|
- **Discussions**: GitHub Discussions
|
|
- **Email**: dev-support@your-domain.com
|
|
|
|
### Resources
|
|
|
|
- **API Reference**: `/api/docs`
|
|
- **Database Schema**: `prisma/schema.prisma`
|
|
- **Component Library**: Storybook documentation
|
|
- **Performance Dashboard**: `/admin/performance`
|
|
|
|
---
|
|
|
|
**Last Updated**: January 2025
|
|
**Version**: 1.0
|
|
**Platform**: White-Label University Portal |