Remove obsolete test scripts and update admin username

- Deleted various test scripts related to due date reminders, edge compatibility, logged-in flow, logging, mobile view, NextAuth, notifications API, notifications working, project API, project creation, Radicale sync configuration, safe audit logging, task API, task sets, user tracking, and verification scripts.
- Removed the script for updating admin username from email to a simple "admin".
- Cleaned up unused PowerShell script for updating queries.
This commit is contained in:
2026-01-16 10:18:37 +01:00
parent ca618a7109
commit 1ac0c09ae2
79 changed files with 0 additions and 8551 deletions

View File

@@ -1,379 +0,0 @@
# Audit Logging Implementation
This document describes the audit logging system implemented for the panel application. The system provides comprehensive tracking of user actions and system events for security, compliance, and monitoring purposes.
## Features
- **Comprehensive Action Tracking**: Logs all CRUD operations on projects, tasks, contracts, notes, and user management
- **Authentication Events**: Tracks login attempts, successes, and failures
- **Detailed Context**: Captures IP addresses, user agents, and request details
- **Flexible Filtering**: Query logs by user, action, resource type, date range, and more
- **Statistics Dashboard**: Provides insights into system usage patterns
- **Role-based Access**: Only admins and project managers can view audit logs
- **Performance Optimized**: Uses database indexes for efficient querying
## Architecture
### Core Components
1. **Audit Log Utility** (`src/lib/auditLog.js`)
- Core logging functions
- Query and statistics functions
- Action and resource type constants
2. **API Endpoints** (`src/app/api/audit-logs/`)
- `/api/audit-logs` - Query audit logs with filtering
- `/api/audit-logs/stats` - Get audit log statistics
3. **UI Components** (`src/components/AuditLogViewer.js`)
- Interactive audit log viewer
- Advanced filtering interface
- Statistics dashboard
4. **Admin Pages** (`src/app/admin/audit-logs/`)
- Admin interface for viewing audit logs
- Role-based access control
### Database Schema
The audit logs are stored in the `audit_logs` table:
```sql
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT, -- User who performed the action
action TEXT NOT NULL, -- Action performed (see AUDIT_ACTIONS)
resource_type TEXT, -- Type of resource affected
resource_id TEXT, -- ID of the affected resource
ip_address TEXT, -- IP address of the user
user_agent TEXT, -- Browser/client information
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
details TEXT, -- Additional details (JSON)
FOREIGN KEY (user_id) REFERENCES users(id)
);
```
## Usage
### Basic Logging
```javascript
import { logAuditEvent, AUDIT_ACTIONS, RESOURCE_TYPES } from "@/lib/auditLog";
// Log a simple action
logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_CREATE,
userId: "user123",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "proj-456",
ipAddress: req.ip,
userAgent: req.headers["user-agent"],
details: {
project_name: "New Project",
project_number: "NP-001",
},
});
```
### API Route Integration
```javascript
import { logApiAction, AUDIT_ACTIONS, RESOURCE_TYPES } from "@/lib/auditLog";
export async function POST(req) {
const data = await req.json();
// Perform the operation
const result = createProject(data);
// Log the action
logApiAction(
req,
AUDIT_ACTIONS.PROJECT_CREATE,
RESOURCE_TYPES.PROJECT,
result.id.toString(),
req.session,
{ projectData: data }
);
return NextResponse.json({ success: true, id: result.id });
}
```
### Querying Audit Logs
```javascript
import { getAuditLogs, getAuditLogStats } from "@/lib/auditLog";
// Get recent logs
const recentLogs = getAuditLogs({
limit: 50,
orderBy: "timestamp",
orderDirection: "DESC",
});
// Get logs for a specific user
const userLogs = getAuditLogs({
userId: "user123",
startDate: "2025-01-01T00:00:00Z",
endDate: "2025-12-31T23:59:59Z",
});
// Get statistics
const stats = getAuditLogStats({
startDate: "2025-01-01T00:00:00Z",
endDate: "2025-12-31T23:59:59Z",
});
```
## Available Actions
### Authentication Actions
- `login` - Successful user login
- `logout` - User logout
- `login_failed` - Failed login attempt
### Project Actions
- `project_create` - Project creation
- `project_update` - Project modification
- `project_delete` - Project deletion
- `project_view` - Project viewing
### Task Actions
- `task_create` - Task creation
- `task_update` - Task modification
- `task_delete` - Task deletion
- `task_status_change` - Task status modification
### Project Task Actions
- `project_task_create` - Project task assignment
- `project_task_update` - Project task modification
- `project_task_delete` - Project task removal
- `project_task_status_change` - Project task status change
### Contract Actions
- `contract_create` - Contract creation
- `contract_update` - Contract modification
- `contract_delete` - Contract deletion
### Note Actions
- `note_create` - Note creation
- `note_update` - Note modification
- `note_delete` - Note deletion
### Admin Actions
- `user_create` - User account creation
- `user_update` - User account modification
- `user_delete` - User account deletion
- `user_role_change` - User role modification
### System Actions
- `data_export` - Data export operations
- `bulk_operation` - Bulk data operations
## Resource Types
- `project` - Project resources
- `task` - Task templates
- `project_task` - Project-specific tasks
- `contract` - Contracts
- `note` - Notes and comments
- `user` - User accounts
- `session` - Authentication sessions
- `system` - System-level operations
## API Endpoints
### GET /api/audit-logs
Query audit logs with optional filtering.
**Query Parameters:**
- `userId` - Filter by user ID
- `action` - Filter by action type
- `resourceType` - Filter by resource type
- `resourceId` - Filter by resource ID
- `startDate` - Filter from date (ISO string)
- `endDate` - Filter to date (ISO string)
- `limit` - Maximum results (default: 100)
- `offset` - Results offset (default: 0)
- `orderBy` - Order by field (default: timestamp)
- `orderDirection` - ASC or DESC (default: DESC)
- `includeStats` - Include statistics (true/false)
**Response:**
```json
{
"success": true,
"data": [
{
"id": 1,
"user_id": "user123",
"user_name": "John Doe",
"user_email": "john@example.com",
"action": "project_create",
"resource_type": "project",
"resource_id": "proj-456",
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"timestamp": "2025-07-09T10:30:00Z",
"details": {
"project_name": "New Project",
"project_number": "NP-001"
}
}
],
"stats": {
"total": 150,
"actionBreakdown": [...],
"userBreakdown": [...],
"resourceBreakdown": [...]
}
}
```
### GET /api/audit-logs/stats
Get audit log statistics.
**Query Parameters:**
- `startDate` - Filter from date (ISO string)
- `endDate` - Filter to date (ISO string)
**Response:**
```json
{
"success": true,
"data": {
"total": 150,
"actionBreakdown": [
{ "action": "project_view", "count": 45 },
{ "action": "login", "count": 23 }
],
"userBreakdown": [
{ "user_id": "user123", "user_name": "John Doe", "count": 67 }
],
"resourceBreakdown": [{ "resource_type": "project", "count": 89 }]
}
}
```
## Access Control
Audit logs are restricted to users with the following roles:
- `admin` - Full access to all audit logs
- `project_manager` - Full access to all audit logs
Other users cannot access audit logs.
## Testing
Run the audit logging test script:
```bash
node test-audit-logging.mjs
```
This will:
1. Create sample audit events
2. Test querying and filtering
3. Verify statistics generation
4. Test date range filtering
## Integration Status
The audit logging system has been integrated into the following API routes:
**Authentication** (`src/lib/auth.js`)
- Login success/failure tracking
- Account lockout logging
**Projects** (`src/app/api/projects/`)
- Project CRUD operations
- List view access
**Notes** (`src/app/api/notes/`)
- Note creation, updates, and deletion
🔄 **Pending Integration:**
- Tasks API
- Project Tasks API
- Contracts API
- User management API
## Performance Considerations
- Database indexes are created on frequently queried fields
- Large result sets are paginated
- Statistics queries are optimized for common use cases
- Failed operations are logged to prevent data loss
## Security Features
- IP address tracking for forensic analysis
- User agent logging for client identification
- Failed authentication attempt tracking
- Detailed change logging for sensitive operations
- Role-based access control for audit log viewing
## Maintenance
### Log Retention
Consider implementing log retention policies:
```sql
-- Delete audit logs older than 1 year
DELETE FROM audit_logs
WHERE timestamp < datetime('now', '-1 year');
```
### Monitoring
Monitor audit log growth and performance:
```sql
-- Check audit log table size
SELECT COUNT(*) as total_logs,
MIN(timestamp) as oldest_log,
MAX(timestamp) as newest_log
FROM audit_logs;
-- Check most active users
SELECT user_id, COUNT(*) as activity_count
FROM audit_logs
WHERE timestamp > datetime('now', '-30 days')
GROUP BY user_id
ORDER BY activity_count DESC
LIMIT 10;
```
## Future Enhancements
- Real-time audit log streaming
- Advanced analytics and reporting
- Integration with external SIEM systems
- Automatic anomaly detection
- Compliance reporting templates
- Log export functionality

View File

@@ -1,971 +0,0 @@
# Authorization Implementation Guide
## Project Overview
This document outlines the implementation strategy for adding authentication and authorization to the Project Management Panel - a Next.js 15 application with SQLite database.
## Current State Analysis (Updated: June 25, 2025)
### ✅ What We Have Implemented
- **Framework**: Next.js 15 with App Router
- **Database**: SQLite with better-sqlite3
- **Authentication**: NextAuth.js v5 with credentials provider
- **User Management**: Complete user CRUD operations with bcrypt password hashing
- **Database Schema**: Users table with roles, audit logs, sessions
- **API Protection**: Middleware system with role-based access control
- **Session Management**: JWT-based sessions with 30-day expiration
- **Security Features**: Account lockout, failed login tracking, password validation
- **UI Components**: Authentication provider, navigation with user context
- **Auth Pages**: Sign-in page implemented
### ✅ What's Protected
- **API Routes**: All major endpoints (projects, contracts, tasks, notes) are protected
- **Role Hierarchy**: admin > project_manager > user > read_only
- **Navigation**: Role-based menu items (admin sees user management)
- **Session Security**: Automatic session management and validation
### 🔄 Partially Implemented
- **Auth Pages**: Sign-in exists, missing sign-out and error pages
- **User Interface**: Basic auth integration, could use more polish
- **Admin Features**: User management backend exists, UI needs completion
- **Audit Logging**: Database schema exists, not fully integrated
### ❌ Still Missing
- Complete user management UI for admins
- Password reset functionality
- Rate limiting implementation
- Enhanced input validation schemas
- CSRF protection
- Security headers middleware
- Comprehensive error handling
- Email notifications
## Recommended Implementation Strategy
### 1. Authentication Solution: NextAuth.js
**Why NextAuth.js?**
- ✅ Native Next.js 15 App Router support
- ✅ Database session management
- ✅ Built-in security features (CSRF, JWT handling)
- ✅ Flexible provider system
- ✅ SQLite adapter available
### 2. Role-Based Access Control (RBAC)
**Proposed User Roles:**
| Role | Permissions | Use Case |
| ------------------- | --------------------------------------- | ----------------------- |
| **Admin** | Full system access, user management | System administrators |
| **Project Manager** | Manage all projects/tasks, view reports | Team leads, supervisors |
| **User** | View/edit assigned projects/tasks | Regular employees |
| **Read-only** | View-only access to data | Clients, stakeholders |
## Implementation Status
### ✅ Phase 1: Foundation Setup - COMPLETED
#### 1.1 Dependencies - ✅ INSTALLED
- NextAuth.js v5 (beta)
- bcryptjs for password hashing
- Zod for validation
- Better-sqlite3 adapter compatibility
#### 1.2 Environment Configuration - ✅ COMPLETED
- `.env.local` configured with NEXTAUTH_SECRET and NEXTAUTH_URL
- Database URL configuration
- Development environment setup
#### 1.3 Database Schema - ✅ IMPLEMENTED
- Users table with roles and security features
- Sessions table for NextAuth.js
- Audit logs table for security tracking
- Proper indexes for performance
#### 1.4 Initial Admin User - ✅ COMPLETED
- `scripts/create-admin.js` script available
- Default admin user: admin@localhost.com / admin123456
### ✅ Phase 2: Authentication Core - COMPLETED
#### 2.1 NextAuth.js Configuration - ✅ IMPLEMENTED
- **File**: `src/lib/auth.js`
- Credentials provider with email/password
- JWT session strategy with 30-day expiration
- Account lockout after 5 failed attempts (15-minute lockout)
- Password verification with bcrypt
- Failed login attempt tracking
- Session callbacks for role management
#### 2.2 API Route Handlers - ✅ IMPLEMENTED
- **File**: `src/app/api/auth/[...nextauth]/route.js`
- NextAuth.js handlers properly configured
#### 2.3 User Management System - ✅ IMPLEMENTED
- **File**: `src/lib/userManagement.js`
- Complete CRUD operations for users
- Password hashing and validation
- Role management functions
- User lookup by ID and email
### ✅ Phase 3: Authorization Middleware - COMPLETED
#### 3.1 API Protection Middleware - ✅ IMPLEMENTED
- **File**: `src/lib/middleware/auth.js`
- `withAuth()` function for protecting routes
- Role hierarchy enforcement (admin=4, project_manager=3, user=2, read_only=1)
- Helper functions: `withReadAuth`, `withUserAuth`, `withAdminAuth`, `withManagerAuth`
- Proper error handling and status codes
#### 3.2 Protected API Routes - ✅ IMPLEMENTED
Example in `src/app/api/projects/route.js`:
- GET requests require read_only access
- POST requests require user access
- All major API endpoints are protected
#### 3.3 Session Provider - ✅ IMPLEMENTED
- **File**: `src/components/auth/AuthProvider.js`
- NextAuth SessionProvider wrapper
- Integrated into root layout
### 🔄 Phase 4: User Interface - PARTIALLY COMPLETED
#### 4.1 Authentication Pages - 🔄 PARTIAL
-**Sign-in page**: `src/app/auth/signin/page.js` - Complete with form validation
-**Sign-out page**: Missing
- 🔄 **Error page**: `src/app/auth/error/page.js` - Basic implementation
-**Unauthorized page**: Missing
#### 4.2 Navigation Updates - ✅ COMPLETED
- **File**: `src/components/ui/Navigation.js`
- User session integration with useSession
- Role-based menu items (admin sees user management)
- Sign-out functionality
- Conditional rendering based on auth status
#### 4.3 User Management Interface - ❌ MISSING
- Backend exists in userManagement.js
- Admin UI for user CRUD operations needed
- Role assignment interface needed
### ❌ Phase 5: Security Enhancements - NOT STARTED
#### 5.1 Input Validation Schemas - ❌ MISSING
- Zod schemas for API endpoints
- Request validation middleware
#### 5.2 Rate Limiting - ❌ MISSING
- Rate limiting middleware
- IP-based request tracking
#### 5.3 Security Headers - ❌ MISSING
- CSRF protection
- Security headers middleware
- Content Security Policy
```javascript
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { BetterSQLite3Adapter } from "@auth/better-sqlite3-adapter";
import db from "./db.js";
import bcrypt from "bcryptjs";
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email("Invalid email format"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: BetterSQLite3Adapter(db),
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // 24 hours
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
try {
// Validate input
const validatedFields = loginSchema.parse(credentials);
// Check if user exists and is active
const user = db
.prepare(
`
SELECT id, email, name, password_hash, role, is_active,
failed_login_attempts, locked_until
FROM users
WHERE email = ? AND is_active = 1
`
)
.get(validatedFields.email);
if (!user) {
throw new Error("Invalid credentials");
}
// Check if account is locked
if (user.locked_until && new Date(user.locked_until) > new Date()) {
throw new Error("Account temporarily locked");
}
// Verify password
const isValidPassword = await bcrypt.compare(
validatedFields.password,
user.password_hash
);
if (!isValidPassword) {
// Increment failed attempts
db.prepare(
`
UPDATE users
SET failed_login_attempts = failed_login_attempts + 1,
locked_until = CASE
WHEN failed_login_attempts >= 4
THEN datetime('now', '+15 minutes')
ELSE locked_until
END
WHERE id = ?
`
).run(user.id);
throw new Error("Invalid credentials");
}
// Reset failed attempts and update last login
db.prepare(
`
UPDATE users
SET failed_login_attempts = 0,
locked_until = NULL,
last_login = CURRENT_TIMESTAMP
WHERE id = ?
`
).run(user.id);
// Log successful login
logAuditEvent(user.id, "LOGIN_SUCCESS", "user", user.id, req);
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
} catch (error) {
console.error("Login error:", error);
return null;
}
},
}),
],
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.role = user.role;
token.userId = user.id;
}
return token;
},
async session({ session, token, user }) {
if (token) {
session.user.id = token.userId || token.sub;
session.user.role = token.role || user?.role;
}
return session;
},
async signIn({ user, account, profile, email, credentials }) {
// Additional sign-in logic if needed
return true;
},
},
pages: {
signIn: "/auth/signin",
signOut: "/auth/signout",
error: "/auth/error",
},
events: {
async signOut({ session, token }) {
if (session?.user?.id) {
logAuditEvent(session.user.id, "LOGOUT", "user", session.user.id);
}
},
},
});
// Audit logging helper
function logAuditEvent(userId, action, resourceType, resourceId, req = null) {
try {
db.prepare(
`
INSERT INTO audit_logs (user_id, action, resource_type, resource_id, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?)
`
).run(
userId,
action,
resourceType,
resourceId,
req?.ip || "unknown",
req?.headers?.["user-agent"] || "unknown"
);
} catch (error) {
console.error("Audit log error:", error);
}
}
```
#### 2.2 API Route Handlers
Create `src/app/api/auth/[...nextauth]/route.js`:
```javascript
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;
```
### Phase 3: Authorization Middleware
#### 3.1 API Protection Middleware
Create `src/lib/middleware/auth.js`:
```javascript
import { auth } from "@/lib/auth";
import { NextResponse } from "next/server";
import { z } from "zod";
// Role hierarchy for permission checking
const ROLE_HIERARCHY = {
admin: 4,
project_manager: 3,
user: 2,
read_only: 1,
};
export function withAuth(handler, options = {}) {
return async (req, context) => {
try {
const session = await auth();
// Check if user is authenticated
if (!session?.user) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
);
}
// Check if user account is active
const user = db
.prepare("SELECT is_active FROM users WHERE id = ?")
.get(session.user.id);
if (!user?.is_active) {
return NextResponse.json(
{ error: "Account deactivated" },
{ status: 403 }
);
}
// Check role-based permissions
if (
options.requiredRole &&
!hasPermission(session.user.role, options.requiredRole)
) {
logAuditEvent(
session.user.id,
"ACCESS_DENIED",
options.resource || "api",
req.url
);
return NextResponse.json(
{ error: "Insufficient permissions" },
{ status: 403 }
);
}
// Check resource-specific permissions
if (options.checkResourceAccess) {
const hasAccess = await options.checkResourceAccess(
session.user,
context.params
);
if (!hasAccess) {
return NextResponse.json(
{ error: "Access denied to this resource" },
{ status: 403 }
);
}
}
// Validate request body if schema provided
if (
options.bodySchema &&
(req.method === "POST" ||
req.method === "PUT" ||
req.method === "PATCH")
) {
try {
const body = await req.json();
options.bodySchema.parse(body);
} catch (error) {
return NextResponse.json(
{ error: "Invalid request data", details: error.errors },
{ status: 400 }
);
}
}
// Add user info to request
req.user = session.user;
req.session = session;
// Call the original handler
return await handler(req, context);
} catch (error) {
console.error("Auth middleware error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
};
}
export function hasPermission(userRole, requiredRole) {
return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];
}
// Helper for read-only operations
export function withReadAuth(handler) {
return withAuth(handler, { requiredRole: "read_only" });
}
// Helper for user-level operations
export function withUserAuth(handler) {
return withAuth(handler, { requiredRole: "user" });
}
// Helper for project manager operations
export function withManagerAuth(handler) {
return withAuth(handler, { requiredRole: "project_manager" });
}
// Helper for admin operations
export function withAdminAuth(handler) {
return withAuth(handler, { requiredRole: "admin" });
}
```
#### 3.2 Client-Side Route Protection
Create `src/components/auth/ProtectedRoute.js`:
```javascript
"use client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export function ProtectedRoute({
children,
requiredRole = null,
fallback = null,
}) {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === "loading") return; // Still loading
if (!session) {
router.push("/auth/signin");
return;
}
if (requiredRole && !hasPermission(session.user.role, requiredRole)) {
router.push("/unauthorized");
return;
}
}, [session, status, router, requiredRole]);
if (status === "loading") {
return (
<div className="flex justify-center items-center h-64">Loading...</div>
);
}
if (!session) {
return fallback || <div>Redirecting to login...</div>;
}
if (requiredRole && !hasPermission(session.user.role, requiredRole)) {
return fallback || <div>Access denied</div>;
}
return children;
}
function hasPermission(userRole, requiredRole) {
const roleHierarchy = {
admin: 4,
project_manager: 3,
user: 2,
read_only: 1,
};
return roleHierarchy[userRole] >= roleHierarchy[requiredRole];
}
```
### Phase 4: User Interface Components
#### 4.1 Authentication Pages
Pages to create:
- `src/app/auth/signin/page.js` - Login form
- `src/app/auth/signout/page.js` - Logout confirmation
- `src/app/auth/error/page.js` - Error handling
- `src/app/unauthorized/page.js` - Access denied page
#### 4.2 Navigation Updates
Update `src/components/ui/Navigation.js` to include:
- Login/logout buttons
- User info display
- Role-based menu items
#### 4.3 User Management Interface
For admin users:
- User listing and management
- Role assignment
- Account activation/deactivation
### Phase 5: Security Enhancements
#### 5.1 Input Validation Schemas
Create `src/lib/schemas/` with Zod schemas for all API endpoints:
```javascript
// src/lib/schemas/project.js
import { z } from "zod";
export const createProjectSchema = z.object({
contract_id: z.number().int().positive(),
project_name: z.string().min(1).max(255),
project_number: z.string().min(1).max(50),
address: z.string().optional(),
// ... other fields
});
export const updateProjectSchema = createProjectSchema.partial();
```
#### 5.2 Rate Limiting
Implement rate limiting for sensitive endpoints:
```javascript
// src/lib/middleware/rateLimit.js
const attempts = new Map();
export function withRateLimit(
handler,
options = { maxAttempts: 5, windowMs: 15 * 60 * 1000 }
) {
return async (req, context) => {
const key = req.ip || "unknown";
const now = Date.now();
const window = attempts.get(key) || {
count: 0,
resetTime: now + options.windowMs,
};
if (now > window.resetTime) {
window.count = 1;
window.resetTime = now + options.windowMs;
} else {
window.count++;
}
attempts.set(key, window);
if (window.count > options.maxAttempts) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
return handler(req, context);
};
}
```
## Implementation Checklist (Updated Status)
### ✅ Phase 1: Foundation - COMPLETED
- [x] Install dependencies (NextAuth.js v5, bcryptjs, zod)
- [x] Create environment configuration (.env.local)
- [x] Extend database schema (users, sessions, audit_logs)
- [x] Create initial admin user script
### ✅ Phase 2: Authentication - COMPLETED
- [x] Configure NextAuth.js with credentials provider
- [x] Create API route handlers (/api/auth/[...nextauth])
- [x] Implement user management system
- [x] Test login/logout functionality
### ✅ Phase 3: Authorization - COMPLETED
- [x] Implement API middleware (withAuth, role hierarchy)
- [x] Protect existing API routes (projects, contracts, tasks, notes)
- [x] Create role-based helper functions
- [x] Integrate session provider in app layout
### 🔄 Phase 4: User Interface - IN PROGRESS
- [x] Create sign-in page with form validation
- [x] Update navigation component with auth integration
- [x] Add role-based menu items
- [ ] Create sign-out confirmation page
- [ ] Create error handling page
- [ ] Create unauthorized access page
- [ ] Build admin user management interface
### ❌ Phase 5: Security Enhancements - NOT STARTED
- [ ] Add input validation schemas to all endpoints
- [ ] Implement rate limiting for sensitive operations
- [ ] Add comprehensive audit logging
- [ ] Create security headers middleware
- [ ] Implement CSRF protection
- [ ] Add password reset functionality
## Current Working Features
### 🔐 Authentication System
- **Login/Logout**: Fully functional with NextAuth.js
- **Session Management**: JWT-based with 30-day expiration
- **Password Security**: bcrypt hashing with salt rounds
- **Account Lockout**: 5 failed attempts = 15-minute lockout
- **Role System**: 4-tier hierarchy (admin, project_manager, user, read_only)
### 🛡️ Authorization System
- **API Protection**: All major endpoints require authentication
- **Role-Based Access**: Different permission levels per endpoint
- **Middleware**: Clean abstraction with helper functions
- **Session Validation**: Automatic session verification
### 📱 User Interface
- **Navigation**: Context-aware with user info and sign-out
- **Auth Pages**: Professional sign-in form with error handling
- **Role Integration**: Admin users see additional menu items
- **Responsive**: Works across device sizes
### 🗄️ Database Security
- **User Management**: Complete CRUD with proper validation
- **Audit Schema**: Ready for comprehensive logging
- **Indexes**: Optimized for performance
- **Constraints**: Role validation and data integrity
## Next Priority Tasks
1. **Complete Auth UI** (High Priority)
- Sign-out confirmation page
- Unauthorized access page
- Enhanced error handling
2. **Admin User Management** (High Priority)
- User listing interface
- Create/edit user forms
- Role assignment controls
3. **Security Enhancements** (Medium Priority)
- Input validation schemas
- Rate limiting middleware
- Comprehensive audit logging
4. **Password Management** (Medium Priority)
- Password reset functionality
- Password strength requirements
- Password change interface
## User Tracking in Projects - NEW FEATURE ✅
### 📊 Project User Management Implementation
We've successfully implemented comprehensive user tracking for projects:
#### Database Schema Updates ✅
- **created_by**: Tracks who created the project (user ID)
- **assigned_to**: Tracks who is assigned to work on the project (user ID)
- **created_at**: Timestamp when project was created
- **updated_at**: Timestamp when project was last modified
- **Indexes**: Performance optimized with proper foreign key indexes
#### API Enhancements ✅
- **Enhanced Queries**: Projects now include user names and emails via JOIN operations
- **User Assignment**: New `/api/projects/users` endpoint for user management
- **Query Filters**: Support for filtering projects by assigned user or creator
- **User Context**: Create/update operations automatically capture authenticated user ID
#### UI Components ✅
- **Project Form**: User assignment dropdown in create/edit forms
- **Project Listing**: "Created By" and "Assigned To" columns in project table
- **User Selection**: Dropdown populated with active users for assignment
#### New Query Functions ✅
- `getAllUsersForAssignment()`: Get active users for assignment dropdown
- `getProjectsByAssignedUser(userId)`: Filter projects by assignee
- `getProjectsByCreator(userId)`: Filter projects by creator
- `updateProjectAssignment(projectId, userId)`: Update project assignment
#### Security Integration ✅
- **Authentication Required**: All user operations require valid session
- **Role-Based Access**: User assignment respects role hierarchy
- **Audit Ready**: Infrastructure prepared for comprehensive user action logging
### Usage Examples
#### Creating Projects with User Tracking
```javascript
// Projects are automatically assigned to the authenticated user as creator
POST /api/projects
{
"project_name": "New Project",
"assigned_to": "user-id-here", // Optional assignment
// ... other project data
}
```
#### Filtering Projects by User
```javascript
// Get projects assigned to specific user
GET /api/projects?assigned_to=user-id
// Get projects created by specific user
GET /api/projects?created_by=user-id
```
#### Updating Project Assignment
```javascript
POST /api/projects/users
{
"projectId": 123,
"assignedToUserId": "new-user-id"
}
```
## Project Tasks User Tracking - NEW FEATURE ✅
### 📋 Task User Management Implementation
We've also implemented comprehensive user tracking for project tasks:
#### Database Schema Updates ✅
- **created_by**: Tracks who created the task (user ID)
- **assigned_to**: Tracks who is assigned to work on the task (user ID)
- **created_at**: Timestamp when task was created
- **updated_at**: Timestamp when task was last modified
- **Indexes**: Performance optimized with proper foreign key indexes
#### API Enhancements ✅
- **Enhanced Queries**: Tasks now include user names and emails via JOIN operations
- **User Assignment**: New `/api/project-tasks/users` endpoint for user management
- **Query Filters**: Support for filtering tasks by assigned user or creator
- **User Context**: Create/update operations automatically capture authenticated user ID
#### UI Components ✅
- **Task Form**: User assignment dropdown in create task forms
- **Task Listing**: "Created By" and "Assigned To" columns in task table
- **User Selection**: Dropdown populated with active users for assignment
#### New Task Query Functions ✅
- `getAllUsersForTaskAssignment()`: Get active users for assignment dropdown
- `getProjectTasksByAssignedUser(userId)`: Filter tasks by assignee
- `getProjectTasksByCreator(userId)`: Filter tasks by creator
- `updateProjectTaskAssignment(taskId, userId)`: Update task assignment
#### Task Creation Behavior ✅
- **Auto-assignment**: Tasks are automatically assigned to the authenticated user as creator
- **Optional Assignment**: Users can assign tasks to other team members during creation
- **Creator Tracking**: All tasks track who created them for accountability
### Task Usage Examples
#### Creating Tasks with User Tracking
```javascript
// Tasks are automatically assigned to the authenticated user as creator
POST /api/project-tasks
{
"project_id": 123,
"task_template_id": 1, // or custom_task_name for custom tasks
"assigned_to": "user-id-here", // Optional, defaults to creator
"priority": "high"
}
```
#### Filtering Tasks by User
```javascript
// Get tasks assigned to specific user
GET /api/project-tasks?assigned_to=user-id
// Get tasks created by specific user
GET /api/project-tasks?created_by=user-id
```
#### Updating Task Assignment
```javascript
POST /api/project-tasks/users
{
"taskId": 456,
"assignedToUserId": "new-user-id"
}
```
### Next Enhancements
1. **Dashboard Views** (Recommended)
- "My Projects" dashboard showing assigned projects
- Project creation history per user
- Workload distribution reports
2. **Advanced Filtering** (Future)
- Multi-user assignment support
- Team-based project assignments
- Role-based project visibility
3. **Notifications** (Future)
- Email alerts on project assignment
- Deadline reminders for assigned users
- Status change notifications
## Notes User Tracking - NEW FEATURE ✅
### 📝 Notes User Management Implementation
We've also implemented comprehensive user tracking for all notes (both project notes and task notes):
#### Database Schema Updates ✅
- **created_by**: Tracks who created the note (user ID)
- **is_system**: Distinguishes between user notes and system-generated notes
- **Enhanced queries**: Notes now include user names and emails via JOIN operations
- **Indexes**: Performance optimized with proper indexes for user lookups
#### API Enhancements ✅
- **User Context**: All note creation operations automatically capture authenticated user ID
- **System Notes**: Automatic system notes (task status changes) track who made the change
- **User Information**: Note retrieval includes creator name and email for display
#### UI Components ✅
- **Project Notes**: Display creator name and email in project note listings
- **Task Notes**: Show who added each note with user badges and timestamps
- **System Notes**: Distinguished from user notes with special styling and "System" badge
- **User Attribution**: Clear indication of who created each note and when
#### New Note Query Functions ✅
- `getAllNotesWithUsers()`: Get all notes with user and project/task context
- `getNotesByCreator(userId)`: Filter notes by creator for user activity tracking
- Enhanced `getNotesByProjectId()` and `getNotesByTaskId()` with user information
#### Automatic User Tracking ✅
- **Note Creation**: All new notes automatically record who created them
- **System Notes**: Task status changes generate system notes attributed to the user who made the change
- **Audit Trail**: Complete history of who added what notes and when
### Notes Usage Examples
#### Project Notes with User Tracking
- Notes display creator name in a blue badge next to the timestamp
- Form automatically associates notes with the authenticated user
- Clear visual distinction between different note authors
#### Task Notes with User Tracking
- User notes show creator name in a gray badge
- System notes show "System" badge but also track the user who triggered the action
- Full audit trail of task status changes and who made them
#### System Note Generation
```javascript
// When a user changes a task status, a system note is automatically created:
// "Status changed from 'pending' to 'in_progress'" - attributed to the user who made the change
```
### Benefits
1. **Accountability**: Full audit trail of who added what notes
2. **Context**: Know who to contact for clarification on specific notes
3. **History**: Track communication and decisions made by team members
4. **System Integration**: Automatic notes for system actions still maintain user attribution
5. **User Experience**: Clear visual indicators of note authors improve team collaboration

View File

@@ -1,129 +0,0 @@
# Quick Deployment Guide - Timezone Fix
## For Production Server
1. **SSH into your server** where Docker is running
2. **Navigate to project directory**
```bash
cd /path/to/panel
```
3. **Pull latest code** (includes timezone fixes)
```bash
git pull origin main
```
4. **Stop running containers**
```bash
docker-compose -f docker-compose.prod.yml down
```
5. **Rebuild Docker images** (this is critical - it bakes in the timezone configuration)
```bash
docker-compose -f docker-compose.prod.yml build --no-cache
```
6. **Start containers**
```bash
docker-compose -f docker-compose.prod.yml up -d
```
7. **Verify timezone is correct**
```bash
# Check container timezone
docker-compose -f docker-compose.prod.yml exec app date
# Should show Polish time with CEST/CET timezone
# Example output:
# Sat Oct 4 19:45:00 CEST 2025
```
8. **Test the fix**
- Post a new note at a known time (e.g., 19:45)
- Verify it displays the same time (19:45)
- Test both project notes and task notes
## What Changed
### Code Changes
- ✅ Fixed `datetime('now', 'localtime')` in all database queries
- ✅ Updated display formatters to use Europe/Warsaw timezone
- ✅ Fixed note display in components
### Docker Changes (Critical!)
- ✅ Set `ENV TZ=Europe/Warsaw` in Dockerfile
- ✅ Configured system timezone in containers
- ✅ Added TZ environment variable to docker-compose files
## Why Rebuild is Necessary
The timezone configuration is **baked into the Docker image** during build time:
- `ENV TZ=Europe/Warsaw` - Set during image build
- `RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime` - Executed during image build
Just restarting containers (`docker-compose restart`) will **NOT** apply these changes!
## Troubleshooting
### If times are still wrong after deployment:
1. **Verify you rebuilt the images**
```bash
docker images | grep panel
# Check the "CREATED" timestamp - should be recent
```
2. **Check if container has correct timezone**
```bash
docker-compose -f docker-compose.prod.yml exec app date
```
Should show Polish time, not UTC!
3. **Check SQLite is using correct time**
```bash
docker-compose -f docker-compose.prod.yml exec app node -e "const db = require('better-sqlite3')('./data/database.sqlite'); console.log(db.prepare(\"SELECT datetime('now', 'localtime') as time\").get());"
```
Should show current Polish time
4. **Force rebuild if needed**
```bash
docker-compose -f docker-compose.prod.yml down
docker system prune -f
docker-compose -f docker-compose.prod.yml build --no-cache
docker-compose -f docker-compose.prod.yml up -d
```
## Expected Behavior After Fix
### Before Fix (Docker in UTC):
```
User posts note at 10:30 Poland time
→ Docker sees 08:30 UTC as "local time"
→ SQLite stores: 08:30
→ Display shows: 08:30 ❌ (2 hours off!)
```
### After Fix (Docker in Europe/Warsaw):
```
User posts note at 10:30 Poland time
→ Docker sees 10:30 Poland time as "local time"
→ SQLite stores: 10:30
→ Display shows: 10:30 ✅ (correct!)
```
## Important Notes
1. **Old notes**: Notes created before this fix may still show incorrect times (they were stored in UTC)
2. **New notes**: All new notes after deployment will show correct times
3. **Audit logs**: Continue to work correctly (they always used ISO format)
4. **Zero downtime**: Can't achieve - need to stop/rebuild/start containers
## Quick Check Command
After deployment, run this one-liner to verify everything:
```bash
docker-compose -f docker-compose.prod.yml exec app sh -c 'date && node -e "console.log(new Date().toLocaleString(\"pl-PL\"))"'
```
Both outputs should show the same Polish time!

View File

@@ -1,156 +0,0 @@
# Docker Timezone Configuration Fix
## Problem
Even after fixing the SQLite `datetime('now', 'localtime')` calls, notes posted at 10:00 still showed as 08:00 when running in Docker.
## Root Cause
**Docker containers run in UTC timezone by default!**
When using `datetime('now', 'localtime')` in SQLite:
- On local Windows machine: Uses Windows timezone (Europe/Warsaw) → ✅ Correct
- In Docker container: Uses container timezone (UTC) → ❌ Wrong by 2 hours
Example:
```
User posts at 10:00 Poland time (UTC+2)
Docker container thinks local time is 08:00 UTC
SQLite datetime('now', 'localtime') stores: 08:00
Display shows: 08:00 (wrong!)
```
## Solution
Set the Docker container timezone to Europe/Warsaw
### 1. Updated Dockerfile (Production)
```dockerfile
# Use Node.js 22.11.0 as the base image
FROM node:22.11.0
# Set timezone to Europe/Warsaw (Polish timezone)
ENV TZ=Europe/Warsaw
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ... rest of Dockerfile
```
### 2. Updated Dockerfile.dev (Development)
```dockerfile
# Use Node.js 22.11.0 as the base image
FROM node:22.11.0
# Set timezone to Europe/Warsaw (Polish timezone)
ENV TZ=Europe/Warsaw
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ... rest of Dockerfile
```
### 3. Updated docker-compose.yml (Development)
```yaml
environment:
- NODE_ENV=development
- TZ=Europe/Warsaw
```
### 4. Updated docker-compose.prod.yml (Production)
```yaml
environment:
- NODE_ENV=production
- TZ=Europe/Warsaw
- NEXTAUTH_SECRET=...
- NEXTAUTH_URL=...
```
## How to Apply
### Option 1: Rebuild Docker Images
```bash
# Stop containers
docker-compose down
# Rebuild images
docker-compose build --no-cache
# Start containers
docker-compose up -d
```
### Option 2: For Production Deployment
```bash
# Pull latest code with fixes
git pull
# Rebuild production image
docker-compose -f docker-compose.prod.yml build --no-cache
# Restart
docker-compose -f docker-compose.prod.yml up -d
```
## Verification
After rebuilding and restarting, verify timezone inside container:
```bash
# Check timezone
docker exec -it <container_name> date
# Should show: Sat Oct 4 19:00:00 CEST 2025
# Check Node.js sees correct timezone
docker exec -it <container_name> node -e "console.log(new Date().toLocaleString('pl-PL', {timeZone: 'Europe/Warsaw'}))"
# Should show current Polish time
# Check SQLite sees correct timezone
docker exec -it <container_name> node -e "const db = require('better-sqlite3')('./data/database.sqlite'); console.log(db.prepare(\"SELECT datetime('now', 'localtime')\").get());"
# Should show current Polish time
```
## Why This Works
1. **TZ Environment Variable**: Tells all processes (including Node.js and SQLite) what timezone to use
2. **Symlink /etc/localtime**: Updates system timezone for the entire container
3. **echo TZ > /etc/timezone**: Ensures the timezone persists
Now when SQLite uses `datetime('now', 'localtime')`:
- Container local time is 10:00 Poland time
- SQLite stores: 10:00
- Display shows: 10:00 ✅
## Important Notes
1. **Must rebuild images**: Just restarting containers is not enough - the timezone configuration is baked into the image
2. **All existing data**: Old notes will still show incorrect times (they were stored in UTC)
3. **New notes**: Will now display correctly
4. **DST handling**: Europe/Warsaw automatically handles Daylight Saving Time transitions
## Alternative Approach (Not Recommended)
Instead of changing container timezone, you could:
1. Store everything in UTC (like audit logs do with ISO format)
2. Always convert on display
But this requires more code changes and the current approach is simpler and more maintainable.
## Files Modified
1. `Dockerfile` - Added TZ configuration
2. `Dockerfile.dev` - Added TZ configuration
3. `docker-compose.yml` - Added TZ environment variable
4. `docker-compose.prod.yml` - Added TZ environment variable
## Testing Checklist
After deployment:
- [ ] Container shows correct date/time with `docker exec <container> date`
- [ ] Post a new note at known time (e.g., 10:30)
- [ ] Verify note displays the same time (10:30)
- [ ] Check both project notes and task notes
- [ ] Verify audit logs still work correctly
- [ ] Check task timestamps (date_started, date_completed)

View File

@@ -1,152 +0,0 @@
# ✅ Dropdown Consolidation - COMPLETED
## Summary of Changes
The project management interface has been successfully updated to eliminate redundant status displays by consolidating status badges and dropdowns into unified interactive components.
## ✅ Components Successfully Updated
### Task Status Dropdowns:
- **ProjectTasksSection.js** → TaskStatusDropdownSimple ✅
- **Tasks page** (`/tasks`) → TaskStatusDropdownSimple ✅
- **ProjectTasksDashboard.js** → TaskStatusDropdownSimple ✅
- **Main Dashboard** (`/`) → TaskStatusDropdownSimple ✅ (read-only mode)
### Status Configurations:
#### Task Statuses:
- `pending` → Warning (yellow)
- `in_progress` → Primary (blue)
- `completed` → Success (green)
- `cancelled` → Danger (red)
#### Project Statuses:
- `registered` → Secondary (gray)
- `in_progress_design` → Primary (blue)
- `in_progress_construction` → Primary (blue)
- `fulfilled` → Success (green)
## 🎯 Key Features Implemented
### Unified Interface:
- Single component serves as both status display and edit interface
- Click to expand dropdown with available status options
- Visual feedback with arrow rotation and hover effects
- Loading states during API updates
### Debug Features (Current):
- Red borders around dropdowns for visibility testing
- Yellow debug headers showing component type
- Console logging for click events and API calls
- Semi-transparent backdrop for easy identification
### Z-Index Solution:
- Dropdown: `z-[9999]` (maximum priority)
- Backdrop: `z-[9998]` (behind dropdown)
## 🧪 Testing Instructions
### 1. Access Test Pages:
```
http://localhost:3000/test-dropdowns # Isolated component testing
http://localhost:3000/projects # Project list with status dropdowns
http://localhost:3000/tasks # Task list with status dropdowns
http://localhost:3000/ # Main dashboard
```
### 2. Standalone HTML Tests:
```
test-dropdown-comprehensive.html # Complete functionality test
test-dropdown.html # Basic dropdown structure test
```
### 3. Test Checklist:
- [ ] Dropdowns appear immediately when clicked
- [ ] Red borders and debug headers are visible
- [ ] Dropdowns appear above all other elements
- [ ] Clicking outside closes dropdowns
- [ ] Dropdowns work properly in table contexts
- [ ] API calls update status correctly
- [ ] Loading states show during updates
- [ ] Error handling reverts status on failure
## 📁 Files Created/Modified
### New Components:
- `src/components/TaskStatusDropdownSimple.js`
- `src/components/ProjectStatusDropdownSimple.js`
- `src/app/test-dropdowns/page.js`
### Updated Components:
- `src/components/ProjectTasksSection.js`
- `src/app/tasks/page.js`
- `src/components/ProjectTasksDashboard.js`
- `src/app/page.js`
### Test Files:
- `test-dropdown-comprehensive.html`
- `test-dropdown.html`
### Documentation:
- `DROPDOWN_IMPLEMENTATION_SUMMARY.md`
- `DROPDOWN_COMPLETION_STATUS.md` ✅ (this file)
## 🚀 Next Steps (Production Polish)
### 1. Remove Debug Features:
```javascript
// Remove these debug elements:
- Red borders (border-2 border-red-500)
- Yellow debug headers
- Console.log statements
- Semi-transparent backdrop styling
```
### 2. Final Styling:
```javascript
// Replace debug styles with:
border border-gray-200 // Subtle borders
shadow-lg // Professional shadows
Clean backdrop (transparent)
```
### 3. Performance Optimization:
- Consider portal-based positioning for complex table layouts
- Add keyboard navigation (Enter/Escape keys)
- Implement click-outside using refs instead of global listeners
### 4. Code Cleanup:
- Remove original TaskStatusDropdown.js and ProjectStatusDropdown.js
- Rename Simple components to drop "Simple" suffix
- Update import statements across application
## ✅ Success Criteria Met
1. **Redundant UI Eliminated**: ✅ Single component replaces badge + dropdown pairs
2. **Z-Index Issues Resolved**: ✅ Dropdowns appear above all elements
3. **Table Compatibility**: ✅ Works properly in table/overflow contexts
4. **API Integration**: ✅ Status updates via PATCH/PUT requests
5. **Error Handling**: ✅ Reverts status on API failures
6. **Loading States**: ✅ Shows "Updating..." during API calls
7. **Consistent Styling**: ✅ Unified design patterns across components
## 🎉 Project Status: READY FOR TESTING
The dropdown consolidation is complete and ready for user testing. All components have been updated to use the simplified, working versions with debug features enabled for validation.

View File

@@ -1,142 +0,0 @@
# Dropdown Consolidation - Implementation Summary
## Problem Identified
The project management interface had redundant status displays where both a status badge and a dropdown showing the same status information were displayed together. Additionally, there was a z-index issue where dropdowns appeared behind other elements.
## Solution Implemented
### 1. Created Unified Dropdown Components
#### TaskStatusDropdown Components:
- **TaskStatusDropdown.js** - Original enhanced component with portal positioning (currently has complexity issues)
- **TaskStatusDropdownSimple.js** - ✅ Simplified working version for testing
#### ProjectStatusDropdown Components:
- **ProjectStatusDropdown.js** - Original enhanced component with portal positioning (currently has complexity issues)
- **ProjectStatusDropdownSimple.js** - ✅ Simplified working version for testing
### 2. Key Features of Unified Components
#### Interactive Status Display:
- Single component serves as both status badge and dropdown
- Click to expand dropdown with status options
- Visual feedback (arrow rotation, hover effects)
- Loading states during API calls
#### Debugging Features (Current Implementation):
- Console logging for click events
- Visible red border around dropdown for testing
- Yellow debug header showing dropdown is visible
- Semi-transparent backdrop for easy identification
#### API Integration:
- TaskStatusDropdown: PATCH `/api/project-tasks/{id}`
- ProjectStatusDropdown: PUT `/api/projects/{id}`
- Callback support for parent component refresh
- Error handling with status reversion
### 3. Updated Components
#### Currently Using Simplified Version:
-**ProjectTasksSection.js** - Task table uses TaskStatusDropdownSimple
-**Test page created** - `/test-dropdowns` for isolated testing
#### Still Using Original (Need to Update):
- **ProjectTasksPage** (`/tasks`) - Uses TaskStatusDropdown
- **ProjectTasksDashboard** - Uses TaskStatusDropdown
- **Main Dashboard** (`/`) - Uses TaskStatusDropdown (read-only mode)
- **Project Detail Pages** - Uses ProjectStatusDropdown
### 4. Configuration
#### Task Status Options:
- `pending` → Warning variant (yellow)
- `in_progress` → Primary variant (blue)
- `completed` → Success variant (green)
- `cancelled` → Danger variant (red)
#### Project Status Options:
- `registered` → Secondary variant (gray)
- `in_progress_design` → Primary variant (blue)
- `in_progress_construction` → Primary variant (blue)
- `fulfilled` → Success variant (green)
### 5. Z-Index Solution
- Dropdown: `z-[9999]` (maximum visibility)
- Backdrop: `z-[9998]` (behind dropdown)
## Current Status
### ✅ Working:
- Simplified dropdown components compile without errors
- Basic dropdown structure and styling
- Debug features for testing
- Test page available at `/test-dropdowns`
### 🚧 In Progress:
- Testing dropdown visibility in browser
- Development server startup (terminal access issues)
### 📋 Next Steps:
1. **Test Simplified Components**
- Verify dropdowns appear correctly
- Test click interactions
- Confirm API calls work
2. **Replace Original Components**
- Update remaining pages to use simplified versions
- Remove complex portal/positioning code if simple version works
3. **Production Polish**
- Remove debug features (red borders, console logs)
- Fine-tune styling and positioning
- Add portal-based positioning if needed for table overflow
4. **Code Cleanup**
- Remove unused original components
- Clean up imports across all files
## Testing Instructions
1. **Access Test Page**: Navigate to `/test-dropdowns`
2. **Check Console**: Open browser dev tools (F12) → Console tab
3. **Test Interactions**: Click dropdowns to see debug messages
4. **Verify Visibility**: Look for red-bordered dropdowns with yellow debug headers
## Files Modified
### New Components:
- `src/components/TaskStatusDropdownSimple.js`
- `src/components/ProjectStatusDropdownSimple.js`
- `src/app/test-dropdowns/page.js`
### Updated Components:
- `src/components/ProjectTasksSection.js` (using simple version)
- `src/components/TaskStatusDropdown.js` (enhanced but problematic)
- `src/components/ProjectStatusDropdown.js` (enhanced but problematic)
### Test Files:
- `test-dropdown.html` (standalone HTML test)
- `start-dev.bat` (development server script)
The consolidation successfully eliminates duplicate status displays and provides a unified interface for status management across the application.

View File

@@ -1,176 +0,0 @@
# Edge Runtime Compatibility Fix - Final Solution
## Problem Resolved
The audit logging system was causing "Edge runtime does not support Node.js 'fs' module" errors because the `better-sqlite3` database module was being loaded in Edge Runtime contexts through static imports.
## Root Cause
The middleware imports `auth.js` → which imported `auditLog.js` → which had a static import of `db.js` → which imports `better-sqlite3`. This caused the entire SQLite module to be loaded even in Edge Runtime where it's not supported.
## Final Solution
### 1. Created Safe Audit Logging Module
**File: `src/lib/auditLogSafe.js`**
This module provides:
-**No static database imports** - completely safe for Edge Runtime
-**Runtime detection** - automatically detects Edge vs Node.js
-**Graceful fallbacks** - console logging in Edge, database in Node.js
-**Constants always available** - `AUDIT_ACTIONS` and `RESOURCE_TYPES`
-**Async/await support** - works with modern API patterns
```javascript
// Safe import that never causes Edge Runtime errors
import {
logAuditEventSafe,
AUDIT_ACTIONS,
RESOURCE_TYPES,
} from "./auditLogSafe.js";
// Works in any runtime
await logAuditEventSafe({
action: AUDIT_ACTIONS.LOGIN,
userId: "user123",
resourceType: RESOURCE_TYPES.SESSION,
});
```
### 2. Updated All Imports
**Files Updated:**
- `src/lib/auth.js` - Authentication logging
- `src/app/api/projects/route.js` - Project operations
- `src/app/api/projects/[id]/route.js` - Individual project operations
- `src/app/api/notes/route.js` - Note operations
**Before:**
```javascript
import { logApiAction, AUDIT_ACTIONS } from "@/lib/auditLog.js"; // ❌ Causes Edge Runtime errors
```
**After:**
```javascript
import { logApiActionSafe, AUDIT_ACTIONS } from "@/lib/auditLogSafe.js"; // ✅ Edge Runtime safe
```
### 3. Runtime Behavior
#### Edge Runtime
- **Detection**: Automatic via `typeof EdgeRuntime !== 'undefined'`
- **Logging**: Console output only
- **Performance**: Zero database overhead
- **Errors**: None - completely safe
#### Node.js Runtime
- **Detection**: Automatic fallback when Edge Runtime not detected
- **Logging**: Full database functionality via dynamic import
- **Performance**: Full audit trail with database persistence
- **Errors**: Graceful handling with console fallback
### 4. Migration Pattern
The safe module uses a smart delegation pattern:
```javascript
// In Edge Runtime: Console logging only
console.log(`[Audit] ${action} by user ${userId}`);
// In Node.js Runtime: Try database, fallback to console
try {
const auditModule = await import("./auditLog.js");
auditModule.logAuditEvent({ ...params });
} catch (dbError) {
console.log("[Audit] Database logging failed, using console fallback");
}
```
## Files Structure
```
src/lib/
├── auditLog.js # Original - Node.js only (database operations)
├── auditLogSafe.js # New - Universal (Edge + Node.js compatible)
├── auditLogEdge.js # Alternative - Edge-specific with API calls
└── auth.js # Updated to use safe imports
```
## Testing
Run the compatibility test:
```bash
node test-safe-audit-logging.mjs
```
**Expected Output:**
```
✅ Safe module imported successfully
✅ Edge Runtime logging successful (console only)
✅ Node.js Runtime logging successful (database + console)
✅ Constants accessible
```
## Verification Checklist
**No more Edge Runtime errors**
**Middleware works without database dependencies**
**Authentication logging works in all contexts**
**API routes maintain full audit functionality**
**Constants available everywhere**
**Graceful degradation in Edge Runtime**
**Full functionality in Node.js Runtime**
## Performance Impact
- **Edge Runtime**: Minimal - only console logging
- **Node.js Runtime**: Same as before - full database operations
- **Import cost**: Near zero - no static database imports
- **Memory usage**: Significantly reduced in Edge Runtime
## Migration Guide
To update existing code:
1. **Replace imports:**
```javascript
// Old
import { logApiAction } from "@/lib/auditLog.js";
// New
import { logApiActionSafe } from "@/lib/auditLogSafe.js";
```
2. **Update function calls:**
```javascript
// Old
logApiAction(req, action, type, id, session, details);
// New
await logApiActionSafe(req, action, type, id, session, details);
```
3. **Add runtime exports** (for API routes):
```javascript
export const runtime = "nodejs"; // For database-heavy routes
```
## Best Practices Applied
1. **Separation of Concerns**: Safe module for universal use, full module for Node.js
2. **Dynamic Imports**: Database modules loaded only when needed
3. **Runtime Detection**: Automatic environment detection
4. **Graceful Degradation**: Meaningful fallbacks in constrained environments
5. **Error Isolation**: Audit failures don't break main application flow
The application now handles both Edge and Node.js runtimes seamlessly with zero Edge Runtime errors! 🎉

View File

@@ -1,161 +0,0 @@
# Final Edge Runtime Fix - Audit Logging System
## ✅ **Issue Resolved**
The Edge Runtime error has been completely fixed! The audit logging system now works seamlessly across all Next.js runtime environments.
## 🔧 **Final Implementation**
### **Problem Summary**
- Edge Runtime was trying to load `better-sqlite3` (Node.js fs module)
- Static imports in middleware caused the entire dependency chain to load
- `middleware.js``auth.js``auditLog.js``db.js``better-sqlite3`
### **Solution Implemented**
#### 1. **Made All Functions Async**
```javascript
// Before: Synchronous with require()
export function logAuditEvent() {
const { default: db } = require("./db.js");
}
// After: Async with dynamic import
export async function logAuditEvent() {
const { default: db } = await import("./db.js");
}
```
#### 2. **Runtime Detection & Graceful Fallbacks**
```javascript
export async function logAuditEvent(params) {
try {
// Edge Runtime detection
if (
typeof EdgeRuntime !== "undefined" ||
process.env.NEXT_RUNTIME === "edge"
) {
console.log(`[Audit Log - Edge Runtime] ${action} by user ${userId}`);
return; // Graceful exit
}
// Node.js Runtime: Full database functionality
const { default: db } = await import("./db.js");
// ... database operations
} catch (error) {
console.error("Failed to log audit event:", error);
// Non-breaking error handling
}
}
```
#### 3. **Safe Wrapper Module (`auditLogSafe.js`)**
```javascript
export async function logAuditEventSafe(params) {
console.log(`[Audit] ${action} by user ${userId}`); // Always log to console
if (typeof EdgeRuntime !== "undefined") {
return; // Edge Runtime: Console only
}
try {
const auditModule = await import("./auditLog.js");
await auditModule.logAuditEvent(params); // Node.js: Database + console
} catch (error) {
console.log("[Audit] Database logging failed, using console fallback");
}
}
```
## 🎯 **Runtime Behavior**
| Runtime | Behavior | Database | Console | Errors |
| ----------- | ------------------------ | -------- | ------- | ---------------------- |
| **Edge** | Console logging only | ❌ | ✅ | ❌ Zero errors |
| **Node.js** | Full audit functionality | ✅ | ✅ | ❌ Full error handling |
## ✅ **Test Results**
```bash
$ node test-safe-audit-logging.mjs
Testing Safe Audit Logging...
1. Testing safe module import...
✅ Safe module imported successfully
Available actions: 27
Available resource types: 8
2. Testing in simulated Edge Runtime...
[Audit] project_view by user anonymous on project:test-123
[Audit] Edge Runtime detected - console logging only
✅ Edge Runtime logging successful (console only)
3. Testing in simulated Node.js Runtime...
[Audit] project_create by user anonymous on project:test-456
Audit log: project_create by user anonymous on project:test-456
✅ Node.js Runtime logging successful (database + console)
4. Testing constants accessibility...
✅ Constants accessible:
LOGIN action: login
PROJECT resource: project
NOTE_CREATE action: note_create
✅ Safe Audit Logging test completed!
Key features verified:
- ✅ No static database imports
- ✅ Edge Runtime compatibility
- ✅ Graceful fallbacks
- ✅ Constants always available
- ✅ Async/await support
The middleware should now work without Edge Runtime errors!
```
## 📁 **Files Updated**
### **Core Audit System**
-`src/lib/auditLog.js` - Made all functions async, removed static imports
-`src/lib/auditLogSafe.js` - New Edge-compatible wrapper module
### **Authentication**
-`src/lib/auth.js` - Updated to use safe audit logging
### **API Routes**
-`src/app/api/audit-logs/route.js` - Updated for async functions
-`src/app/api/audit-logs/stats/route.js` - Updated for async functions
-`src/app/api/audit-logs/log/route.js` - Updated for async functions
-`src/app/api/projects/route.js` - Using safe audit logging
-`src/app/api/projects/[id]/route.js` - Using safe audit logging
-`src/app/api/notes/route.js` - Using safe audit logging
## 🚀 **Benefits Achieved**
1. **✅ Zero Edge Runtime Errors** - No more fs module conflicts
2. **✅ Universal Compatibility** - Works in any Next.js runtime environment
3. **✅ No Functionality Loss** - Full audit trail in production (Node.js runtime)
4. **✅ Graceful Degradation** - Meaningful console logging in Edge Runtime
5. **✅ Performance Optimized** - No unnecessary database loads in Edge Runtime
6. **✅ Developer Friendly** - Clear logging shows what's happening in each runtime
## 🎉 **Final Status**
**The audit logging system is now production-ready and Edge Runtime compatible!**
- **Middleware**: ✅ Works without errors
- **Authentication**: ✅ Logs login/logout events
- **API Routes**: ✅ Full audit trail for CRUD operations
- **Admin Interface**: ✅ View audit logs at `/admin/audit-logs`
- **Edge Runtime**: ✅ Zero errors, console fallbacks
- **Node.js Runtime**: ✅ Full database functionality
Your application should now run perfectly without any Edge Runtime errors while maintaining comprehensive audit logging! 🎊

View File

@@ -1,137 +0,0 @@
# Polish Geospatial Layers Integration - COMPLETE SUCCESS! 🎉
## ✅ Mission Accomplished
All Polish geospatial layers including Google Maps have been successfully integrated into the main project's mapping system. The integration maintains proper transparency handling and provides a comprehensive mapping solution.
## 🚀 What Was Implemented
### 1. Enhanced Layer Configuration (`mapLayers.js`)
**Before**: Only basic OpenStreetMap + simple Polish orthophoto
**After**: 8 base layers + 6 overlay layers with full transparency support
### 2. Updated Main Map Components
- **`LeafletMap.js`** - Main project map component ✅
- **`EnhancedLeafletMap.js`** - Enhanced map variant ✅
- Added `WMSTileLayer` import and proper overlay handling
### 3. Comprehensive Layer Selection
#### Base Layers (8 total)
1. **OpenStreetMap** (default)
2. **🇵🇱 Polish Orthophoto (Standard)** - WMTS format
3. **🇵🇱 Polish Orthophoto (High Resolution)** - WMTS format
4. **🌍 Google Satellite** - Global satellite imagery
5. **🌍 Google Hybrid** - Satellite + roads
6. **🌍 Google Roads** - Road map
7. **Satellite (Esri)** - Alternative satellite
8. **Topographic** - CartoDB topographic
#### Overlay Layers (6 total with transparency)
1. **📋 Polish Cadastral Data** (WMS, 80% opacity)
2. **🏗️ Polish Spatial Planning** (WMS, 70% opacity)
3. **🛣️ LP-Portal Roads** (WMS, 90% opacity)
4. **🏷️ LP-Portal Street Names** (WMS, 100% opacity)
5. **📐 LP-Portal Parcels** (WMS, 60% opacity)
6. **📍 LP-Portal Survey Markers** (WMS, 80% opacity)
## 🎯 Key Features Implemented
### Layer Control Interface
- **📚 Layer Control Button** in top-right corner
- **Radio buttons** for base layers (mutually exclusive)
- **Checkboxes** for overlays (can combine multiple)
- **Emoji icons** for easy layer identification
### Transparency System
- **Base layers**: Fully opaque backgrounds
- **Overlay layers**: Each with optimized transparency:
- Property boundaries: Semi-transparent for visibility
- Planning zones: Semi-transparent for context
- Roads: Mostly opaque for navigation
- Text labels: Fully opaque for readability
- Survey data: Semi-transparent for reference
### Technical Excellence
- **WMTS Integration**: Proper KVP format for Polish orthophoto
- **WMS Integration**: Transparent PNG overlays with correct parameters
- **Performance**: Efficient tile loading and layer switching
- **Compatibility**: Works with existing project structure
- **SSR Safe**: Proper dynamic imports for Next.js
## 🌍 Geographic Coverage
### Poland-Specific Layers
- **Polish Orthophoto**: Complete national coverage at high resolution
- **Cadastral Data**: Official property boundaries nationwide
- **Spatial Planning**: Zoning data where available
- **LP-Portal**: Municipal data for specific regions
### Global Layers
- **Google Services**: Worldwide satellite and road data
- **Esri Satellite**: Global high-resolution imagery
- **OpenStreetMap**: Community-driven global mapping
## 📱 Where It's Available
### Main Project Maps
- **`/projects/map`** - Projects overview map ✅
- **Individual project cards** - Project location maps ✅
- **All existing map components** - Enhanced with new layers ✅
### Demo/Test Pages (Still Available)
- **`/comprehensive-polish-map`** - Full-featured demo
- **`/test-polish-map`** - Layer comparison
- **`/debug-polish-orthophoto`** - Technical testing
## 🔧 Code Changes Summary
### Layer Configuration (`mapLayers.js`)
```javascript
// Added 6 new base layers including Polish orthophoto + Google
// Added 6 overlay layers with WMS configuration
// Proper transparency and opacity settings
```
### Map Components (`LeafletMap.js`, `EnhancedLeafletMap.js`)
```javascript
// Added WMSTileLayer import
// Added Overlay component support
// Layer control with both BaseLayer and Overlay
// Transparency parameter handling
```
## 🎯 User Experience
### Easy Layer Selection
1. Click **📚** layer control button
2. Select base layer (aerial photos, satellite, roads, etc.)
3. Check/uncheck overlays (property boundaries, planning, etc.)
4. Layers update instantly
### Visual Clarity
- **Emojis** make layer types instantly recognizable
- **Proper transparency** prevents overlays from obscuring base maps
- **Performance** optimized for smooth switching
## 🚀 Ready for Production
**Integration Complete**: All layers working in main project maps
**Transparency Handled**: Overlays properly configured with opacity
**Performance Optimized**: Efficient loading and switching
**User-Friendly**: Clear interface with emoji identifiers
**Tested**: Development server running successfully
**Documented**: Comprehensive guides available
## 🎉 Final Result
The project now has **enterprise-grade Polish geospatial capabilities** integrated directly into the main mapping system. Users can access:
- **High-resolution Polish orthophoto** from official government sources
- **Official cadastral data** for property boundaries
- **Spatial planning information** for zoning
- **Municipal data** from LP-Portal
- **Global satellite imagery** from Google and Esri
- **Full transparency control** for overlay combinations
**Mission: ACCOMPLISHED!** 🚀🗺️🇵🇱

View File

@@ -1,116 +0,0 @@
# Polish Geospatial Layers Integration - Project Maps Complete! 🎉
## ✅ Successfully Integrated Into Main Project Maps
All Polish geospatial layers and Google layers have been successfully integrated into the main project's mapping system.
## 🗺️ Available Layers in Project Maps
### Base Layers (Mutually Exclusive)
1. **OpenStreetMap** - Default layer
2. **🇵🇱 Polish Orthophoto (Standard)** - High-quality aerial imagery
3. **🇵🇱 Polish Orthophoto (High Resolution)** - Ultra-high resolution aerial imagery
4. **🌍 Google Satellite** - Google satellite imagery
5. **🌍 Google Hybrid** - Google satellite with roads overlay
6. **🌍 Google Roads** - Google road map
7. **Satellite (Esri)** - Esri world imagery
8. **Topographic** - CartoDB Voyager topographic map
### Overlay Layers (Can be Combined with Transparency)
1. **📋 Polish Cadastral Data** - Property boundaries and parcel information (80% opacity)
2. **🏗️ Polish Spatial Planning** - Zoning and urban planning data (70% opacity)
3. **🛣️ LP-Portal Roads** - Detailed road network (90% opacity)
4. **🏷️ LP-Portal Street Names** - Street names and descriptions (100% opacity)
5. **📐 LP-Portal Parcels** - Municipal property parcels (60% opacity)
6. **📍 LP-Portal Survey Markers** - Survey markers and reference points (80% opacity)
## 📁 Updated Files
### Core Map Components
- **`src/components/ui/LeafletMap.js`** - Main project map component ✅
- **`src/components/ui/EnhancedLeafletMap.js`** - Enhanced map component ✅
- **`src/components/ui/mapLayers.js`** - Layer configuration ✅
### Map Usage in Project
- **`src/app/projects/map/page.js`** - Projects map page (uses LeafletMap)
- **`src/components/ui/ProjectMap.js`** - Individual project maps (uses LeafletMap)
## 🚀 How It Works
### Layer Control
- **Layer Control Button** (📚) appears in top-right corner of maps
- **Base Layers** - Radio buttons (only one can be selected)
- **Overlay Layers** - Checkboxes (multiple can be selected)
### Transparency Handling
- **Base layers** are fully opaque (no transparency)
- **Overlay layers** have appropriate transparency levels:
- Cadastral data: Semi-transparent for property boundaries
- Planning data: Semi-transparent for zoning information
- Roads: Mostly opaque for visibility
- Street names: Fully opaque for text readability
- Parcels: Semi-transparent for boundary visualization
- Survey markers: Semi-transparent for reference points
### Automatic Integration
All existing project maps now have access to:
- Polish orthophoto layers
- Google satellite/road layers
- Polish government WMS overlays
- LP-Portal municipal data overlays
## 🎯 Benefits
1. **Enhanced Mapping Capabilities**: Rich selection of base layers for different use cases
2. **Polish-Specific Data**: Access to official Polish cadastral and planning data
3. **Transparency Support**: Overlays work correctly with transparency
4. **Maintained Performance**: Layers load efficiently and switch smoothly
5. **User-Friendly**: Clear naming with emojis for easy identification
## 🌍 Geographic Coverage
- **Polish Orthophoto**: Complete coverage of Poland
- **Polish Cadastral**: Official property boundaries across Poland
- **Polish Planning**: Zoning data where available
- **LP-Portal**: Municipal data (specific regions)
- **Google Layers**: Global coverage
- **Esri Satellite**: Global coverage
## 📱 Test Locations
Perfect locations to test all layers:
- **Kraków**: [50.0647, 19.9450] - Historic center with detailed cadastral data
- **Warszawa**: [52.2297, 21.0122] - Capital city with planning data
- **Gdańsk**: [54.3520, 18.6466] - Port city with orthophoto coverage
- **Wrocław**: [51.1079, 17.0385] - University city
- **Poznań**: [52.4064, 16.9252] - Industrial center
## 🔧 Technical Implementation
### WMTS Integration
- Polish orthophoto uses proper WMTS KVP format
- EPSG:3857 coordinate system for Leaflet compatibility
- Standard 256x256 tile size for optimal performance
### WMS Overlay Integration
- Transparent PNG format for overlays
- Proper parameter configuration for each service
- Optimized opacity levels for each overlay type
- Tiled requests for better performance
### React/Leaflet Architecture
- Uses `react-leaflet` components: `TileLayer` and `WMSTileLayer`
- Proper layer control with `BaseLayer` and `Overlay` components
- Icon fixes for marker display
- SSR-safe dynamic imports
## 🎉 Status: COMPLETE
✅ All Polish geospatial layers integrated
✅ Google layers integrated
✅ Transparency properly handled
✅ Layer control working
✅ Project maps updated
✅ Documentation complete
The main project maps now have comprehensive Polish geospatial capabilities with proper transparency support! 🚀

View File

@@ -1,47 +0,0 @@
# Merge Complete - auth2 to main
## Summary
Successfully merged the `auth2` branch into the `main` branch on **2024-12-29**.
## What was merged
The `auth2` branch contained extensive authentication and authorization features:
### Core Features Added:
1. **Authentication System** - Complete NextAuth.js implementation with database sessions
2. **Authorization & Access Control** - Role-based permissions (admin, user, guest)
3. **User Management** - Admin interface for user creation, editing, and role management
4. **Audit Logging** - Comprehensive logging of all user actions and system events
5. **Edge Runtime Compatibility** - Fixed SSR and build issues for production deployment
### Technical Improvements:
- **SSR Fixes** - Resolved all Server-Side Rendering issues with map components and auth pages
- **Build Optimization** - Project now builds cleanly without errors
- **UI/UX Preservation** - Maintained all original functionality, especially the projects map view
- **Security Enhancements** - Added middleware for route protection and audit logging
## Files Modified/Added:
- **98 files changed** with 9,544 additions and 658 deletions
- Major additions include authentication pages, admin interfaces, API routes, and middleware
- All test/debug pages moved to `debug-disabled/` folder to keep them out of production builds
## Verification:
✅ Build completed successfully (`npm run build`)
✅ Development server starts without errors (`npm run dev`)
✅ All pages load correctly, including `/projects/map`
✅ Original UI/UX functionality preserved
✅ Authentication and authorization working as expected
## Post-Merge Status:
- **Current branch**: `main`
- **Remote status**: All changes pushed to origin/main
- **Ready for production**: Yes, all SSR issues resolved
- **Authentication**: Fully functional with admin panel at `/admin/users`
## Next Steps:
1. Test the production deployment
2. Create initial admin user using `node scripts/create-admin.js`
3. Monitor audit logs for any issues
4. Consider cleaning up old test files in future iterations
---
*Merge completed by GitHub Copilot on 2024-12-29*

View File

@@ -1,90 +0,0 @@
# Branch Merge Preparation Summary
## ✅ Completed Tasks
### 1. Build Issues Fixed
- **SSR Issues**: Fixed server-side rendering issues with Leaflet map components
- **useSearchParams**: Added Suspense boundaries to all pages using useSearchParams
- **Dynamic Imports**: Implemented proper dynamic imports for map components
- **Build Success**: Project now builds successfully without errors
### 2. Code Quality Improvements
- **README Updated**: Comprehensive documentation reflecting current project state
- **Project Structure**: Updated project structure documentation
- **API Documentation**: Added complete API endpoint documentation
- **Clean Build**: All pages compile and build correctly
### 3. Debug Pages Management
- **Temporary Relocation**: Moved debug/test pages to `debug-disabled/` folder
- **Build Optimization**: Removed non-production pages from build process
- **Development Tools**: Preserved debug functionality for future development
### 4. Authentication & Authorization
- **Auth Pages Fixed**: All authentication pages now build correctly
- **Suspense Boundaries**: Proper error boundaries for auth components
- **Session Management**: Maintained existing auth functionality
## 🔍 Current State
### Build Status
-**npm run build**: Successful
-**34 pages**: All pages compile
-**Static Generation**: Working correctly
- ⚠️ **ESLint Warning**: Parser serialization issue (non-blocking)
### Branch Status
- **Branch**: `auth2`
- **Status**: Ready for merge to main
- **Commit**: `faeb1ca` - "Prepare branch for merge to main"
- **Files Changed**: 13 files modified/moved
## 🚀 Next Steps for Merge
### 1. Pre-merge Checklist
- [x] All build errors resolved
- [x] Documentation updated
- [x] Non-production code moved
- [x] Changes committed
- [ ] Final testing (recommended)
- [ ] Merge to main branch
### 2. Post-merge Tasks
- [ ] Re-enable debug pages if needed (move back from `debug-disabled/`)
- [ ] Fix ESLint parser configuration
- [ ] Add integration tests
- [ ] Deploy to production
### 3. Optional Improvements
- [ ] Fix ESLint configuration for better linting
- [ ] Add more comprehensive error handling
- [ ] Optimize bundle size
- [ ] Add more unit tests
## 📝 Files Modified
### Core Changes
- `README.md` - Updated comprehensive documentation
- `src/app/auth/error/page.js` - Added Suspense boundary
- `src/app/auth/signin/page.js` - Added Suspense boundary
- `src/app/projects/[id]/page.js` - Fixed dynamic import
- `src/app/projects/map/page.js` - Added Suspense boundary
- `src/components/ui/ClientProjectMap.js` - New client component wrapper
### Debug Pages (Temporarily Moved)
- `debug-disabled/debug-polish-orthophoto/` - Polish orthophoto debug
- `debug-disabled/test-polish-orthophoto/` - Polish orthophoto test
- `debug-disabled/test-polish-map/` - Polish map test
- `debug-disabled/test-improved-wmts/` - WMTS test
- `debug-disabled/comprehensive-polish-map/` - Comprehensive map test
## 🎯 Recommendation
**The branch is now ready for merge to main.** All critical build issues have been resolved, and the project builds successfully. The debug pages have been temporarily moved to prevent build issues while preserving their functionality for future development.
To proceed with the merge:
1. Switch to main branch: `git checkout main`
2. Merge auth2 branch: `git merge auth2`
3. Push to origin: `git push origin main`
4. Deploy if needed
The project is now in a stable state with comprehensive authentication, project management, and mapping functionality.

View File

@@ -1,139 +0,0 @@
# Polish Geospatial Layers Integration Guide
## 🎯 All 4+ Polish Layers Successfully Implemented!
This document shows how to use the comprehensive Polish geospatial layers that have been converted from your OpenLayers implementation to work with Leaflet/React.
## 📦 Available Components
### Complete Map Components
- `ComprehensivePolishMap.js` - Full-featured map with all layers
- `AdvancedPolishOrthophotoMap.js` - Advanced map with overlays
- `PolishOrthophotoMap.js` - Basic map with Polish orthophoto
### Individual Layer Components
- `PolishGeoLayers.js` - Individual layer components for custom integration
## 🗺️ Implemented Layers
### Base Layers (WMTS)
1. **Polish Orthophoto Standard Resolution**
- URL: `https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/StandardResolution`
- Format: JPEG, Max Zoom: 19
2. **Polish Orthophoto High Resolution**
- URL: `https://mapy.geoportal.gov.pl/wss/service/PZGIK/ORTO/WMTS/HighResolution`
- Format: JPEG, Max Zoom: 19
### Overlay Layers (WMS)
3. **Polish Cadastral Data (Działki)**
- Service: GUGiK Krajowa Integracja Ewidencji Gruntów
- Layers: Property boundaries, parcels, buildings
- Format: PNG (transparent)
4. **Polish Spatial Planning (MPZT)**
- Service: Geoportal Spatial Planning Integration
- Layers: Zoning, planning boundaries, land use
- Format: PNG (transparent)
### Additional LP-Portal Layers
5. **LP-Portal Roads**
6. **LP-Portal Street Names**
7. **LP-Portal Property Parcels**
8. **LP-Portal Survey Markers**
## 🚀 How to Use
### Option 1: Use Complete Component
```jsx
import ComprehensivePolishMap from '../components/ui/ComprehensivePolishMap';
export default function MyPage() {
return (
<div style={{ height: '500px' }}>
<ComprehensivePolishMap
center={[50.0647, 19.9450]} // Krakow
zoom={14}
markers={[]}
showLayerControl={true}
/>
</div>
);
}
```
### Option 2: Use Individual Layers
```jsx
import { MapContainer, LayersControl } from 'react-leaflet';
import {
PolishOrthophotoStandard,
PolishCadastralData,
LPPortalRoads
} from '../components/ui/PolishGeoLayers';
export default function CustomMap() {
const { BaseLayer, Overlay } = LayersControl;
return (
<MapContainer center={[50.0647, 19.9450]} zoom={14}>
<LayersControl>
<BaseLayer checked name="Polish Orthophoto">
<PolishOrthophotoStandard />
</BaseLayer>
<Overlay name="Property Boundaries">
<PolishCadastralData />
</Overlay>
<Overlay name="Roads">
<LPPortalRoads />
</Overlay>
</LayersControl>
</MapContainer>
);
}
```
## 📍 Test Locations
Good locations to test the layers:
- **Kraków**: [50.0647, 19.9450] - Historic center
- **Warszawa**: [52.2297, 21.0122] - Capital city
- **Gdańsk**: [54.3520, 18.6466] - Port city
- **Wrocław**: [51.1079, 17.0385] - University city
- **Poznań**: [52.4064, 16.9252] - Industrial center
## ⚙️ Technical Details
### WMTS Implementation
- Uses proper KVP (Key-Value Pair) URL format
- EPSG:3857 coordinate system for Leaflet compatibility
- Standard tile size (256x256)
### WMS Implementation
- Transparent PNG overlays
- Proper parameter configuration
- Tiled requests for better performance
### Performance Considerations
- All layers use standard web projections
- Optimized for React/Leaflet
- Minimal additional dependencies (only proj4 for future enhancements)
## 🎉 Success!
All layers from your OpenLayers implementation are now working in your Leaflet-based React/Next.js project:
✅ Polish Orthophoto (Standard & High-Res)
✅ Polish Cadastral Data (Property boundaries)
✅ Polish Spatial Planning (Zoning data)
✅ LP-Portal Municipal Data (Roads, names, parcels, surveys)
The implementation maintains the same functionality as your original OpenLayers code while being fully compatible with your existing React/Leaflet architecture.
## 📱 Test Pages Available
- `/comprehensive-polish-map` - Full featured map
- `/test-polish-map` - Basic comparison
- `/test-improved-wmts` - Technical testing

View File

@@ -1,18 +0,0 @@
import db from "./src/lib/db.js";
console.log("Adding can_be_assigned column to users table...");
// Add the new column
db.prepare(`
ALTER TABLE users
ADD COLUMN can_be_assigned INTEGER DEFAULT 1
`).run();
// Set admin users to not be assignable by default
db.prepare(`
UPDATE users
SET can_be_assigned = 0
WHERE role = 'admin'
`).run();
console.log("Migration completed. Admin users are now not assignable by default.");

View File

@@ -1,56 +0,0 @@
import { readFileSync } from "fs";
import Database from "better-sqlite3";
// Check database directly
const dbPath = "./data/database.sqlite";
const db = new Database(dbPath);
console.log("Checking audit logs table...\n");
// Check table schema
const schema = db
.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_logs'"
)
.get();
console.log("Table schema:");
console.log(schema?.sql || "Table not found");
console.log("\n" + "=".repeat(50) + "\n");
// Get some audit logs
const logs = db
.prepare("SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 5")
.all();
console.log(`Found ${logs.length} audit log entries:`);
logs.forEach((log, index) => {
console.log(`\n${index + 1}. ID: ${log.id}`);
console.log(` Timestamp: ${log.timestamp}`);
console.log(` User ID: ${log.user_id || "NULL"}`);
console.log(` Action: ${log.action}`);
console.log(` Resource Type: ${log.resource_type}`);
console.log(` Resource ID: ${log.resource_id || "N/A"}`);
console.log(` IP Address: ${log.ip_address || "N/A"}`);
console.log(` User Agent: ${log.user_agent || "N/A"}`);
console.log(` Details: ${log.details || "NULL"}`);
console.log(` Details type: ${typeof log.details}`);
});
// Count null user_ids
const nullUserCount = db
.prepare("SELECT COUNT(*) as count FROM audit_logs WHERE user_id IS NULL")
.get();
const totalCount = db.prepare("SELECT COUNT(*) as count FROM audit_logs").get();
console.log(`\n${"=".repeat(50)}`);
console.log(`Total audit logs: ${totalCount.count}`);
console.log(`Logs with NULL user_id: ${nullUserCount.count}`);
console.log(
`Percentage with NULL user_id: ${(
(nullUserCount.count / totalCount.count) *
100
).toFixed(2)}%`
);
db.close();

View File

@@ -1,13 +0,0 @@
import db from "./src/lib/db.js";
console.log("Checking projects table structure:");
const tableInfo = db.prepare("PRAGMA table_info(projects)").all();
console.log(JSON.stringify(tableInfo, null, 2));
// Check if created_at and updated_at columns exist
const hasCreatedAt = tableInfo.some((col) => col.name === "created_at");
const hasUpdatedAt = tableInfo.some((col) => col.name === "updated_at");
console.log("\nColumn existence check:");
console.log("created_at exists:", hasCreatedAt);
console.log("updated_at exists:", hasUpdatedAt);

View File

@@ -1,22 +0,0 @@
import db from './src/lib/db.js';
console.log('Checking contacts in database...\n');
const contacts = db.prepare('SELECT contact_id, name, phone, email, is_active, contact_type FROM contacts LIMIT 10').all();
console.log(`Total contacts found: ${contacts.length}\n`);
if (contacts.length > 0) {
console.log('Sample contacts:');
contacts.forEach(c => {
console.log(` ID: ${c.contact_id}, Name: ${c.name}, Phone: ${c.phone || 'N/A'}, Email: ${c.email || 'N/A'}, Active: ${c.is_active}, Type: ${c.contact_type}`);
});
} else {
console.log('No contacts found in database!');
}
const activeCount = db.prepare('SELECT COUNT(*) as count FROM contacts WHERE is_active = 1').get();
console.log(`\nActive contacts: ${activeCount.count}`);
const totalCount = db.prepare('SELECT COUNT(*) as count FROM contacts').get();
console.log(`Total contacts: ${totalCount.count}`);

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env node
import db from "./src/lib/db.js";
import { parseISO, isAfter, startOfDay, addDays } from "date-fns";
const today = startOfDay(new Date());
const threeDaysFromNow = addDays(today, 3);
const oneDayFromNow = addDays(today, 1);
console.log(`Today: ${today.toISOString().split('T')[0]}`);
console.log(`3 days from now: ${threeDaysFromNow.toISOString().split('T')[0]}`);
console.log(`1 day from now: ${oneDayFromNow.toISOString().split('T')[0]}`);
const projects = db.prepare(`
SELECT project_name, finish_date, project_status
FROM projects
WHERE finish_date IS NOT NULL
AND project_status != 'fulfilled'
AND project_status != 'cancelled'
ORDER BY finish_date ASC
`).all();
console.log(`\nFound ${projects.length} active projects with due dates:`);
projects.forEach(project => {
try {
const finishDate = parseISO(project.finish_date);
const finishDateStart = startOfDay(finishDate);
const isDueIn3Days = finishDateStart.getTime() === threeDaysFromNow.getTime();
const isDueIn1Day = finishDateStart.getTime() === oneDayFromNow.getTime();
const isOverdue = isAfter(today, finishDateStart);
let status = '';
if (isDueIn3Days) status = '⚠️ DUE IN 3 DAYS';
else if (isDueIn1Day) status = '🚨 DUE IN 1 DAY';
else if (isOverdue) status = '❌ OVERDUE';
else status = '📅 Future';
console.log(`${status} - ${project.project_name}: ${project.finish_date.split('T')[0]} (${project.project_status})`);
} catch (error) {
console.log(`❌ Error parsing date for ${project.project_name}: ${project.finish_date}`);
}
});

View File

@@ -1,5 +0,0 @@
import db from "./src/lib/db.js";
console.log("Current projects table structure:");
const tableInfo = db.prepare("PRAGMA table_info(projects)").all();
console.log(JSON.stringify(tableInfo, null, 2));

View File

@@ -1,32 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
// Check table structures first
console.log("Users table structure:");
const usersSchema = db.prepare("PRAGMA table_info(users)").all();
console.log(usersSchema);
console.log("\nProjects table structure:");
const projectsSchema = db.prepare("PRAGMA table_info(projects)").all();
console.log(projectsSchema);
// Check if there are any projects
const projects = db
.prepare(
`
SELECT p.*,
creator.name as created_by_name,
assignee.name as assigned_to_name
FROM projects p
LEFT JOIN users creator ON p.created_by = creator.id
LEFT JOIN users assignee ON p.assigned_to = assignee.id
LIMIT 5
`
)
.all();
console.log("\nProjects in database:");
console.log(JSON.stringify(projects, null, 2));
db.close();

View File

@@ -1,10 +0,0 @@
import db from "./src/lib/db.js";
console.log("Database schema for notes table:");
console.log(db.prepare("PRAGMA table_info(notes)").all());
console.log("\nDatabase schema for project_tasks table:");
console.log(db.prepare("PRAGMA table_info(project_tasks)").all());
console.log("\nSample notes to check is_system column:");
console.log(db.prepare("SELECT * FROM notes LIMIT 5").all());

View File

@@ -1,25 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("Project Tasks table structure:");
const projectTasksSchema = db.prepare("PRAGMA table_info(project_tasks)").all();
console.table(projectTasksSchema);
console.log("\nSample project tasks with user tracking:");
const tasks = db
.prepare(
`
SELECT pt.*,
creator.name as created_by_name,
assignee.name as assigned_to_name
FROM project_tasks pt
LEFT JOIN users creator ON pt.created_by = creator.id
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
LIMIT 3
`
)
.all();
console.table(tasks);
db.close();

View File

@@ -1,355 +0,0 @@
"use client";
import { useState } from 'react';
import dynamic from 'next/dynamic';
const ComprehensivePolishMap = dynamic(
() => import('../../components/ui/ComprehensivePolishMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
export default function ComprehensivePolishMapPage() {
const [selectedLocation, setSelectedLocation] = useState('krakow');
// Different locations to test the layers
const locations = {
krakow: {
center: [50.0647, 19.9450],
zoom: 14,
name: "Kraków",
description: "Historic city center with good cadastral data coverage"
},
warsaw: {
center: [52.2297, 21.0122],
zoom: 14,
name: "Warszawa",
description: "Capital city with extensive planning data"
},
gdansk: {
center: [54.3520, 18.6466],
zoom: 14,
name: "Gdańsk",
description: "Port city with detailed property boundaries"
},
wroclaw: {
center: [51.1079, 17.0385],
zoom: 14,
name: "Wrocław",
description: "University city with good orthophoto coverage"
},
poznan: {
center: [52.4064, 16.9252],
zoom: 14,
name: "Poznań",
description: "Industrial center with road network data"
}
};
const currentLocation = locations[selectedLocation];
// Test markers for selected location
const testMarkers = [
{
position: currentLocation.center,
popup: `${currentLocation.name} - ${currentLocation.description}`
}
];
return (
<div className="min-h-screen bg-gray-100">
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold text-gray-800 mb-6">
🇵🇱 Comprehensive Polish Geospatial Data Platform
</h1>
<div className="bg-green-50 border border-green-200 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold text-green-800 mb-3">
All Polish Layers Implementation Complete! 🎉
</h2>
<p className="text-green-700 mb-4">
This comprehensive map includes all layers from your OpenLayers implementation,
converted to work seamlessly with your Leaflet-based React/Next.js project.
</p>
<div className="grid md:grid-cols-2 gap-4 text-sm">
<div>
<strong className="text-green-800">Base Layers:</strong>
<ul className="mt-1 text-green-700">
<li> Polish Orthophoto (Standard & High Resolution)</li>
<li> OpenStreetMap, Google Maps, Esri Satellite</li>
</ul>
</div>
<div>
<strong className="text-green-800">Overlay Layers:</strong>
<ul className="mt-1 text-green-700">
<li> Cadastral Data, Spatial Planning</li>
<li> LP-Portal Roads, Street Names, Parcels, Surveys</li>
</ul>
</div>
</div>
</div>
{/* Location Selector */}
<div className="bg-white rounded-lg shadow-lg p-4 mb-6">
<h3 className="text-lg font-semibold text-gray-800 mb-3">
🎯 Select Test Location:
</h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{Object.entries(locations).map(([key, location]) => (
<button
key={key}
onClick={() => setSelectedLocation(key)}
className={`px-3 py-2 rounded-lg text-sm transition-colors ${
selectedLocation === key
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{location.name}
</button>
))}
</div>
<p className="text-sm text-gray-600 mt-2">
<strong>Current:</strong> {currentLocation.description}
</p>
</div>
{/* Map Container */}
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="p-4 bg-blue-600 text-white">
<h2 className="text-xl font-semibold">
Interactive Map: {currentLocation.name}
</h2>
<p className="text-blue-100 mt-2">
Use the layer control (top-right) to toggle between base layers and enable overlay layers.
Combine orthophoto with cadastral data for detailed property analysis.
</p>
</div>
<div className="h-96 md:h-[700px]">
<ComprehensivePolishMap
key={selectedLocation} // Force re-render when location changes
center={currentLocation.center}
zoom={currentLocation.zoom}
markers={testMarkers}
showLayerControl={true}
/>
</div>
</div>
{/* Layer Information */}
<div className="mt-8 grid md:grid-cols-2 gap-6">
{/* Base Layers */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4 flex items-center">
🗺 Base Layers
</h3>
<div className="space-y-3 text-sm">
<div className="flex items-start">
<span className="w-4 h-4 bg-green-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>Polish Orthophoto (Standard)</strong>
<p className="text-gray-600 mt-1">High-quality aerial imagery from Polish Geoportal</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-emerald-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>Polish Orthophoto (High Resolution)</strong>
<p className="text-gray-600 mt-1">Ultra-high resolution aerial imagery for detailed analysis</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-blue-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>OpenStreetMap</strong>
<p className="text-gray-600 mt-1">Community-driven map data</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-red-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>Google Maps</strong>
<p className="text-gray-600 mt-1">Satellite imagery and road overlay</p>
</div>
</div>
</div>
</div>
{/* Overlay Layers */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4 flex items-center">
📊 Overlay Layers
</h3> <div className="space-y-3 text-sm">
<div className="flex items-start">
<span className="w-4 h-4 bg-orange-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>📋 Polish Cadastral Data</strong>
<p className="text-gray-600 mt-1">Property boundaries, parcels, and building outlines</p>
<p className="text-xs text-gray-500">Opacity: 80% - Semi-transparent overlay</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-purple-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>🏗 Polish Spatial Planning</strong>
<p className="text-gray-600 mt-1">Zoning data and urban planning information</p>
<p className="text-xs text-gray-500">Opacity: 70% - Semi-transparent overlay</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-teal-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>🛣 LP-Portal Roads</strong>
<p className="text-gray-600 mt-1">Detailed road network data</p>
<p className="text-xs text-gray-500">Opacity: 90% - Mostly opaque for visibility</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-indigo-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>🏷 LP-Portal Street Names</strong>
<p className="text-gray-600 mt-1">Street names and road descriptions</p>
<p className="text-xs text-gray-500">Opacity: 100% - Fully opaque for readability</p>
</div>
</div>
<div className="flex items-start">
<span className="w-4 h-4 bg-pink-500 rounded-full mr-3 mt-0.5 flex-shrink-0"></span>
<div>
<strong>📐 LP-Portal Parcels & Surveys</strong>
<p className="text-gray-600 mt-1">Property parcels and survey markers</p>
<p className="text-xs text-gray-500">Opacity: 60-80% - Variable transparency</p>
</div>
</div>
</div>
</div>
</div>
{/* Transparency Information */}
<div className="mt-8 bg-green-50 border border-green-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-green-800 mb-4">
🎨 Layer Transparency Handling
</h3>
<div className="grid md:grid-cols-2 gap-6 text-sm">
<div>
<h4 className="font-semibold text-green-700 mb-3">Base Layers (Opaque):</h4>
<div className="space-y-2">
<div className="flex justify-between">
<span>Polish Orthophoto</span>
<span className="bg-green-200 px-2 py-1 rounded text-xs">100% Opaque</span>
</div>
<div className="flex justify-between">
<span>Google Satellite/Roads</span>
<span className="bg-green-200 px-2 py-1 rounded text-xs">100% Opaque</span>
</div>
</div>
</div>
<div>
<h4 className="font-semibold text-green-700 mb-3">Overlay Layers (Transparent):</h4>
<div className="space-y-2">
<div className="flex justify-between">
<span>📋 Cadastral Data</span>
<span className="bg-yellow-200 px-2 py-1 rounded text-xs">80% Opacity</span>
</div>
<div className="flex justify-between">
<span>🏗 Spatial Planning</span>
<span className="bg-yellow-200 px-2 py-1 rounded text-xs">70% Opacity</span>
</div>
<div className="flex justify-between">
<span>🛣 Roads</span>
<span className="bg-blue-200 px-2 py-1 rounded text-xs">90% Opacity</span>
</div>
<div className="flex justify-between">
<span>🏷 Street Names</span>
<span className="bg-green-200 px-2 py-1 rounded text-xs">100% Opacity</span>
</div>
<div className="flex justify-between">
<span>📐 Parcels</span>
<span className="bg-orange-200 px-2 py-1 rounded text-xs">60% Opacity</span>
</div>
<div className="flex justify-between">
<span>📍 Survey Markers</span>
<span className="bg-yellow-200 px-2 py-1 rounded text-xs">80% Opacity</span>
</div>
</div>
</div>
</div>
<div className="mt-4 p-3 bg-green-100 rounded">
<p className="text-green-800 text-sm">
<strong>Smart Transparency:</strong> Each overlay layer has been optimized with appropriate transparency levels.
Property boundaries are semi-transparent (60-80%) so you can see the underlying imagery,
while text labels are fully opaque (100%) for maximum readability.
</p>
</div>
</div>
{/* Usage Guide */}
<div className="mt-8 bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-blue-800 mb-4">
📋 How to Use This Comprehensive Map
</h3>
<div className="grid md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold text-blue-700 mb-2">Basic Navigation:</h4>
<ul className="text-blue-600 space-y-1 text-sm">
<li> Use mouse wheel to zoom in/out</li>
<li> Click and drag to pan around</li>
<li> Use layer control (top-right) to switch layers</li>
<li> Select different Polish cities above to test</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-blue-700 mb-2">Advanced Features:</h4>
<ul className="text-blue-600 space-y-1 text-sm">
<li> Combine orthophoto with cadastral overlay</li>
<li> Enable multiple overlays simultaneously</li>
<li> Use high-resolution orthophoto for detail work</li>
<li> Compare with Google/OSM base layers</li>
</ul>
</div>
</div>
</div>
{/* Technical Implementation */}
<div className="mt-8 bg-gray-50 border border-gray-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Technical Implementation Details
</h3>
<div className="grid md:grid-cols-3 gap-6 text-sm">
<div>
<h4 className="font-semibold text-gray-700 mb-2">WMTS Integration:</h4>
<ul className="text-gray-600 space-y-1">
<li> Proper KVP URL construction</li>
<li> EPSG:3857 coordinate system</li>
<li> Standard and high-res orthophoto</li>
<li> Multiple format support (JPEG/PNG)</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-700 mb-2">WMS Overlays:</h4>
<ul className="text-gray-600 space-y-1">
<li> Polish government services</li>
<li> LP-Portal municipal data</li>
<li> Transparent overlay support</li>
<li> Multiple layer combinations</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-700 mb-2">React/Leaflet:</h4>
<ul className="text-gray-600 space-y-1">
<li> React-Leaflet component integration</li>
<li> Dynamic layer switching</li>
<li> Responsive design</li>
<li> Performance optimized</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,9 +0,0 @@
// Temporarily disabled debug pages during build
// These pages are for development/testing purposes only
// To re-enable, rename this file to layout.js
export default function DebugLayout({ children }) {
return children;
}
export const dynamic = 'force-dynamic';

View File

@@ -1,113 +0,0 @@
"use client";
import dynamic from 'next/dynamic';
const DebugPolishOrthophotoMap = dynamic(
() => import('../../components/ui/DebugPolishOrthophotoMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
export const dynamicParams = true;
export default function DebugPolishOrthophotoPage() {
// Test marker in Poland
const testMarkers = [
{
position: [50.0647, 19.9450], // Krakow
popup: "Kraków - Test Location"
}
];
return (
<div className="min-h-screen bg-gray-100">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-800 mb-6">
Debug Polish Geoportal Orthophoto
</h1>
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<h2 className="text-lg font-semibold text-red-800 mb-2">
Debug Mode Active
</h2>
<p className="text-red-700">
This page tests multiple URL formats for Polish Geoportal orthophoto tiles.
Check the browser console and the debug panel on the map for network request information.
</p>
</div>
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="p-4 bg-blue-600 text-white">
<h2 className="text-xl font-semibold">Debug Map with Multiple Orthophoto Options</h2>
<p className="text-blue-100 mt-2">
Try switching between different Polish orthophoto options using the layer control.
Google layers are included as working references.
</p>
</div>
<div className="h-96 md:h-[600px]">
<DebugPolishOrthophotoMap
center={[50.0647, 19.9450]} // Centered on Krakow
zoom={12}
markers={testMarkers}
/>
</div>
</div>
<div className="mt-8 bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
URL Formats Being Tested:
</h3>
<div className="space-y-4 text-sm"> <div className="bg-gray-50 p-3 rounded">
<strong>Option 1 (WMTS KVP EPSG:3857):</strong>
<code className="block mt-1 text-xs">
?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTO&STYLE=default&TILEMATRIXSET=EPSG:3857&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image/jpeg
</code>
<span className="text-gray-600">Standard Web Mercator projection</span>
</div>
<div className="bg-gray-50 p-3 rounded">
<strong>Option 2 (WMTS KVP EPSG:2180):</strong>
<code className="block mt-1 text-xs">
?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTO&STYLE=default&TILEMATRIXSET=EPSG:2180&TILEMATRIX=EPSG:2180:{z}&TILEROW={y}&TILECOL={x}&FORMAT=image/jpeg
</code>
<span className="text-gray-600">Polish coordinate system</span>
</div>
<div className="bg-gray-50 p-3 rounded">
<strong>Option 3 (Alternative TILEMATRIXSET):</strong>
<code className="block mt-1 text-xs">
?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTO&STYLE=default&TILEMATRIXSET=GoogleMapsCompatible&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image/jpeg
</code>
<span className="text-gray-600">Google Maps compatible matrix</span>
</div>
<div className="bg-gray-50 p-3 rounded">
<strong>Option 4 (PNG format):</strong>
<code className="block mt-1 text-xs">
?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTO&STYLE=default&TILEMATRIXSET=EPSG:3857&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image/png
</code>
<span className="text-gray-600">PNG format instead of JPEG</span>
</div>
</div>
</div>
<div className="mt-8 bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">
Debug Instructions:
</h3>
<ol className="text-yellow-700 space-y-2">
<li><strong>1.</strong> Open browser Developer Tools (F12) and go to Network tab</li>
<li><strong>2.</strong> Switch between different Polish orthophoto options in the layer control</li>
<li><strong>3.</strong> Look for requests to geoportal.gov.pl in the Network tab</li>
<li><strong>4.</strong> Check the debug panel on the map for request/response info</li>
<li><strong>5.</strong> Note which options return 200 OK vs 404/403 errors</li>
<li><strong>6.</strong> Compare with working Google layers</li>
</ol>
</div>
</div>
</div>
);
}

View File

@@ -1,107 +0,0 @@
"use client";
import dynamic from 'next/dynamic';
const ImprovedPolishOrthophotoMap = dynamic(
() => import('../../components/ui/ImprovedPolishOrthophotoMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
export default function ImprovedPolishOrthophotoPage() {
const testMarkers = [
{
position: [50.0647, 19.9450], // Krakow
popup: "Kraków - Testing WMTS"
}
];
return (
<div className="min-h-screen bg-gray-100">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-800 mb-6">
Improved Polish WMTS Implementation
</h1>
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
<h2 className="text-lg font-semibold text-green-800 mb-2">
Custom WMTS Layer Implementation
</h2>
<p className="text-green-700">
This version uses a custom WMTS layer that properly constructs KVP URLs based on the GetCapabilities response.
Check the debug panel on the map to see the actual requests being made.
</p>
</div>
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="p-4 bg-blue-600 text-white">
<h2 className="text-xl font-semibold">Custom WMTS Layer with Proper KVP URLs</h2>
<p className="text-blue-100 mt-2">
This implementation builds proper WMTS GetTile requests with all required parameters.
Monitor the debug panel and browser network tab for request details.
</p>
</div>
<div className="h-96 md:h-[600px]">
<ImprovedPolishOrthophotoMap
center={[50.0647, 19.9450]}
zoom={12}
markers={testMarkers}
/>
</div>
</div>
<div className="mt-8 bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
WMTS Parameters Being Tested:
</h3>
<div className="grid md:grid-cols-2 gap-4 text-sm">
<div className="bg-gray-50 p-3 rounded">
<strong>Tile Matrix Sets Available:</strong>
<ul className="mt-2 space-y-1">
<li> EPSG:3857 (Web Mercator)</li>
<li> EPSG:4326 (WGS84)</li>
<li> EPSG:2180 (Polish National Grid)</li>
</ul>
</div>
<div className="bg-gray-50 p-3 rounded">
<strong>Formats Available:</strong>
<ul className="mt-2 space-y-1">
<li> image/jpeg (default)</li>
<li> image/png</li>
</ul>
</div>
</div>
</div>
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-blue-800 mb-2">
Testing Instructions:
</h3>
<ol className="text-blue-700 space-y-2">
<li><strong>1.</strong> Open Browser Developer Tools (F12) Network tab</li>
<li><strong>2.</strong> Filter by "geoportal.gov.pl" to see WMTS requests</li>
<li><strong>3.</strong> Switch between different Polish WMTS options</li>
<li><strong>4.</strong> Check if requests return 200 OK or error codes</li>
<li><strong>5.</strong> Compare with Google Satellite (known working)</li>
<li><strong>6.</strong> Monitor the debug panel for request URLs</li>
</ol>
</div>
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">
Expected Behavior:
</h3>
<p className="text-yellow-700">
If the Polish orthophoto tiles appear, you should see aerial imagery of Poland.
If they don't load, check the network requests - they should show proper WMTS GetTile URLs
with all required parameters (SERVICE, REQUEST, LAYER, TILEMATRIXSET, etc.).
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,217 +0,0 @@
"use client";
import { useState } from 'react';
import dynamic from 'next/dynamic';
const PolishOrthophotoMap = dynamic(
() => import('../../components/ui/PolishOrthophotoMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
const AdvancedPolishOrthophotoMap = dynamic(
() => import('../../components/ui/AdvancedPolishOrthophotoMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
export default function PolishOrthophotoTestPage() {
const [activeMap, setActiveMap] = useState('basic');
// Test markers - various locations in Poland
const testMarkers = [
{
position: [50.0647, 19.9450], // Krakow
popup: "Kraków - Main Market Square"
},
{
position: [52.2297, 21.0122], // Warsaw
popup: "Warszawa - Palace of Culture and Science"
},
{
position: [54.3520, 18.6466], // Gdansk
popup: "Gdańsk - Old Town"
}
];
return (
<div className="min-h-screen bg-gray-100">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-800 mb-6">
Polish Geoportal Orthophoto Integration
</h1>
{/* Map Type Selector */}
<div className="mb-6 bg-white rounded-lg shadow-lg p-4">
<h2 className="text-lg font-semibold text-gray-800 mb-3">
Choose Map Implementation:
</h2>
<div className="flex space-x-4">
<button
onClick={() => setActiveMap('basic')}
className={`px-4 py-2 rounded-lg transition-colors ${
activeMap === 'basic'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
Basic Polish Orthophoto
</button>
<button
onClick={() => setActiveMap('advanced')}
className={`px-4 py-2 rounded-lg transition-colors ${
activeMap === 'advanced'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
Advanced with WMS Overlays
</button>
</div>
</div>
{/* Map Container */}
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="p-4 bg-blue-600 text-white">
<h2 className="text-xl font-semibold">
{activeMap === 'basic'
? 'Basic Polish Orthophoto Map'
: 'Advanced Polish Orthophoto with WMS Overlays'
}
</h2>
<p className="text-blue-100 mt-2">
{activeMap === 'basic'
? 'Demonstrates working Polish Geoportal orthophoto tiles with multiple base layer options.'
: 'Advanced version includes Polish cadastral data (działki) and spatial planning (MPZT) as overlay layers.'
}
</p>
</div>
<div className="h-96 md:h-[600px]">
{activeMap === 'basic' ? (
<PolishOrthophotoMap
center={[50.0647, 19.9450]} // Centered on Krakow
zoom={12}
markers={testMarkers}
showLayerControl={true}
/>
) : (
<AdvancedPolishOrthophotoMap
center={[50.0647, 19.9450]} // Centered on Krakow
zoom={12}
markers={testMarkers}
showLayerControl={true}
/>
)}
</div>
</div>
{/* Features Overview */}
<div className="mt-8 grid md:grid-cols-2 gap-6">
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Basic Map Features:
</h3>
<ul className="space-y-2 text-gray-600">
<li className="flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-3"></span>
Polish Geoportal Orthophoto (Working)
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-blue-500 rounded-full mr-3"></span>
OpenStreetMap base layer
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-red-500 rounded-full mr-3"></span>
Google Satellite imagery
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-yellow-500 rounded-full mr-3"></span>
Google Roads overlay
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-purple-500 rounded-full mr-3"></span>
Esri World Imagery
</li>
</ul>
</div>
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Advanced Map Features:
</h3>
<ul className="space-y-2 text-gray-600">
<li className="flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-3"></span>
Standard & High Resolution Orthophoto
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-orange-500 rounded-full mr-3"></span>
Polish Cadastral Data (WMS)
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-teal-500 rounded-full mr-3"></span>
Spatial Planning Data (MPZT)
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-indigo-500 rounded-full mr-3"></span>
Overlay layer support
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-pink-500 rounded-full mr-3"></span>
Multiple base layers
</li>
</ul>
</div>
</div>
{/* Technical Implementation Details */}
<div className="mt-8 bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Technical Implementation:
</h3>
<div className="grid md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold text-gray-700 mb-2">Key Improvements:</h4>
<ul className="text-sm text-gray-600 space-y-1">
<li> Uses REST tile service instead of WMTS for better compatibility</li>
<li> Proper tile size (512px) with zoomOffset=-1</li>
<li> proj4 integration for EPSG:2180 coordinate system</li>
<li> Multiple fallback layers for reliability</li>
<li> WMS overlay support for cadastral data</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-700 mb-2">Based on OpenLayers Code:</h4>
<ul className="text-sm text-gray-600 space-y-1">
<li> Converted from OpenLayers to Leaflet implementation</li>
<li> Maintains same layer structure and URLs</li>
<li> Includes Polish projection definitions</li>
<li> Compatible with existing React/Next.js setup</li>
<li> Extensible for additional WMS services</li>
</ul>
</div>
</div>
</div>
{/* Usage Instructions */}
<div className="mt-8 bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-blue-800 mb-2">
How to Use:
</h3>
<div className="text-blue-700 space-y-2">
<p><strong>1.</strong> Use the layer control (top-right) to switch between base layers</p>
<p><strong>2.</strong> In advanced mode, enable overlay layers for cadastral/planning data</p>
<p><strong>3.</strong> Click on markers to see location information</p>
<p><strong>4.</strong> Zoom in to see high-resolution orthophoto details</p>
<p><strong>5.</strong> Combine orthophoto with cadastral overlay for property boundaries</p>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,106 +0,0 @@
"use client";
import dynamic from 'next/dynamic';
const PolishOrthophotoMap = dynamic(
() => import('../../components/ui/PolishOrthophotoMap'),
{
ssr: false,
loading: () => <div className="flex items-center justify-center h-96">Loading map...</div>
}
);
export default function TestPolishOrthophotoPage() {
// Test markers - various locations in Poland
const testMarkers = [
{
position: [50.0647, 19.9450], // Krakow
popup: "Kraków - Main Market Square"
},
{
position: [52.2297, 21.0122], // Warsaw
popup: "Warszawa - Palace of Culture and Science"
},
{
position: [54.3520, 18.6466], // Gdansk
popup: "Gdańsk - Old Town"
}
];
return (
<div className="min-h-screen bg-gray-100">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-800 mb-6">
Polish Geoportal Orthophoto Map Test
</h1>
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="p-4 bg-blue-600 text-white">
<h2 className="text-xl font-semibold">Interactive Map with Polish Orthophoto</h2>
<p className="text-blue-100 mt-2">
This map demonstrates working Polish Geoportal orthophoto tiles.
Use the layer control (top-right) to switch between different map layers.
</p>
</div>
<div className="h-96 md:h-[600px]">
<PolishOrthophotoMap
center={[50.0647, 19.9450]} // Centered on Krakow
zoom={12}
markers={testMarkers}
showLayerControl={true}
/>
</div>
</div>
<div className="mt-8 bg-white rounded-lg shadow-lg p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Map Layers Available:
</h3>
<ul className="space-y-2 text-gray-600">
<li className="flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-3"></span>
<strong>Polish Geoportal Orthophoto:</strong> High-resolution aerial imagery from Polish Geoportal
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-blue-500 rounded-full mr-3"></span>
<strong>OpenStreetMap:</strong> Standard OpenStreetMap tiles
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-red-500 rounded-full mr-3"></span>
<strong>Google Satellite:</strong> Google satellite imagery
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-yellow-500 rounded-full mr-3"></span>
<strong>Google Roads:</strong> Google road overlay
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-purple-500 rounded-full mr-3"></span>
<strong>Esri Satellite:</strong> Esri world imagery
</li>
</ul>
</div>
<div className="mt-8 bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">
Implementation Notes:
</h3>
<div className="text-yellow-700 space-y-2">
<p>
The Polish Geoportal orthophoto uses REST tile service instead of WMTS for better compatibility
</p>
<p>
Tile size is set to 512px with zoomOffset=-1 for proper tile alignment
</p>
<p>
proj4 library is included for coordinate system transformations (EPSG:2180)
</p>
<p>
Multiple fallback layers are provided for comparison and reliability
</p>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,11 +0,0 @@
// Debug file to test dropdown functionality
console.log("Testing dropdown components...");
// Simple test to check if components are rendering
const testTask = {
id: 1,
status: "pending",
task_name: "Test Task",
};
console.log("Test task:", testTask);

View File

@@ -1,49 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("Project Tasks table columns:");
const projectTasksSchema = db.prepare("PRAGMA table_info(project_tasks)").all();
projectTasksSchema.forEach((col) => {
console.log(
`${col.name}: ${col.type} (${col.notnull ? "NOT NULL" : "NULL"})`
);
});
console.log("\nChecking if created_at and updated_at columns exist...");
const hasCreatedAt = projectTasksSchema.some(
(col) => col.name === "created_at"
);
const hasUpdatedAt = projectTasksSchema.some(
(col) => col.name === "updated_at"
);
console.log("created_at exists:", hasCreatedAt);
console.log("updated_at exists:", hasUpdatedAt);
// Let's try a simple insert to see what happens
console.log("\nTesting manual insert...");
try {
const result = db
.prepare(
`
INSERT INTO project_tasks (
project_id, task_template_id, status, priority,
created_by, assigned_to, created_at, updated_at
)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`
)
.run(1, 1, "pending", "normal", "test-user", "test-user");
console.log("Insert successful, ID:", result.lastInsertRowid);
// Clean up
db.prepare("DELETE FROM project_tasks WHERE id = ?").run(
result.lastInsertRowid
);
console.log("Test record cleaned up");
} catch (error) {
console.error("Insert failed:", error.message);
}
db.close();

View File

@@ -1,60 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("Adding user tracking columns to notes table...\n");
try {
console.log("Adding created_by column...");
db.exec(`ALTER TABLE notes ADD COLUMN created_by TEXT;`);
console.log("✓ created_by column added");
} catch (e) {
console.log("created_by column already exists or error:", e.message);
}
try {
console.log("Adding is_system column...");
db.exec(`ALTER TABLE notes ADD COLUMN is_system INTEGER DEFAULT 0;`);
console.log("✓ is_system column added");
} catch (e) {
console.log("is_system column already exists or error:", e.message);
}
console.log("\nVerifying columns were added...");
const schema = db.prepare("PRAGMA table_info(notes)").all();
const hasCreatedBy = schema.some((col) => col.name === "created_by");
const hasIsSystem = schema.some((col) => col.name === "is_system");
console.log("created_by exists:", hasCreatedBy);
console.log("is_system exists:", hasIsSystem);
if (hasCreatedBy && hasIsSystem) {
console.log("\n✅ All columns are now present!");
// Test a manual insert
console.log("\nTesting manual note insert...");
try {
const result = db
.prepare(
`
INSERT INTO notes (project_id, note, created_by, is_system, note_date)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
`
)
.run(1, "Test note with user tracking", "test-user-id", 0);
console.log("Insert successful, ID:", result.lastInsertRowid);
// Clean up
db.prepare("DELETE FROM notes WHERE note_id = ?").run(
result.lastInsertRowid
);
console.log("Test record cleaned up");
} catch (error) {
console.error("Insert failed:", error.message);
}
} else {
console.log("\n❌ Some columns are still missing");
}
db.close();

View File

@@ -1,37 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("Adding missing columns to project_tasks table...\n");
try {
console.log("Adding created_at column...");
db.exec(`ALTER TABLE project_tasks ADD COLUMN created_at TEXT;`);
console.log("✓ created_at column added");
} catch (e) {
console.log("created_at column already exists or error:", e.message);
}
try {
console.log("Adding updated_at column...");
db.exec(`ALTER TABLE project_tasks ADD COLUMN updated_at TEXT;`);
console.log("✓ updated_at column added");
} catch (e) {
console.log("updated_at column already exists or error:", e.message);
}
console.log("\nVerifying columns were added...");
const schema = db.prepare("PRAGMA table_info(project_tasks)").all();
const hasCreatedAt = schema.some((col) => col.name === "created_at");
const hasUpdatedAt = schema.some((col) => col.name === "updated_at");
console.log("created_at exists:", hasCreatedAt);
console.log("updated_at exists:", hasUpdatedAt);
if (hasCreatedAt && hasUpdatedAt) {
console.log("\n✅ All columns are now present!");
} else {
console.log("\n❌ Some columns are still missing");
}
db.close();

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
import db from "./src/lib/db.js";
export default function migrateAddCompletionDate() {
try {
// First, check if actual_completion_date exists and rename it to completion_date
const columns = db.prepare("PRAGMA table_info(projects)").all();
const hasActualCompletionDate = columns.some(col => col.name === 'actual_completion_date');
const hasCompletionDate = columns.some(col => col.name === 'completion_date');
if (hasActualCompletionDate && !hasCompletionDate) {
// Rename the column
db.exec(`
ALTER TABLE projects RENAME COLUMN actual_completion_date TO completion_date;
`);
console.log("Migration completed: Renamed actual_completion_date to completion_date");
} else if (!hasActualCompletionDate && !hasCompletionDate) {
// Add the column if it doesn't exist
db.exec(`
ALTER TABLE projects ADD COLUMN completion_date TEXT;
`);
console.log("Migration completed: Added completion_date column to projects table");
} else if (hasCompletionDate) {
console.log("Migration skipped: completion_date column already exists");
}
} catch (error) {
console.error("Migration failed:", error);
throw error;
}
}

View File

@@ -1,38 +0,0 @@
import db from "./src/lib/db.js";
// Migration to add docx_templates table
const migration = () => {
console.log("Running migration: add-docx-templates-table");
try {
db.exec(`
-- Table: docx_templates
CREATE TABLE IF NOT EXISTS docx_templates (
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
template_name TEXT NOT NULL,
description TEXT,
original_filename TEXT NOT NULL,
stored_filename TEXT NOT NULL,
file_path TEXT NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
is_active INTEGER DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
created_by TEXT,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id)
);
-- Indexes for templates
CREATE INDEX IF NOT EXISTS idx_docx_templates_active ON docx_templates(is_active);
CREATE INDEX IF NOT EXISTS idx_docx_templates_created_by ON docx_templates(created_by);
`);
console.log("Migration completed successfully");
} catch (error) {
console.error("Migration failed:", error);
throw error;
}
};
migration();

View File

@@ -1,27 +0,0 @@
import db from "./src/lib/db.js";
export default function migrateAddEditedAtToNotes() {
try {
// Check if edited_at column already exists
const columns = db.prepare("PRAGMA table_info(notes)").all();
const hasEditedAt = columns.some(col => col.name === 'edited_at');
if (!hasEditedAt) {
// Add the edited_at column
db.exec(`
ALTER TABLE notes ADD COLUMN edited_at TEXT;
`);
console.log("Migration completed: Added edited_at column to notes table");
} else {
console.log("Migration skipped: edited_at column already exists");
}
} catch (error) {
console.error("Migration failed:", error);
throw error;
}
}
// Run the migration if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
migrateAddEditedAtToNotes();
}

View File

@@ -1,43 +0,0 @@
import Database from "better-sqlite3";
// Migration script to add 'initial' column to users table
// Run this on your live server to apply the database changes
const dbPath = process.argv[2] || "./data/database.sqlite"; // Allow custom path via command line
console.log(`Applying migration to database: ${dbPath}`);
try {
const db = new Database(dbPath);
// Check if initial column already exists
const schema = db.prepare("PRAGMA table_info(users)").all();
const hasInitialColumn = schema.some(column => column.name === 'initial');
if (hasInitialColumn) {
console.log("✅ Initial column already exists in users table");
} else {
// Add the initial column
db.prepare("ALTER TABLE users ADD COLUMN initial TEXT").run();
console.log("✅ Added 'initial' column to users table");
}
// Verify the column was added
const updatedSchema = db.prepare("PRAGMA table_info(users)").all();
const initialColumn = updatedSchema.find(column => column.name === 'initial');
if (initialColumn) {
console.log("✅ Migration completed successfully");
console.log(`Column details: ${JSON.stringify(initialColumn, null, 2)}`);
} else {
console.error("❌ Migration failed - initial column not found");
process.exit(1);
}
db.close();
console.log("Database connection closed");
} catch (error) {
console.error("❌ Migration failed:", error.message);
process.exit(1);
}

View File

@@ -1,25 +0,0 @@
import db from "./src/lib/db.js";
console.log("Adding settings table...");
try {
db.exec(`
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
description TEXT,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_by TEXT,
FOREIGN KEY (updated_by) REFERENCES users(id)
);
`);
db.exec(`
INSERT OR IGNORE INTO settings (key, value, description) VALUES
('backup_notification_user_id', '', 'User ID to receive backup completion notifications');
`);
console.log("✅ Settings table created successfully");
} catch (error) {
console.error("Error creating settings table:", error);
}

View File

@@ -1,75 +0,0 @@
import db from './src/lib/db.js';
console.log('Starting migration to add team_lead role to users table constraint...');
try {
// Disable foreign key constraints temporarily
db.pragma('foreign_keys = OFF');
console.log('Disabled foreign key constraints');
// Since SQLite doesn't support modifying CHECK constraints directly,
// we need to recreate the table with the new constraint
// First, create a backup table with current data
db.exec('CREATE TABLE users_backup AS SELECT * FROM users');
console.log('Created backup table');
// Drop the original table
db.exec('DROP TABLE users');
console.log('Dropped original table');
// Recreate the table with the updated constraint
db.exec(`
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
name TEXT NOT NULL,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT CHECK(role IN ('admin', 'team_lead', 'project_manager', 'user', 'read_only')) DEFAULT 'user',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
is_active INTEGER DEFAULT 1,
last_login TEXT,
failed_login_attempts INTEGER DEFAULT 0,
locked_until TEXT,
can_be_assigned INTEGER DEFAULT 1,
initial TEXT
)
`);
console.log('Created new table with updated constraint');
// Copy data back from backup
db.exec(`
INSERT INTO users (
id, name, username, password_hash, role, created_at, updated_at,
is_active, last_login, failed_login_attempts, locked_until,
can_be_assigned, initial
)
SELECT
id, name, username, password_hash, role, created_at, updated_at,
is_active, last_login, failed_login_attempts, locked_until,
can_be_assigned, initial
FROM users_backup
`);
console.log('Copied data back from backup');
// Drop the backup table
db.exec('DROP TABLE users_backup');
console.log('Dropped backup table');
// Re-enable foreign key constraints
db.pragma('foreign_keys = ON');
console.log('Re-enabled foreign key constraints');
// Verify the migration
const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get();
console.log(`✅ Migration completed successfully! Users table now has ${userCount.count} records`);
// Verify the constraint allows the new role
console.log('✅ CHECK constraint now includes: admin, team_lead, project_manager, user, read_only');
} catch (error) {
console.error('❌ Migration failed:', error.message);
console.error('You may need to restore from backup manually');
process.exit(1);
}

View File

@@ -1,36 +0,0 @@
import db from './src/lib/db.js';
console.log('Starting migration to add wartosc_zlecenia field to projects table...');
try {
// Check if wartosc_zlecenia column already exists
const schema = db.prepare("PRAGMA table_info(projects)").all();
const hasWartoscZleceniaColumn = schema.some(column => column.name === 'wartosc_zlecenia');
if (hasWartoscZleceniaColumn) {
console.log("✅ wartosc_zlecenia column already exists in projects table");
} else {
// Add the wartosc_zlecenia column
db.prepare("ALTER TABLE projects ADD COLUMN wartosc_zlecenia REAL").run();
console.log("✅ Added 'wartosc_zlecenia' column to projects table");
}
// Verify the column was added
const updatedSchema = db.prepare("PRAGMA table_info(projects)").all();
const wartoscZleceniaColumn = updatedSchema.find(column => column.name === 'wartosc_zlecenia');
if (wartoscZleceniaColumn) {
console.log("✅ Migration completed successfully");
console.log(`Column details: ${JSON.stringify(wartoscZleceniaColumn, null, 2)}`);
} else {
console.error("❌ Migration failed - wartosc_zlecenia column not found");
process.exit(1);
}
db.close();
console.log("Database connection closed");
} catch (error) {
console.error("❌ Migration failed:", error.message);
process.exit(1);
}

View File

@@ -1,46 +0,0 @@
import db from './src/lib/db.js';
import initializeDatabase from './src/lib/init-db.js';
console.log('🚀 Initializing contacts tables...\n');
try {
// Run database initialization which will create the new contacts tables
initializeDatabase();
console.log('✅ Contacts tables created successfully!\n');
// Check if there are projects with contact data in the old text field
const projectsWithContacts = db.prepare(`
SELECT project_id, project_name, contact
FROM projects
WHERE contact IS NOT NULL AND contact != ''
`).all();
if (projectsWithContacts.length > 0) {
console.log(`📋 Found ${projectsWithContacts.length} projects with contact information in the old text field.\n`);
console.log('Sample contacts that could be migrated:');
projectsWithContacts.slice(0, 5).forEach(p => {
console.log(` - ${p.project_name}: "${p.contact}"`);
});
console.log('\n You can manually create contacts from the /contacts page and link them to projects.');
console.log(' The old contact field will remain in the database for reference.\n');
} else {
console.log(' No existing contact data found in projects.\n');
}
// Show table statistics
const contactsCount = db.prepare('SELECT COUNT(*) as count FROM contacts').get();
const projectContactsCount = db.prepare('SELECT COUNT(*) as count FROM project_contacts').get();
console.log('📊 Database Statistics:');
console.log(` - Contacts: ${contactsCount.count}`);
console.log(` - Project-Contact Links: ${projectContactsCount.count}`);
console.log('\n✨ Migration complete! You can now:');
console.log(' 1. Visit /contacts to manage your contacts');
console.log(' 2. Add/edit projects to link contacts');
console.log(' 3. View linked contacts in project details\n');
} catch (error) {
console.error('❌ Error during migration:', error);
process.exit(1);
}

View File

@@ -1,188 +0,0 @@
import db from './src/lib/db.js';
import initializeDatabase from './src/lib/init-db.js';
console.log('🚀 Migrating contact data from projects...\n');
try {
// Run database initialization to ensure tables exist
initializeDatabase();
console.log('✅ Database tables verified\n');
// Get all projects with contact data
const projectsWithContacts = db.prepare(`
SELECT project_id, project_name, contact
FROM projects
WHERE contact IS NOT NULL AND contact != '' AND TRIM(contact) != ''
`).all();
if (projectsWithContacts.length === 0) {
console.log(' No contact data found in projects to migrate.\n');
process.exit(0);
}
console.log(`📋 Found ${projectsWithContacts.length} projects with contact information\n`);
let created = 0;
let linked = 0;
let skipped = 0;
const createContact = db.prepare(`
INSERT INTO contacts (name, phone, email, contact_type, notes, is_active)
VALUES (?, ?, ?, 'project', ?, 1)
`);
const linkContact = db.prepare(`
INSERT OR IGNORE INTO project_contacts (project_id, contact_id, is_primary, relationship_type)
VALUES (?, ?, 1, 'general')
`);
// Process each project
for (const project of projectsWithContacts) {
try {
const contactText = project.contact.trim();
// Parse contact information - common formats:
// "Jan Kowalski, tel. 123-456-789"
// "Jan Kowalski 123-456-789"
// "123-456-789"
// "Jan Kowalski"
let name = '';
let phone = '';
let email = '';
let notes = '';
// Try to extract email
const emailPattern = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/;
const emailMatch = contactText.match(emailPattern);
if (emailMatch) {
email = emailMatch[1].trim();
}
// Try to extract phone number (various formats)
const phonePatterns = [
/(?:\+?48)?[\s-]?(\d{3}[\s-]?\d{3}[\s-]?\d{3})/, // Polish: 123-456-789, 123 456 789, +48 123456789
/(?:\+?48)?[\s-]?(\d{9})/, // 9 digits
/tel\.?\s*[:.]?\s*([+\d\s-]+)/i, // tel. 123-456-789
/phone\s*[:.]?\s*([+\d\s-]+)/i, // phone: 123-456-789
/(\d{3}[-\s]?\d{3}[-\s]?\d{3})/, // Generic phone pattern
];
for (const pattern of phonePatterns) {
const match = contactText.match(pattern);
if (match) {
phone = match[1] || match[0];
phone = phone.replace(/\s+/g, ' ').trim();
break;
}
}
// Extract name (text before phone/email or comma)
let textForName = contactText;
if (phone) {
// Remove phone from text to get name
textForName = textForName.replace(phone, '');
}
if (email) {
// Remove email from text to get name
textForName = textForName.replace(email, '');
}
// Remove common prefixes like "tel.", "phone:", "email:", commas, etc.
name = textForName.replace(/tel\.?|phone:?|email:?|e-mail:?|,/gi, '').trim();
// Clean up name
name = name.replace(/^[,\s-]+|[,\s-]+$/g, '').trim();
// If we couldn't extract structured data, use project name and put original text in notes
if (!phone && !email) {
// No structured contact info found, put everything in notes
notes = `${contactText}`;
name = project.project_name;
} else if (!name) {
// We have phone/email but no clear name
name = project.project_name;
}
// Check if this contact already exists (by name, phone, or email)
let existingContact = null;
if (phone) {
existingContact = db.prepare(`
SELECT contact_id FROM contacts
WHERE phone LIKE ? OR phone LIKE ?
`).get(`%${phone}%`, `%${phone.replace(/\s/g, '')}%`);
}
if (!existingContact && email) {
existingContact = db.prepare(`
SELECT contact_id FROM contacts
WHERE LOWER(email) = LOWER(?)
`).get(email);
}
if (!existingContact && name && name !== project.project_name) {
existingContact = db.prepare(`
SELECT contact_id FROM contacts
WHERE LOWER(name) = LOWER(?)
`).get(name);
}
let contactId;
if (existingContact) {
contactId = existingContact.contact_id;
console.log(` ♻️ Using existing contact "${name}" for project "${project.project_name}"`);
} else {
// Create new contact
const result = createContact.run(
name,
phone || null,
email || null,
notes || `Przeniesiono z projektu: ${project.project_name}`
);
contactId = result.lastInsertRowid;
created++;
const contactInfo = [];
if (phone) contactInfo.push(`📞 ${phone}`);
if (email) contactInfo.push(`📧 ${email}`);
const infoStr = contactInfo.length > 0 ? ` (${contactInfo.join(', ')})` : '';
console.log(` ✨ Created contact "${name}"${infoStr} for project "${project.project_name}"`);
}
// Link contact to project
linkContact.run(project.project_id, contactId);
linked++;
} catch (error) {
console.error(` ❌ Error processing project "${project.project_name}":`, error.message);
skipped++;
}
}
console.log('\n📊 Migration Summary:');
console.log(` - Contacts created: ${created}`);
console.log(` - Project-contact links created: ${linked}`);
console.log(` - Projects skipped: ${skipped}`);
console.log(` - Total projects processed: ${projectsWithContacts.length}`);
// Show final statistics
const contactsCount = db.prepare('SELECT COUNT(*) as count FROM contacts').get();
const projectContactsCount = db.prepare('SELECT COUNT(*) as count FROM project_contacts').get();
console.log('\n📈 Current Database Statistics:');
console.log(` - Total contacts: ${contactsCount.count}`);
console.log(` - Total project-contact links: ${projectContactsCount.count}`);
console.log('\n✨ Migration complete!');
console.log(' - Visit /contacts to view and manage your contacts');
console.log(' - Edit projects to see linked contacts');
console.log(' - The old contact text field is preserved for reference\n');
} catch (error) {
console.error('❌ Error during migration:', error);
process.exit(1);
}

View File

@@ -1,87 +0,0 @@
import db from './src/lib/db.js';
console.log('Starting migration to add cancelled status to project_status constraint...');
try {
// Disable foreign key constraints temporarily
db.pragma('foreign_keys = OFF');
console.log('Disabled foreign key constraints');
// Since SQLite doesn't support modifying CHECK constraints directly,
// we need to recreate the table with the new constraint
// First, create a backup table with current data
db.exec('CREATE TABLE projects_backup AS SELECT * FROM projects');
console.log('Created backup table');
// Drop the original table
db.exec('DROP TABLE projects');
console.log('Dropped original table');
// Recreate the table with the updated constraint
db.exec(`
CREATE TABLE projects (
project_id INTEGER PRIMARY KEY AUTOINCREMENT,
contract_id INTEGER,
project_name TEXT NOT NULL,
project_number TEXT NOT NULL,
address TEXT,
plot TEXT,
district TEXT,
unit TEXT,
city TEXT,
investment_number TEXT,
finish_date TEXT,
wp TEXT,
contact TEXT,
notes TEXT,
project_type TEXT CHECK(project_type IN ('design', 'construction', 'design+construction')) DEFAULT 'design',
project_status TEXT CHECK(project_status IN ('registered', 'in_progress_design', 'in_progress_construction', 'fulfilled', 'cancelled')) DEFAULT 'registered',
coordinates TEXT,
created_by TEXT,
assigned_to TEXT,
created_at TEXT,
updated_at TEXT,
FOREIGN KEY (contract_id) REFERENCES contracts(contract_id)
)
`);
console.log('Created new table with updated constraint');
// Copy data back
db.exec('INSERT INTO projects SELECT * FROM projects_backup');
console.log('Restored data from backup');
// Drop backup table
db.exec('DROP TABLE projects_backup');
console.log('Cleaned up backup table');
// Re-enable foreign key constraints
db.pragma('foreign_keys = ON');
console.log('Re-enabled foreign key constraints');
// Verify the new constraint
const schema = db.prepare('SELECT sql FROM sqlite_master WHERE type=\'table\' AND name=\'projects\'').get();
console.log('New table definition:');
console.log(schema.sql);
console.log('Migration completed successfully!');
} catch (error) {
console.error('Migration failed:', error);
// Re-enable foreign keys in case of error
try {
db.pragma('foreign_keys = ON');
} catch (e) {
console.error('Failed to re-enable foreign keys:', e);
}
// Try to restore from backup if it exists
try {
db.exec('DROP TABLE IF EXISTS projects');
db.exec('ALTER TABLE projects_backup RENAME TO projects');
console.log('Restored from backup due to error');
} catch (restoreError) {
console.error('Failed to restore from backup:', restoreError);
}
}

View File

@@ -1,60 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("🔄 Migrating database to username-based authentication...\n");
try {
// Check current table structure
const tableInfo = db.prepare("PRAGMA table_info(users)").all();
console.log("Current users table columns:");
tableInfo.forEach(col => console.log(` - ${col.name}: ${col.type}`));
const hasUsername = tableInfo.some(col => col.name === 'username');
const hasEmail = tableInfo.some(col => col.name === 'email');
if (hasUsername) {
console.log("✅ Username column already exists!");
} else if (hasEmail) {
console.log("\n📝 Adding username column...");
// Add username column
db.exec(`ALTER TABLE users ADD COLUMN username TEXT;`);
console.log("✅ Username column added");
// Copy email data to username for existing users
console.log("📋 Migrating existing email data to username...");
const result = db.exec(`UPDATE users SET username = email WHERE username IS NULL;`);
console.log("✅ Data migrated");
// Create unique index on username
console.log("🔍 Creating unique index on username...");
try {
db.exec(`CREATE UNIQUE INDEX idx_users_username_unique ON users(username);`);
console.log("✅ Unique index created");
} catch (e) {
console.log(" Index already exists or couldn't be created:", e.message);
}
// Verify migration
console.log("\n🔍 Verifying migration...");
const users = db.prepare("SELECT id, name, username, email FROM users LIMIT 3").all();
console.log("Sample users after migration:");
users.forEach(user => {
console.log(` - ${user.name}: username="${user.username}", email="${user.email || 'NULL'}"`);
});
console.log("\n✅ Migration completed successfully!");
console.log(" You can now log in using usernames instead of emails");
} else {
console.log("❌ Neither username nor email column found. Database may be corrupted.");
process.exit(1);
}
} catch (error) {
console.error("❌ Migration failed:", error.message);
process.exit(1);
} finally {
db.close();
}

View File

@@ -1,31 +0,0 @@
#!/bin/bash
# Database migration runner for deployment
# This script runs all pending migrations in order
echo "🔄 Running database migrations..."
# List of migration scripts to run (in order)
MIGRATIONS=(
"migrate-add-team-lead-role.mjs"
"migrate-add-wartosc-zlecenia.mjs"
)
for migration in "${MIGRATIONS[@]}"; do
if [ -f "$migration" ]; then
echo "Running migration: $migration"
if node "$migration"; then
echo "✅ Migration $migration completed successfully"
# Optionally move completed migration to a completed folder
# mkdir -p migrations/completed
# mv "$migration" "migrations/completed/"
else
echo "❌ Migration $migration failed"
exit 1
fi
else
echo "Migration file $migration not found, skipping..."
fi
done
echo "✅ All migrations completed"

View File

@@ -1,6 +0,0 @@
@echo off
cd /d "x:\projekty\panel"
echo Clearing Next.js cache...
if exist .next rmdir /s /q .next
echo Starting development server...
npm run dev

View File

@@ -1,97 +0,0 @@
// Test script to verify audit logging after our fixes
// This test shows what happens when API calls are made with proper authentication
console.log("=== TESTING AUDIT LOGGING FIX ===\n");
// Simulate the flow that would happen in a real authenticated API call
async function testAuditLogging() {
try {
// Import the logging function
const { logAuditEventSafe, AUDIT_ACTIONS, RESOURCE_TYPES } = await import(
"./src/lib/auditLogSafe.js"
);
console.log("1. Testing audit logging with proper user session...");
// Simulate an authenticated session (like what req.auth would contain)
const mockAuthenticatedSession = {
user: {
id: "e42a4b036074ff7233942a0728557141", // Real user ID from our logs
email: "admin@localhost.com",
name: "Administrator",
role: "admin",
},
expires: "2025-08-08T21:18:07.949Z",
};
// Simulate a null/undefined session (like unauthenticated requests)
const mockUnauthenticatedSession = null;
// Test 1: Authenticated user logging
console.log("\n2. Testing with authenticated session:");
await logAuditEventSafe({
action: AUDIT_ACTIONS.PROJECT_VIEW,
userId: mockAuthenticatedSession?.user?.id || null,
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "test-project-123",
ipAddress: "127.0.0.1",
userAgent: "Test Browser",
details: {
test: "authenticated_user_test",
timestamp: new Date().toISOString(),
},
});
// Test 2: Unauthenticated user logging (should result in null userId)
console.log("\n3. Testing with unauthenticated session:");
await logAuditEventSafe({
action: AUDIT_ACTIONS.LOGIN_FAILED,
userId: mockUnauthenticatedSession?.user?.id || null,
resourceType: RESOURCE_TYPES.SESSION,
resourceId: null,
ipAddress: "127.0.0.1",
userAgent: "Test Browser",
details: {
test: "unauthenticated_user_test",
email: "hacker@test.com",
reason: "invalid_credentials",
},
});
// Test 3: Check what we just logged
console.log("\n4. Checking the audit events we just created...");
const { getAuditLogs } = await import("./src/lib/auditLog.js");
const latestLogs = await getAuditLogs({ limit: 2 });
console.log("Latest 2 audit events:");
latestLogs.forEach((log, index) => {
const userDisplay = log.user_id ? `user ${log.user_id}` : "NULL USER ID";
console.log(
`${index + 1}. ${log.timestamp} - ${log.action} by ${userDisplay} on ${
log.resource_type
}:${log.resource_id || "N/A"}`
);
if (log.details) {
const details =
typeof log.details === "string"
? JSON.parse(log.details)
: log.details;
console.log(` Details: ${JSON.stringify(details, null, 4)}`);
}
});
console.log("\n5. CONCLUSION:");
console.log("✅ The audit logging system is working correctly!");
console.log("✅ Authenticated users get proper user IDs logged");
console.log(
"✅ Unauthenticated requests get NULL user IDs (which is expected)"
);
console.log(
"✅ The logApiActionSafe function will extract userId from session?.user?.id correctly"
);
} catch (error) {
console.error("Test failed:", error);
}
}
testAuditLogging();

View File

@@ -1,138 +0,0 @@
import {
logAuditEvent,
getAuditLogs,
getAuditLogStats,
AUDIT_ACTIONS,
RESOURCE_TYPES,
} from "./src/lib/auditLog.js";
// Test audit logging functionality
console.log("Testing Audit Logging System...\n");
// Test 1: Log some sample events
console.log("1. Creating sample audit events...");
logAuditEvent({
action: AUDIT_ACTIONS.LOGIN,
userId: "user123",
resourceType: RESOURCE_TYPES.SESSION,
ipAddress: "192.168.1.100",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
email: "test@example.com",
role: "user",
},
});
logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_CREATE,
userId: "user123",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "proj-456",
ipAddress: "192.168.1.100",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
project_name: "Test Project",
project_number: "TP-001",
},
});
logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_UPDATE,
userId: "user456",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "proj-456",
ipAddress: "192.168.1.101",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
updatedFields: ["project_name", "address"],
oldValues: { project_name: "Test Project" },
newValues: { project_name: "Updated Test Project" },
},
});
logAuditEvent({
action: AUDIT_ACTIONS.LOGIN_FAILED,
userId: null,
resourceType: RESOURCE_TYPES.SESSION,
ipAddress: "192.168.1.102",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
email: "hacker@evil.com",
reason: "invalid_password",
failed_attempts: 3,
},
});
console.log("Sample events created!\n");
// Test 2: Retrieve audit logs
console.log("2. Retrieving audit logs...");
const allLogs = getAuditLogs();
console.log(`Found ${allLogs.length} total audit events`);
// Show the latest 3 events
console.log("\nLatest audit events:");
allLogs.slice(0, 3).forEach((log, index) => {
console.log(
`${index + 1}. ${log.timestamp} - ${log.action} by user ${
log.user_id || "anonymous"
} on ${log.resource_type}:${log.resource_id || "N/A"}`
);
if (log.details) {
console.log(` Details: ${JSON.stringify(log.details, null, 2)}`);
}
});
// Test 3: Filtered queries
console.log("\n3. Testing filtered queries...");
const loginEvents = getAuditLogs({ action: AUDIT_ACTIONS.LOGIN });
console.log(`Found ${loginEvents.length} login events`);
const projectEvents = getAuditLogs({ resourceType: RESOURCE_TYPES.PROJECT });
console.log(`Found ${projectEvents.length} project-related events`);
const user123Events = getAuditLogs({ userId: "user123" });
console.log(`Found ${user123Events.length} events by user123`);
// Test 4: Statistics
console.log("\n4. Getting audit statistics...");
const stats = getAuditLogStats();
console.log("Overall statistics:");
console.log(`- Total events: ${stats.total}`);
console.log("- Action breakdown:");
stats.actionBreakdown.forEach((action) => {
console.log(` - ${action.action}: ${action.count}`);
});
console.log("- User breakdown:");
stats.userBreakdown.forEach((user) => {
console.log(
` - ${user.user_name || user.user_id || "Anonymous"}: ${user.count}`
);
});
console.log("- Resource breakdown:");
stats.resourceBreakdown.forEach((resource) => {
console.log(` - ${resource.resource_type}: ${resource.count}`);
});
// Test 5: Date range filtering
console.log("\n5. Testing date range filtering...");
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
const recentLogs = getAuditLogs({
startDate: oneHourAgo.toISOString(),
endDate: now.toISOString(),
});
console.log(`Found ${recentLogs.length} events in the last hour`);
console.log("\nAudit logging test completed successfully! ✅");
console.log("\nTo view audit logs in the application:");
console.log("1. Start your Next.js application");
console.log("2. Login as an admin or project manager");
console.log("3. Navigate to /admin/audit-logs");
console.log("4. Use the filters to explore the audit trail");

View File

@@ -1,109 +0,0 @@
// Test authenticated API access using NextAuth.js client-side approach
const BASE_URL = 'http://localhost:3000';
async function testAuthenticatedAPI() {
console.log('🔐 Testing Authenticated API Access\n');
try {
// Test 1: Check if server is running
console.log('1⃣ Checking server status...');
const healthResponse = await fetch(`${BASE_URL}/api/auth/session`);
console.log(`Server status: ${healthResponse.status}`);
if (!healthResponse.ok) {
console.log('❌ Server not responding properly');
return;
}
// Test 2: Test unauthenticated access to protected endpoints
console.log('\n2⃣ Testing unauthenticated access...');
const protectedEndpoints = [
'/api/projects',
'/api/contracts',
'/api/tasks',
'/api/project-tasks'
];
for (const endpoint of protectedEndpoints) {
const response = await fetch(`${BASE_URL}${endpoint}`);
console.log(`${endpoint}: ${response.status} ${response.status === 401 ? '✅ (properly protected)' : '❌ (not protected)'}`);
}
// Test 3: Check protected pages
console.log('\n3⃣ Testing protected pages...');
const protectedPages = ['/projects', '/contracts', '/tasks'];
for (const page of protectedPages) {
const response = await fetch(`${BASE_URL}${page}`, {
redirect: 'manual'
});
if (response.status === 302) {
const location = response.headers.get('location');
if (location && location.includes('/auth/signin')) {
console.log(`${page}: ✅ Properly redirects to sign-in`);
} else {
console.log(`${page}: ⚠️ Redirects to: ${location}`);
}
} else if (response.status === 200) {
console.log(`${page}: ❌ Accessible without authentication`);
} else {
console.log(`${page}: ❓ Status ${response.status}`);
}
}
// Test 4: Test sign-in page accessibility
console.log('\n4⃣ Testing sign-in page...');
const signinResponse = await fetch(`${BASE_URL}/auth/signin`);
if (signinResponse.ok) {
console.log('✅ Sign-in page accessible');
const content = await signinResponse.text();
const hasEmailField = content.includes('name="email"') || content.includes('id="email"');
const hasPasswordField = content.includes('name="password"') || content.includes('id="password"');
console.log(` Email field: ${hasEmailField ? '✅' : '❌'}`);
console.log(` Password field: ${hasPasswordField ? '✅' : '❌'}`);
} else {
console.log('❌ Sign-in page not accessible');
}
// Test 5: Check NextAuth.js providers endpoint
console.log('\n5⃣ Testing NextAuth.js configuration...');
const providersResponse = await fetch(`${BASE_URL}/api/auth/providers`);
if (providersResponse.ok) {
const providers = await providersResponse.json();
console.log('✅ NextAuth.js providers endpoint accessible');
console.log('Available providers:', Object.keys(providers));
} else {
console.log('❌ NextAuth.js providers endpoint failed');
}
// Test 6: Check CSRF token endpoint
console.log('\n6⃣ Testing CSRF token...');
const csrfResponse = await fetch(`${BASE_URL}/api/auth/csrf`);
if (csrfResponse.ok) {
const csrf = await csrfResponse.json();
console.log('✅ CSRF token endpoint accessible');
console.log('CSRF token available:', !!csrf.csrfToken);
} else {
console.log('❌ CSRF token endpoint failed');
}
console.log('\n🎯 Manual Testing Instructions:');
console.log('1. Open browser to: http://localhost:3000/auth/signin');
console.log('2. Use credentials:');
console.log(' Email: admin@localhost.com');
console.log(' Password: admin123456');
console.log('3. After login, test these pages:');
protectedPages.forEach(page => {
console.log(` - http://localhost:3000${page}`);
});
console.log('4. Test API endpoints with browser dev tools or Postman');
} catch (error) {
console.error('❌ Test failed with error:', error.message);
}
}
// Run the test
testAuthenticatedAPI();

View File

@@ -1,40 +0,0 @@
// Test script to verify API route protection with better error handling
const BASE_URL = 'http://localhost:3000';
// Test unauthenticated access to protected routes
async function testProtectedRoutes() {
console.log('🔐 Testing Authorization Setup\n');
const protectedRoutes = [
'/api/projects',
'/api/contracts'
];
console.log('Testing unauthenticated access to protected routes...\n');
for (const route of protectedRoutes) {
try {
const response = await fetch(`${BASE_URL}${route}`);
const contentType = response.headers.get('content-type');
console.log(`Route: ${route}`);
console.log(`Status: ${response.status}`);
console.log(`Content-Type: ${contentType}`);
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
console.log(`Response: ${JSON.stringify(data)}`);
} else {
const text = await response.text();
console.log(`Response (first 200 chars): ${text.substring(0, 200)}...`);
}
console.log('---\n');
} catch (error) {
console.log(`${route} - ERROR: ${error.message}\n`);
}
}
}
// Run the test
testProtectedRoutes().catch(console.error);

View File

@@ -1,127 +0,0 @@
// Test authenticated access to pages and API endpoints
const BASE_URL = 'http://localhost:3000';
// Helper to extract cookies from response headers
function extractCookies(response) {
const cookies = [];
const setCookieHeaders = response.headers.get('set-cookie');
if (setCookieHeaders) {
cookies.push(setCookieHeaders);
}
return cookies.join('; ');
}
// Test authenticated access
async function testAuthenticatedAccess() {
console.log('🔐 Testing Authenticated Access\n');
// Step 1: Get the sign-in page to check if it loads
console.log('1⃣ Testing sign-in page access...');
try {
const signInResponse = await fetch(`${BASE_URL}/auth/signin`);
console.log(`✅ Sign-in page: ${signInResponse.status} ${signInResponse.statusText}`);
if (signInResponse.status === 200) {
const pageContent = await signInResponse.text();
const hasForm = pageContent.includes('Sign in to your account');
console.log(` Form present: ${hasForm ? '✅ Yes' : '❌ No'}`);
}
} catch (error) {
console.log(`❌ Sign-in page error: ${error.message}`);
}
console.log('\n2⃣ Testing authentication endpoint...');
// Step 2: Test the authentication API endpoint
try {
const sessionResponse = await fetch(`${BASE_URL}/api/auth/session`);
console.log(`✅ Session endpoint: ${sessionResponse.status} ${sessionResponse.statusText}`);
if (sessionResponse.status === 200) {
const sessionData = await sessionResponse.json();
console.log(` Session data: ${JSON.stringify(sessionData)}`);
}
} catch (error) {
console.log(`❌ Session endpoint error: ${error.message}`);
}
console.log('\n3⃣ Testing CSRF token endpoint...');
// Step 3: Get CSRF token
try {
const csrfResponse = await fetch(`${BASE_URL}/api/auth/csrf`);
console.log(`✅ CSRF endpoint: ${csrfResponse.status} ${csrfResponse.statusText}`);
if (csrfResponse.status === 200) {
const csrfData = await csrfResponse.json();
console.log(` CSRF token: ${csrfData.csrfToken ? '✅ Present' : '❌ Missing'}`);
}
} catch (error) {
console.log(`❌ CSRF endpoint error: ${error.message}`);
}
console.log('\n4⃣ Testing main dashboard page (unauthenticated)...');
// Step 4: Test main page redirect
try {
const mainPageResponse = await fetch(`${BASE_URL}/`, {
redirect: 'manual' // Don't follow redirects automatically
});
console.log(`✅ Main page: ${mainPageResponse.status} ${mainPageResponse.statusText}`);
if (mainPageResponse.status === 307 || mainPageResponse.status === 302) {
const location = mainPageResponse.headers.get('location');
console.log(` Redirects to: ${location}`);
console.log(` Correct redirect: ${location && location.includes('/auth/signin') ? '✅ Yes' : '❌ No'}`);
}
} catch (error) {
console.log(`❌ Main page error: ${error.message}`);
}
console.log('\n5⃣ Testing projects page (unauthenticated)...');
// Step 5: Test projects page redirect
try {
const projectsPageResponse = await fetch(`${BASE_URL}/projects`, {
redirect: 'manual'
});
console.log(`✅ Projects page: ${projectsPageResponse.status} ${projectsPageResponse.statusText}`);
if (projectsPageResponse.status === 307 || projectsPageResponse.status === 302) {
const location = projectsPageResponse.headers.get('location');
console.log(` Redirects to: ${location}`);
console.log(` Correct redirect: ${location && location.includes('/auth/signin') ? '✅ Yes' : '❌ No'}`);
}
} catch (error) {
console.log(`❌ Projects page error: ${error.message}`);
}
console.log('\n6⃣ Testing API endpoints (unauthenticated)...');
// Step 6: Test API endpoints
const apiEndpoints = ['/api/projects', '/api/contracts', '/api/tasks/templates'];
for (const endpoint of apiEndpoints) {
try {
const response = await fetch(`${BASE_URL}${endpoint}`);
const data = await response.json();
if (response.status === 401) {
console.log(`${endpoint}: Protected (401) - ${data.error}`);
} else {
console.log(`${endpoint}: Not protected (${response.status})`);
}
} catch (error) {
console.log(`${endpoint}: Error - ${error.message}`);
}
}
console.log('\n📋 Summary:');
console.log('- Sign-in page should be accessible');
console.log('- Protected pages should redirect to /auth/signin');
console.log('- Protected API endpoints should return 401 with JSON error');
console.log('- Auth endpoints (/api/auth/*) should be accessible');
}
// Run the test
testAuthenticatedAccess().catch(console.error);

View File

@@ -1,37 +0,0 @@
import { auth } from "@/lib/auth";
// Test what the auth session looks like
console.log("Testing authentication session structure...\n");
async function testAuth() {
try {
// Create a mock request
const mockReq = {
url: "http://localhost:3000/api/projects",
method: "GET",
headers: new Map([
["cookie", ""], // Add any cookies if needed
]),
};
// This is how the auth middleware would wrap a handler
const testHandler = auth(async (req) => {
console.log("=== Authentication Session Debug ===");
console.log("req.auth:", JSON.stringify(req.auth, null, 2));
console.log("req.auth?.user:", JSON.stringify(req.auth?.user, null, 2));
console.log("req.auth?.user?.id:", req.auth?.user?.id);
console.log("req.user:", JSON.stringify(req.user, null, 2));
console.log("req.user?.id:", req.user?.id);
return { success: true };
});
// This would normally be called by Next.js
const result = await testHandler(mockReq);
console.log("Handler result:", result);
} catch (error) {
console.error("Auth test failed:", error);
}
}
testAuth();

View File

@@ -1,49 +0,0 @@
// Test script to verify API route protection
const BASE_URL = 'http://localhost:3000';
// Test unauthenticated access to protected routes
async function testProtectedRoutes() {
console.log('🔐 Testing Authorization Setup\n');
const protectedRoutes = [
'/api/projects',
'/api/contracts',
'/api/tasks/templates',
'/api/project-tasks',
'/api/notes',
'/api/all-project-tasks'
];
console.log('Testing unauthenticated access to protected routes...\n');
for (const route of protectedRoutes) {
try {
const response = await fetch(`${BASE_URL}${route}`);
const data = await response.json();
if (response.status === 401) {
console.log(`${route} - PROTECTED (401 Unauthorized)`);
} else {
console.log(`${route} - NOT PROTECTED (${response.status})`);
console.log(` Response: ${JSON.stringify(data).substring(0, 100)}...`);
}
} catch (error) {
console.log(`${route} - ERROR: ${error.message}`);
}
}
console.log('\n🔍 Testing authentication endpoint...\n');
// Test NextAuth endpoint
try {
const response = await fetch(`${BASE_URL}/api/auth/session`);
const data = await response.json();
console.log(`✅ /api/auth/session - Available (${response.status})`);
console.log(` Response: ${JSON.stringify(data)}`);
} catch (error) {
console.log(`❌ /api/auth/session - ERROR: ${error.message}`);
}
}
// Run the test
testProtectedRoutes().catch(console.error);

View File

@@ -1,115 +0,0 @@
// Complete authentication flow test
const BASE_URL = 'http://localhost:3000';
async function testCompleteAuthFlow() {
console.log('🔐 Testing Complete Authentication Flow\n');
// Test 1: Verify unauthenticated access is properly blocked
console.log('1⃣ Testing unauthenticated access protection...');
const protectedRoutes = [
{ path: '/', name: 'Dashboard' },
{ path: '/projects', name: 'Projects Page' },
{ path: '/tasks/templates', name: 'Tasks Page' }
];
for (const route of protectedRoutes) {
try {
const response = await fetch(`${BASE_URL}${route.path}`, {
redirect: 'manual'
});
if (response.status === 302 || response.status === 307) {
const location = response.headers.get('location');
if (location && location.includes('/auth/signin')) {
console.log(`${route.name}: Properly redirects to sign-in`);
} else {
console.log(`${route.name}: Redirects to wrong location: ${location}`);
}
} else {
console.log(`${route.name}: Not protected (${response.status})`);
}
} catch (error) {
console.log(`${route.name}: Error - ${error.message}`);
}
}
// Test 2: Verify API protection
console.log('\n2⃣ Testing API protection...');
const apiRoutes = ['/api/projects', '/api/contracts', '/api/tasks/templates'];
for (const route of apiRoutes) {
try {
const response = await fetch(`${BASE_URL}${route}`);
const data = await response.json();
if (response.status === 401 && data.error === 'Authentication required') {
console.log(`${route}: Properly protected`);
} else {
console.log(`${route}: Not protected (${response.status}) - ${JSON.stringify(data)}`);
}
} catch (error) {
console.log(`${route}: Error - ${error.message}`);
}
}
// Test 3: Verify auth endpoints work
console.log('\n3⃣ Testing NextAuth endpoints...');
const authEndpoints = [
{ path: '/api/auth/session', name: 'Session' },
{ path: '/api/auth/providers', name: 'Providers' },
{ path: '/api/auth/csrf', name: 'CSRF' }
];
for (const endpoint of authEndpoints) {
try {
const response = await fetch(`${BASE_URL}${endpoint.path}`);
if (response.status === 200) {
console.log(`${endpoint.name}: Working (200)`);
} else {
console.log(`${endpoint.name}: Error (${response.status})`);
}
} catch (error) {
console.log(`${endpoint.name}: Error - ${error.message}`);
}
}
// Test 4: Verify sign-in page accessibility
console.log('\n4⃣ Testing sign-in page...');
try {
const response = await fetch(`${BASE_URL}/auth/signin`);
if (response.status === 200) {
const html = await response.text();
const hasForm = html.includes('Sign in to your account');
const hasEmailField = html.includes('email');
const hasPasswordField = html.includes('password');
console.log(` ✅ Sign-in page: Accessible (200)`);
console.log(` ✅ Form present: ${hasForm ? 'Yes' : 'No'}`);
console.log(` ✅ Email field: ${hasEmailField ? 'Yes' : 'No'}`);
console.log(` ✅ Password field: ${hasPasswordField ? 'Yes' : 'No'}`);
} else {
console.log(` ❌ Sign-in page: Error (${response.status})`);
}
} catch (error) {
console.log(` ❌ Sign-in page: Error - ${error.message}`);
}
console.log('\n📋 Summary:');
console.log('✅ All protected pages redirect to sign-in');
console.log('✅ All API endpoints require authentication');
console.log('✅ NextAuth endpoints are functional');
console.log('✅ Sign-in page is accessible and complete');
console.log('\n🎉 Authentication system is fully functional!');
console.log('\n📝 Next steps:');
console.log(' • Visit http://localhost:3000/auth/signin');
console.log(' • Login with: admin@localhost / admin123456');
console.log(' • Access the protected application!');
}
testCompleteAuthFlow().catch(console.error);

View File

@@ -1,54 +0,0 @@
import db from './src/lib/db.js';
console.log('Testing contacts query...\n');
try {
// Test 1: Basic query
console.log('Test 1: Basic contact query');
const basic = db.prepare('SELECT contact_id, name FROM contacts WHERE is_active = 1 LIMIT 3').all();
console.log('✓ Basic query works:', basic.length, 'contacts\n');
// Test 2: With LEFT JOIN
console.log('Test 2: With project_contacts join');
const withJoin = db.prepare(`
SELECT c.contact_id, c.name, COUNT(pc.project_id) as count
FROM contacts c
LEFT JOIN project_contacts pc ON c.contact_id = pc.contact_id
WHERE c.is_active = 1
GROUP BY c.contact_id
LIMIT 3
`).all();
console.log('✓ Join query works:', withJoin.length, 'contacts\n');
// Test 3: With both joins
console.log('Test 3: With both joins (no CASE)');
const bothJoins = db.prepare(`
SELECT c.contact_id, c.name, COUNT(p.project_id) as count
FROM contacts c
LEFT JOIN project_contacts pc ON c.contact_id = pc.contact_id
LEFT JOIN projects p ON pc.project_id = p.project_id
WHERE c.is_active = 1
GROUP BY c.contact_id
LIMIT 3
`).all();
console.log('✓ Both joins work:', bothJoins.length, 'contacts\n');
// Test 4: With CASE statement
console.log('Test 4: With CASE statement');
const withCase = db.prepare(`
SELECT c.contact_id, c.name,
COUNT(DISTINCT CASE WHEN p.is_deleted = 0 THEN p.project_id ELSE NULL END) as count
FROM contacts c
LEFT JOIN project_contacts pc ON c.contact_id = pc.contact_id
LEFT JOIN projects p ON pc.project_id = p.project_id
WHERE c.is_active = 1
GROUP BY c.contact_id
LIMIT 3
`).all();
console.log('✓ CASE query works:', withCase.length, 'contacts');
withCase.forEach(c => console.log(` ${c.name}: ${c.count} active projects`));
} catch (error) {
console.error('❌ Query failed:', error.message);
console.error(error.stack);
}

View File

@@ -1,40 +0,0 @@
import { createProject } from "./src/lib/queries/projects.js";
import initializeDatabase from "./src/lib/init-db.js";
// Initialize database
initializeDatabase();
console.log("Testing createProject function...\n");
const testProjectData = {
contract_id: 1, // Assuming contract 1 exists
project_name: "Test Project - User Tracking",
address: "Test Address 123",
plot: "123/456",
district: "Test District",
unit: "Test Unit",
city: "Test City",
investment_number: "TEST-2025-001",
finish_date: "2025-12-31",
wp: "TEST/2025/001",
contact: "test@example.com",
notes: "Test project with user tracking",
project_type: "design",
project_status: "registered",
coordinates: "50.0,20.0",
assigned_to: "e42a4b036074ff7233942a0728557141", // admin user ID
};
try {
console.log("Creating test project with admin user as creator...");
const result = createProject(
testProjectData,
"e42a4b036074ff7233942a0728557141"
);
console.log("✅ Project created successfully!");
console.log("Result:", result);
console.log("Project ID:", result.lastInsertRowid);
} catch (error) {
console.error("❌ Error creating project:", error.message);
console.error("Stack:", error.stack);
}

View File

@@ -1,124 +0,0 @@
import {
logAuditEvent,
getAuditLogs,
getAuditLogStats,
AUDIT_ACTIONS,
RESOURCE_TYPES,
} from "./src/lib/auditLog.js";
// Test audit logging functionality
console.log("Testing Audit Logging System...\n");
async function testAuditLogging() {
try {
// Test 1: Check existing audit logs
console.log("1. Checking existing audit logs...");
const existingLogs = await getAuditLogs({ limit: 10 });
console.log(`Found ${existingLogs.length} existing audit events`);
if (existingLogs.length > 0) {
console.log("\nLatest audit events:");
existingLogs.slice(0, 5).forEach((log, index) => {
console.log(
`${index + 1}. ${log.timestamp} - ${log.action} by user ${
log.user_id || "NULL"
} on ${log.resource_type}:${log.resource_id || "N/A"}`
);
if (log.details) {
console.log(
` Details: ${JSON.stringify(JSON.parse(log.details), null, 2)}`
);
}
});
}
// Check for null userIds
const nullUserIdLogs = await getAuditLogs();
const nullUserCount = nullUserIdLogs.filter(
(log) => log.user_id === null
).length;
console.log(
`\nFound ${nullUserCount} audit events with NULL user_id out of ${nullUserIdLogs.length} total`
);
// Test 2: Log some sample events with different user scenarios
console.log("\n2. Creating sample audit events...");
await logAuditEvent({
action: AUDIT_ACTIONS.LOGIN,
userId: "user123",
resourceType: RESOURCE_TYPES.SESSION,
ipAddress: "192.168.1.100",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
email: "test@example.com",
role: "user",
},
});
await logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_CREATE,
userId: "user123",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "proj-456",
ipAddress: "192.168.1.100",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
project_name: "Test Project",
project_number: "TP-001",
},
});
// Test null userId scenario
await logAuditEvent({
action: AUDIT_ACTIONS.LOGIN_FAILED,
userId: null,
resourceType: RESOURCE_TYPES.SESSION,
ipAddress: "192.168.1.102",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
details: {
email: "hacker@evil.com",
reason: "invalid_password",
failed_attempts: 3,
},
});
console.log("Sample events created!\n");
// Test 3: Check new logs
console.log("3. Checking audit logs after test events...");
const newLogs = await getAuditLogs({ limit: 5 });
console.log(`Latest 5 audit events:`);
newLogs.forEach((log, index) => {
console.log(
`${index + 1}. ${log.timestamp} - ${log.action} by user ${
log.user_id || "NULL"
} on ${log.resource_type}:${log.resource_id || "N/A"}`
);
});
// Test 4: Statistics
console.log("\n4. Getting audit log statistics...");
const stats = await getAuditLogStats();
console.log(`Total events: ${stats.total}`);
console.log("\nAction breakdown:");
stats.actionBreakdown.forEach((item) => {
console.log(` ${item.action}: ${item.count}`);
});
console.log("\nUser breakdown:");
stats.userBreakdown.slice(0, 5).forEach((item) => {
console.log(
` ${item.user_id || "NULL"} (${item.user_name || "Unknown"}): ${
item.count
}`
);
});
} catch (error) {
console.error("Test failed:", error);
}
}
// Run the test
testAuditLogging();

View File

@@ -1,56 +0,0 @@
// Test script to verify date formatting
import { formatDate, formatDateForInput } from "./src/lib/utils.js";
console.log("Testing Date Formatting Functions...\n");
// Test cases
const testDates = [
"2024-01-15",
"2024-12-25T14:30:00",
"2024-06-01",
new Date("2024-03-10"),
new Date("2024-09-22T09:15:30"),
null,
undefined,
"invalid-date",
];
console.log("formatDate() tests (DD.MM.YYYY format):");
testDates.forEach((date, index) => {
try {
const result = formatDate(date);
console.log(`${index + 1}. ${JSON.stringify(date)} -> "${result}"`);
} catch (error) {
console.log(
`${index + 1}. ${JSON.stringify(date)} -> ERROR: ${error.message}`
);
}
});
console.log("\nformatDate() with time tests (DD.MM.YYYY HH:MM format):");
testDates.forEach((date, index) => {
try {
const result = formatDate(date, { includeTime: true });
console.log(`${index + 1}. ${JSON.stringify(date)} -> "${result}"`);
} catch (error) {
console.log(
`${index + 1}. ${JSON.stringify(date)} -> ERROR: ${error.message}`
);
}
});
console.log(
"\nformatDateForInput() tests (YYYY-MM-DD format for HTML inputs):"
);
testDates.forEach((date, index) => {
try {
const result = formatDateForInput(date);
console.log(`${index + 1}. ${JSON.stringify(date)} -> "${result}"`);
} catch (error) {
console.log(
`${index + 1}. ${JSON.stringify(date)} -> ERROR: ${error.message}`
);
}
});
console.log("\nDate formatting verification complete!");

View File

@@ -1,417 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Comprehensive Dropdown Test</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.test-container {
border: 2px solid #10b981;
background: #f0fdf4;
}
.dropdown-test {
border: 1px solid #6b7280;
margin: 10px 0;
padding: 10px;
}
.status-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
}
.status-warning {
background: #fef3c7;
color: #92400e;
}
.status-primary {
background: #dbeafe;
color: #1e40af;
}
.status-success {
background: #d1fae5;
color: #065f46;
}
.status-danger {
background: #fee2e2;
color: #991b1b;
}
.status-secondary {
background: #f3f4f6;
color: #374151;
}
</style>
</head>
<body class="p-8 bg-gray-50">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold mb-6 text-gray-900">
Dropdown Component Validation
</h1>
<!-- Test 1: Basic Dropdown Functionality -->
<div class="test-container p-6 mb-6 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-green-800">
✅ Test 1: Basic Dropdown Structure
</h2>
<div class="dropdown-test">
<label class="block text-sm font-medium mb-2"
>Task Status Dropdown Simulation:</label
>
<div class="relative inline-block">
<button id="task-status-btn" class="status-badge status-warning">
Pending
<svg
class="w-3 h-3 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div
id="task-status-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]"
>
<div class="bg-yellow-100 p-2 text-xs text-center border-b">
DEBUG: Task Status Visible
</div>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-warning">Pending</span>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-primary">In Progress</span>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-success">Completed</span>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-danger">Cancelled</span>
</button>
</div>
</div>
</div>
<div class="dropdown-test">
<label class="block text-sm font-medium mb-2"
>Project Status Dropdown Simulation:</label
>
<div class="relative inline-block">
<button
id="project-status-btn"
class="status-badge status-secondary"
>
Registered
<svg
class="w-3 h-3 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div
id="project-status-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]"
>
<div class="bg-yellow-100 p-2 text-xs text-center border-b">
DEBUG: Project Status Visible
</div>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-secondary">Registered</span>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-primary"
>In Progress (Design)</span
>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-primary"
>In Progress (Construction)</span
>
</button>
<button class="w-full text-left px-3 py-2 hover:bg-gray-50">
<span class="status-badge status-success">Completed</span>
</button>
</div>
</div>
</div>
</div>
<!-- Test 2: Table Context -->
<div class="test-container p-6 mb-6 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-green-800">
✅ Test 2: Dropdown in Table Context
</h2>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th
class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"
>
Task
</th>
<th
class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"
>
Status
</th>
<th
class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase"
>
Project Status
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr>
<td class="px-4 py-4 text-sm text-gray-900">Sample Task 1</td>
<td class="px-4 py-4">
<div class="relative inline-block">
<button
id="table-task-btn"
class="status-badge status-primary"
>
In Progress
<svg
class="w-3 h-3 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div
id="table-task-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]"
>
<div
class="bg-yellow-100 p-2 text-xs text-center border-b"
>
DEBUG: Table Task Visible
</div>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-warning">Pending</span>
</button>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-primary"
>In Progress</span
>
</button>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-success"
>Completed</span
>
</button>
</div>
</div>
</td>
<td class="px-4 py-4">
<div class="relative inline-block">
<button
id="table-project-btn"
class="status-badge status-primary"
>
In Progress (Design)
<svg
class="w-3 h-3 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div
id="table-project-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]"
>
<div
class="bg-yellow-100 p-2 text-xs text-center border-b"
>
DEBUG: Table Project Visible
</div>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-secondary"
>Registered</span
>
</button>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-primary"
>In Progress (Design)</span
>
</button>
<button
class="w-full text-left px-3 py-2 hover:bg-gray-50"
>
<span class="status-badge status-success"
>Completed</span
>
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Test Results -->
<div class="bg-blue-50 border-2 border-blue-200 p-6 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-blue-800">
🧪 Test Results
</h2>
<div id="test-results" class="space-y-2 text-sm">
<p>⏳ Click the dropdown buttons above to test functionality...</p>
</div>
<div class="mt-4 p-4 bg-white rounded border">
<h3 class="font-medium mb-2">Expected Behavior:</h3>
<ul class="text-sm space-y-1 text-gray-700">
<li>✅ Dropdowns should appear immediately when clicked</li>
<li>✅ Red border and yellow debug header should be visible</li>
<li>
✅ Dropdown should appear above all other elements (z-index test)
</li>
<li>✅ Clicking outside should close the dropdown</li>
<li>✅ Dropdown should not be clipped by table overflow</li>
</ul>
</div>
</div>
</div>
<script>
const dropdowns = [
{
btn: "task-status-btn",
dropdown: "task-status-dropdown",
name: "Task Status",
},
{
btn: "project-status-btn",
dropdown: "project-status-dropdown",
name: "Project Status",
},
{
btn: "table-task-btn",
dropdown: "table-task-dropdown",
name: "Table Task Status",
},
{
btn: "table-project-btn",
dropdown: "table-project-dropdown",
name: "Table Project Status",
},
];
const results = document.getElementById("test-results");
let testCount = 0;
function addResult(message, type = "info") {
testCount++;
const colors = {
success: "text-green-700",
error: "text-red-700",
info: "text-blue-700",
};
results.innerHTML += `<p class="${colors[type]}">${testCount}. ${message}</p>`;
}
dropdowns.forEach(({ btn, dropdown, name }) => {
const button = document.getElementById(btn);
const dropdownEl = document.getElementById(dropdown);
button.addEventListener("click", function (e) {
e.stopPropagation();
// Close all other dropdowns
dropdowns.forEach(({ dropdown: otherId }) => {
if (otherId !== dropdown) {
document.getElementById(otherId).classList.add("hidden");
}
});
// Toggle current dropdown
const isHidden = dropdownEl.classList.contains("hidden");
dropdownEl.classList.toggle("hidden");
if (isHidden) {
addResult(`${name} dropdown opened successfully`, "success");
// Test visibility
const rect = dropdownEl.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
addResult(
`${name} dropdown is visible (${rect.width}x${rect.height}px)`,
"success"
);
} else {
addResult(`${name} dropdown has zero dimensions!`, "error");
}
// Test z-index
const computedStyle = window.getComputedStyle(dropdownEl);
addResult(
`${name} dropdown z-index: ${computedStyle.zIndex}`,
"info"
);
} else {
addResult(`${name} dropdown closed`, "info");
}
});
});
// Close dropdowns when clicking outside
document.addEventListener("click", function () {
dropdowns.forEach(({ dropdown }) => {
document.getElementById(dropdown).classList.add("hidden");
});
});
// Initial test message
setTimeout(() => {
if (testCount === 0) {
addResult("Waiting for user interaction...", "info");
}
}, 1000);
</script>
</body>
</html>

View File

@@ -1,156 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dropdown Test</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.debug-border {
border: 2px solid red !important;
}
</style>
</head>
<body class="p-8 bg-gray-100">
<h1 class="text-2xl mb-4">Dropdown Visibility Test</h1>
<!-- Test basic dropdown structure -->
<div class="mb-8">
<h2 class="text-lg mb-2">Basic Dropdown Test</h2>
<div class="relative">
<button
id="test-btn"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Click me
</button>
<div
id="test-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-[9999] min-w-[120px]"
>
<div class="px-3 py-2 hover:bg-gray-50">Option 1</div>
<div class="px-3 py-2 hover:bg-gray-50">Option 2</div>
<div class="px-3 py-2 hover:bg-gray-50">Option 3</div>
</div>
</div>
</div>
<!-- Test with high z-index -->
<div class="mb-8">
<h2 class="text-lg mb-2">High Z-Index Test</h2>
<div class="relative">
<button
id="test-btn-2"
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
Click me (z-index 9999)
</button>
<div
id="test-dropdown-2"
class="hidden absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]"
>
<div class="px-3 py-2 hover:bg-gray-50 bg-yellow-100">
High Z Option 1
</div>
<div class="px-3 py-2 hover:bg-gray-50 bg-yellow-100">
High Z Option 2
</div>
<div class="px-3 py-2 hover:bg-gray-50 bg-yellow-100">
High Z Option 3
</div>
</div>
</div>
</div>
<!-- Test in table container -->
<div class="mb-8">
<h2 class="text-lg mb-2">Table Container Test</h2>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left">Name</th>
<th class="px-6 py-3 text-left">Status</th>
<th class="px-6 py-3 text-left">Actions</th>
</tr>
</thead>
<tbody>
<tr class="border-t">
<td class="px-6 py-4">Test Task</td>
<td class="px-6 py-4">
<div class="relative">
<button
id="table-btn"
class="bg-purple-500 text-white px-3 py-1 rounded text-sm"
>
Status Dropdown
</button>
<div
id="table-dropdown"
class="hidden absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-[9999] min-w-[120px]"
>
<div class="px-3 py-2 hover:bg-gray-50 bg-blue-100">
Pending
</div>
<div class="px-3 py-2 hover:bg-gray-50 bg-green-100">
In Progress
</div>
<div class="px-3 py-2 hover:bg-gray-50 bg-red-100">
Completed
</div>
</div>
</div>
</td>
<td class="px-6 py-4">Edit</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
// Add click handlers
document
.getElementById("test-btn")
.addEventListener("click", function () {
const dropdown = document.getElementById("test-dropdown");
dropdown.classList.toggle("hidden");
console.log(
"Dropdown 1 toggled, hidden:",
dropdown.classList.contains("hidden")
);
});
document
.getElementById("test-btn-2")
.addEventListener("click", function () {
const dropdown = document.getElementById("test-dropdown-2");
dropdown.classList.toggle("hidden");
console.log(
"Dropdown 2 toggled, hidden:",
dropdown.classList.contains("hidden")
);
});
document
.getElementById("table-btn")
.addEventListener("click", function () {
const dropdown = document.getElementById("table-dropdown");
dropdown.classList.toggle("hidden");
console.log(
"Table dropdown toggled, hidden:",
dropdown.classList.contains("hidden")
);
});
// Close dropdowns when clicking outside
document.addEventListener("click", function (e) {
if (!e.target.closest(".relative")) {
document.querySelectorAll('[id$="-dropdown"]').forEach((dropdown) => {
dropdown.classList.add("hidden");
});
}
});
</script>
</body>
</html>

View File

@@ -1,72 +0,0 @@
#!/usr/bin/env node
/**
* Test script to simulate due date reminders
* Creates a test project due in 3 days and runs the reminder script
*/
import db from "./src/lib/db.js";
import { addDays, format } from "date-fns";
async function createTestProject() {
try {
console.log("🧪 Creating test project due in 3 days...");
// Create a test contract first
const contractResult = db.prepare(`
INSERT INTO contracts (contract_number, contract_name, customer, date_signed)
VALUES (?, ?, ?, ?)
`).run('TEST-001', 'Test Contract', 'Test Customer', new Date().toISOString());
const contractId = contractResult.lastInsertRowid;
// Create a test project due in 3 days
const dueDate = addDays(new Date(), 3);
const projectResult = db.prepare(`
INSERT INTO projects (
contract_id, project_name, project_number, address,
finish_date, project_status
) VALUES (?, ?, ?, ?, ?, ?)
`).run(
contractId,
'Test Project - Due Soon',
'1/TEST-001',
'Test Address 123',
dueDate.toISOString(),
'in_progress_design'
);
console.log(`✅ Created test project due on ${format(dueDate, 'yyyy-MM-dd')}`);
console.log("🔄 Running due date reminders script...");
// Run the reminders script
const { execSync } = await import('child_process');
execSync('node send-due-date-reminders.mjs', { stdio: 'inherit' });
// Check if notifications were created
const notifications = db.prepare(`
SELECT * FROM notifications
WHERE type = 'due_date_reminder'
ORDER BY created_at DESC
LIMIT 5
`).all();
console.log(`📢 Found ${notifications.length} due date reminder notifications:`);
notifications.forEach(notif => {
console.log(` - ${notif.title}: ${notif.message.substring(0, 100)}...`);
});
// Clean up test data
console.log("🧹 Cleaning up test data...");
db.prepare('DELETE FROM projects WHERE project_name = ?').run('Test Project - Due Soon');
db.prepare('DELETE FROM contracts WHERE contract_number = ?').run('TEST-001');
db.prepare('DELETE FROM notifications WHERE type = ? AND title LIKE ?').run('due_date_reminder', 'Projekt kończy się za%');
console.log("✅ Test completed successfully!");
} catch (error) {
console.error("❌ Test failed:", error);
}
}
createTestProject();

View File

@@ -1,83 +0,0 @@
/**
* Test Edge Runtime compatibility for audit logging
*/
// Test Edge Runtime detection
console.log("Testing Edge Runtime compatibility...\n");
// Simulate Edge Runtime environment
const originalEdgeRuntime = global.EdgeRuntime;
const originalNextRuntime = process.env.NEXT_RUNTIME;
console.log("1. Testing in simulated Edge Runtime environment...");
global.EdgeRuntime = "edge";
process.env.NEXT_RUNTIME = "edge";
// Import the audit logging functions
const { logAuditEvent, getAuditLogs, AUDIT_ACTIONS, RESOURCE_TYPES } =
await import("./src/lib/auditLog.js");
// Test logging in Edge Runtime
logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_VIEW,
userId: "test-user",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "test-project",
details: { test: "edge runtime test" },
});
// Test querying in Edge Runtime
const logs = getAuditLogs({ limit: 10 });
console.log(`Queried logs in Edge Runtime: ${logs.length} results`);
console.log("2. Testing in simulated Node.js Runtime environment...");
// Restore Node.js environment
delete global.EdgeRuntime;
delete process.env.NEXT_RUNTIME;
// Test logging in Node.js Runtime
try {
logAuditEvent({
action: AUDIT_ACTIONS.PROJECT_CREATE,
userId: "test-user",
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "test-project-2",
details: { test: "nodejs runtime test" },
});
console.log("Node.js runtime logging: ✅ Success");
} catch (error) {
console.log("Node.js runtime logging: ❌ Error:", error.message);
}
// Test querying in Node.js Runtime
try {
const nodeLogs = getAuditLogs({ limit: 10 });
console.log(
`Node.js runtime querying: ✅ Success (${nodeLogs.length} results)`
);
} catch (error) {
console.log("Node.js runtime querying: ❌ Error:", error.message);
}
// Restore original environment
if (originalEdgeRuntime !== undefined) {
global.EdgeRuntime = originalEdgeRuntime;
} else {
delete global.EdgeRuntime;
}
if (originalNextRuntime !== undefined) {
process.env.NEXT_RUNTIME = originalNextRuntime;
} else {
delete process.env.NEXT_RUNTIME;
}
console.log("\n✅ Edge Runtime compatibility test completed!");
console.log("\nKey points:");
console.log(
"- Edge Runtime: Logs to console, returns empty arrays for queries"
);
console.log("- Node.js Runtime: Full database functionality");
console.log('- API routes are configured with runtime: "nodejs"');
console.log("- Middleware avoids database operations");
console.log("- Error handling prevents runtime crashes");

View File

@@ -1,206 +0,0 @@
// Test authenticated flow without external dependencies
const BASE_URL = 'http://localhost:3000';
// Test data
const TEST_CREDENTIALS = {
email: 'admin@localhost.com',
password: 'admin123456'
};
// Helper function to extract cookies from response
function extractCookies(response) {
const cookies = response.headers.raw()['set-cookie'];
if (!cookies) return '';
return cookies
.map(cookie => cookie.split(';')[0])
.join('; ');
}
// Helper function to make authenticated requests
async function makeAuthenticatedRequest(url, options = {}, cookies = '') {
return fetch(url, {
...options,
headers: {
'Cookie': cookies,
'Content-Type': 'application/json',
...options.headers
}
});
}
async function testCompleteAuthenticatedFlow() {
console.log('🔐 Testing Complete Authenticated Flow\n');
try {
// Step 1: Get CSRF token from sign-in page
console.log('1⃣ Getting CSRF token...');
const signinResponse = await fetch(`${BASE_URL}/auth/signin`);
const signinHtml = await signinResponse.text();
// Extract CSRF token (NextAuth.js typically includes it in the form)
const csrfMatch = signinHtml.match(/name="csrfToken" value="([^"]+)"/);
const csrfToken = csrfMatch ? csrfMatch[1] : null;
if (!csrfToken) {
console.log('❌ Could not extract CSRF token');
return;
}
console.log('✅ CSRF token extracted');
const initialCookies = extractCookies(signinResponse);
// Step 2: Attempt login
console.log('\n2⃣ Attempting login...');
const loginResponse = await fetch(`${BASE_URL}/api/auth/callback/credentials`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': initialCookies
},
body: new URLSearchParams({
csrfToken,
email: TEST_CREDENTIALS.email,
password: TEST_CREDENTIALS.password,
callbackUrl: `${BASE_URL}/projects`,
json: 'true'
}),
redirect: 'manual'
});
console.log(`Login response status: ${loginResponse.status}`);
if (loginResponse.status === 200) {
const loginResult = await loginResponse.json();
console.log('Login result:', loginResult);
if (loginResult.url) {
console.log('✅ Login successful, redirecting to:', loginResult.url);
} else if (loginResult.error) {
console.log('❌ Login failed:', loginResult.error);
return;
}
} else if (loginResponse.status === 302) {
console.log('✅ Login successful (redirect)');
} else {
console.log('❌ Login failed with status:', loginResponse.status);
const errorText = await loginResponse.text();
console.log('Error response:', errorText.substring(0, 500));
return;
}
// Get session cookies
const sessionCookies = extractCookies(loginResponse) || initialCookies;
console.log('Session cookies:', sessionCookies ? 'Present' : 'Missing');
// Step 3: Test session endpoint
console.log('\n3⃣ Testing session endpoint...');
const sessionResponse = await makeAuthenticatedRequest(
`${BASE_URL}/api/auth/session`,
{},
sessionCookies
);
if (sessionResponse.ok) {
const session = await sessionResponse.json();
console.log('✅ Session data:', JSON.stringify(session, null, 2));
} else {
console.log('❌ Session check failed:', sessionResponse.status);
}
// Step 4: Test protected pages
console.log('\n4⃣ Testing protected pages...');
const protectedPages = ['/projects', '/contracts', '/tasks'];
for (const page of protectedPages) {
const pageResponse = await makeAuthenticatedRequest(
`${BASE_URL}${page}`,
{},
sessionCookies
);
if (pageResponse.ok) {
console.log(`${page} - accessible`);
} else if (pageResponse.status === 302) {
console.log(`⚠️ ${page} - redirected (status: 302)`);
} else {
console.log(`${page} - failed (status: ${pageResponse.status})`);
}
}
// Step 5: Test API endpoints
console.log('\n5⃣ Testing API endpoints...');
const apiEndpoints = [
{ url: '/api/projects', method: 'GET' },
{ url: '/api/contracts', method: 'GET' },
{ url: '/api/tasks', method: 'GET' },
{ url: '/api/tasks/templates', method: 'GET' }
];
for (const endpoint of apiEndpoints) {
const apiResponse = await makeAuthenticatedRequest(
`${BASE_URL}${endpoint.url}`,
{ method: endpoint.method },
sessionCookies
);
if (apiResponse.ok) {
const data = await apiResponse.json();
console.log(`${endpoint.method} ${endpoint.url} - success (${Array.isArray(data) ? data.length : 'object'} items)`);
} else if (apiResponse.status === 401) {
console.log(`${endpoint.method} ${endpoint.url} - unauthorized (status: 401)`);
} else {
console.log(`${endpoint.method} ${endpoint.url} - failed (status: ${apiResponse.status})`);
const errorText = await apiResponse.text();
console.log(` Error: ${errorText.substring(0, 200)}`);
}
}
// Step 6: Test creating data
console.log('\n6⃣ Testing data creation...');
// Test creating a project
const projectData = {
name: 'Test Project Auth',
description: 'Testing authentication flow',
deadline: '2025-12-31',
status: 'active'
};
const createProjectResponse = await makeAuthenticatedRequest(
`${BASE_URL}/api/projects`,
{
method: 'POST',
body: JSON.stringify(projectData)
},
sessionCookies
);
if (createProjectResponse.ok) {
const newProject = await createProjectResponse.json();
console.log('✅ Project creation successful:', newProject.name);
// Clean up - delete the test project
const deleteResponse = await makeAuthenticatedRequest(
`${BASE_URL}/api/projects/${newProject.id}`,
{ method: 'DELETE' },
sessionCookies
);
if (deleteResponse.ok) {
console.log('✅ Test project cleaned up');
}
} else {
console.log('❌ Project creation failed:', createProjectResponse.status);
const errorText = await createProjectResponse.text();
console.log(' Error:', errorText.substring(0, 200));
}
} catch (error) {
console.error('❌ Test failed with error:', error.message);
}
}
// Run the test
testCompleteAuthenticatedFlow();

View File

@@ -1,49 +0,0 @@
import db from "./src/lib/db.js";
import {
createProjectTask,
updateProjectTaskStatus,
} from "./src/lib/queries/tasks.js";
import { getNotesByTaskId } from "./src/lib/queries/notes.js";
console.log("Testing automatic logging system...\n");
// Test 1: Create a new task and check if system note is created
console.log("Test 1: Creating a new task...");
try {
const result = createProjectTask({
project_id: 1, // Assuming project ID 1 exists
custom_task_name: "Test Task for Logging",
custom_description: "Testing automatic note creation",
custom_max_wait_days: 7,
priority: "high",
status: "pending",
});
const taskId = result.lastInsertRowid;
console.log(`✓ Task created with ID: ${taskId}`);
// Check if system note was created
const notes = getNotesByTaskId(taskId);
console.log(`✓ Notes found: ${notes.length}`);
console.log("Notes:", notes);
// Test 2: Update task status and check if system note is created
console.log("\nTest 2: Updating task status...");
updateProjectTaskStatus(taskId, "in_progress");
console.log("✓ Task status updated to in_progress");
// Check if new system note was created
const updatedNotes = getNotesByTaskId(taskId);
console.log(`✓ Notes after status update: ${updatedNotes.length}`);
console.log("Updated notes:", updatedNotes);
// Clean up - delete test task
console.log("\nCleaning up test data...");
db.prepare("DELETE FROM notes WHERE task_id = ?").run(taskId);
db.prepare("DELETE FROM project_tasks WHERE id = ?").run(taskId);
console.log("✓ Test data cleaned up");
} catch (error) {
console.error("❌ Error during testing:", error);
}
console.log("\nTest completed.");

View File

@@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Map Test - Mobile View</title>
<style>
body {
margin: 0;
padding: 20px;
}
.container {
max-width: 400px;
margin: 0 auto;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
iframe {
width: 100%;
height: 600px;
border: none;
}
.info {
padding: 10px;
background: #f5f5f5;
font-family: Arial, sans-serif;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="info">
<strong>Mobile View Test (400px width)</strong><br />
Testing responsive behavior of the projects map
</div>
<iframe src="http://localhost:3000/projects/map"></iframe>
</div>
</body>
</html>

View File

@@ -1,47 +0,0 @@
// Simple test for NextAuth endpoints
const BASE_URL = 'http://localhost:3000';
async function testNextAuthEndpoints() {
console.log('🔐 Testing NextAuth Endpoints\n');
// Test session endpoint
try {
const sessionResponse = await fetch(`${BASE_URL}/api/auth/session`);
console.log(`Session endpoint: ${sessionResponse.status} ${sessionResponse.statusText}`);
if (sessionResponse.ok) {
const sessionData = await sessionResponse.json();
console.log(`Session data: ${JSON.stringify(sessionData)}\n`);
}
} catch (error) {
console.log(`Session endpoint error: ${error.message}\n`);
}
// Test providers endpoint
try {
const providersResponse = await fetch(`${BASE_URL}/api/auth/providers`);
console.log(`Providers endpoint: ${providersResponse.status} ${providersResponse.statusText}`);
if (providersResponse.ok) {
const providersData = await providersResponse.json();
console.log(`Providers: ${JSON.stringify(providersData, null, 2)}\n`);
}
} catch (error) {
console.log(`Providers endpoint error: ${error.message}\n`);
}
// Test CSRF endpoint
try {
const csrfResponse = await fetch(`${BASE_URL}/api/auth/csrf`);
console.log(`CSRF endpoint: ${csrfResponse.status} ${csrfResponse.statusText}`);
if (csrfResponse.ok) {
const csrfData = await csrfResponse.json();
console.log(`CSRF token present: ${csrfData.csrfToken ? 'Yes' : 'No'}\n`);
}
} catch (error) {
console.log(`CSRF endpoint error: ${error.message}\n`);
}
}
testNextAuthEndpoints().catch(console.error);

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env node
/**
* Test script to verify notifications API
*/
async function testNotificationsAPI() {
try {
console.log("Testing notifications API...");
// Test unread count endpoint
const unreadResponse = await fetch('http://localhost:3001/api/notifications/unread-count');
if (unreadResponse.ok) {
const unreadData = await unreadResponse.json();
console.log("✅ Unread count:", unreadData.unreadCount);
} else {
console.log("❌ Unread count endpoint failed:", unreadResponse.status);
}
// Test notifications list endpoint
const notificationsResponse = await fetch('http://localhost:3001/api/notifications');
if (notificationsResponse.ok) {
const notificationsData = await notificationsResponse.json();
console.log("✅ Notifications fetched:", notificationsData.notifications.length);
console.log("Sample notification:", notificationsData.notifications[0]);
} else {
console.log("❌ Notifications endpoint failed:", notificationsResponse.status);
}
} catch (error) {
console.error("Error testing API:", error);
}
}
testNotificationsAPI();

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env node
/**
* Test script to verify notifications are working
*/
async function testNotifications() {
try {
console.log("Testing notifications system...");
// Test unread count endpoint (this should work without auth for now)
console.log("1. Testing unread count endpoint...");
const unreadResponse = await fetch('http://localhost:3001/api/notifications/unread-count');
if (unreadResponse.status === 401) {
console.log("✅ Unread count endpoint requires auth (expected)");
} else if (unreadResponse.ok) {
const data = await unreadResponse.json();
console.log("✅ Unread count:", data.unreadCount);
} else {
console.log("❌ Unread count endpoint failed:", unreadResponse.status);
}
// Test notifications endpoint
console.log("2. Testing notifications endpoint...");
const notificationsResponse = await fetch('http://localhost:3001/api/notifications');
if (notificationsResponse.status === 401) {
console.log("✅ Notifications endpoint requires auth (expected)");
} else if (notificationsResponse.ok) {
const data = await notificationsResponse.json();
console.log("✅ Notifications fetched:", data.notifications?.length || 0);
} else {
console.log("❌ Notifications endpoint failed:", notificationsResponse.status);
}
console.log("\n🎉 Notification system test completed!");
console.log("Note: API endpoints require authentication, so 401 responses are expected.");
console.log("Test the UI by logging into the application and checking the notification dropdown.");
} catch (error) {
console.error("❌ Error testing notifications:", error);
}
}
testNotifications();

View File

@@ -1,27 +0,0 @@
import fetch from "node-fetch";
async function testProjectAPI() {
const baseURL = "http://localhost:3000";
console.log("Testing project API endpoints...\n");
try {
// Test fetching project 1
console.log("1. Fetching project 1:");
const response = await fetch(`${baseURL}/api/projects/1`);
console.log("Status:", response.status);
if (response.ok) {
const project = await response.json();
console.log("Project data received:");
console.log(JSON.stringify(project, null, 2));
} else {
const error = await response.text();
console.log("Error:", error);
}
} catch (error) {
console.error("Error testing API:", error.message);
}
}
testProjectAPI();

View File

@@ -1,43 +0,0 @@
// Test project creation
const BASE_URL = "http://localhost:3001";
async function testProjectCreation() {
console.log("🧪 Testing project creation...\n");
try {
// First, login to get session
console.log("1. Logging in...");
const loginResponse = await fetch(
`${BASE_URL}/api/auth/signin/credentials`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "admin@localhost.com",
password: "admin123456",
}),
}
);
console.log("Login response status:", loginResponse.status);
const loginResult = await loginResponse.text();
console.log("Login result:", loginResult.substring(0, 200));
// Try a simple API call to see the auth system
console.log("\n2. Testing projects API...");
const projectsResponse = await fetch(`${BASE_URL}/api/projects`);
console.log("Projects API status:", projectsResponse.status);
if (projectsResponse.status === 401) {
console.log("❌ Authentication required (expected for this test)");
} else {
const projectsData = await projectsResponse.json();
console.log("✅ Projects API accessible");
console.log("Number of projects:", projectsData.length);
}
} catch (error) {
console.error("❌ Test failed:", error.message);
}
}
testProjectCreation();

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env node
/**
* Test Radicale sync configuration
*/
import { getRadicaleConfig, isRadicaleEnabled, generateVCard } from './src/lib/radicale-sync.js';
console.log('🧪 Testing Radicale Sync Configuration\n');
// Check if enabled
if (isRadicaleEnabled()) {
const config = getRadicaleConfig();
console.log('✅ Radicale sync is ENABLED');
console.log(` URL: ${config.url}`);
console.log(` Username: ${config.username}`);
console.log(` Password: ${config.password ? '***' + config.password.slice(-3) : 'not set'}`);
} else {
console.log('❌ Radicale sync is DISABLED');
console.log(' Set RADICALE_URL, RADICALE_USERNAME, and RADICALE_PASSWORD in .env.local to enable');
}
console.log('\n📝 Testing VCARD Generation\n');
// Test VCARD generation
const testContact = {
contact_id: 999,
name: 'Jan Kowalski',
phone: '["123-456-789", "987-654-321"]',
email: 'jan.kowalski@example.com',
company: 'Test Company',
position: 'Manager',
contact_type: 'project',
notes: 'Test contact for VCARD generation',
is_active: 1,
created_at: new Date().toISOString()
};
const vcard = generateVCard(testContact);
console.log('Generated VCARD:');
console.log('─'.repeat(60));
console.log(vcard);
console.log('─'.repeat(60));
console.log('\n✅ Test complete!');

View File

@@ -1,82 +0,0 @@
/**
* Test the safe audit logging in different runtime environments
*/
console.log("Testing Safe Audit Logging...\n");
// Test 1: Import the safe module (should work in any runtime)
console.log("1. Testing safe module import...");
try {
const { AUDIT_ACTIONS, RESOURCE_TYPES, logAuditEventSafe } = await import(
"./src/lib/auditLogSafe.js"
);
console.log("✅ Safe module imported successfully");
console.log(` Available actions: ${Object.keys(AUDIT_ACTIONS).length}`);
console.log(
` Available resource types: ${Object.keys(RESOURCE_TYPES).length}`
);
} catch (error) {
console.log("❌ Failed to import safe module:", error.message);
}
// Test 2: Test in simulated Edge Runtime
console.log("\n2. Testing in simulated Edge Runtime...");
global.EdgeRuntime = "edge";
try {
const { logAuditEventSafe, AUDIT_ACTIONS, RESOURCE_TYPES } = await import(
"./src/lib/auditLogSafe.js"
);
await logAuditEventSafe({
action: AUDIT_ACTIONS.PROJECT_VIEW,
userId: null, // Use null to avoid foreign key constraint
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "test-123",
details: { test: "edge runtime" },
});
console.log("✅ Edge Runtime logging successful (console only)");
} catch (error) {
console.log("❌ Edge Runtime logging failed:", error.message);
}
// Test 3: Test in simulated Node.js Runtime
console.log("\n3. Testing in simulated Node.js Runtime...");
delete global.EdgeRuntime;
try {
const { logAuditEventSafe, AUDIT_ACTIONS, RESOURCE_TYPES } = await import(
"./src/lib/auditLogSafe.js"
);
await logAuditEventSafe({
action: AUDIT_ACTIONS.PROJECT_CREATE,
userId: null, // Use null to avoid foreign key constraint
resourceType: RESOURCE_TYPES.PROJECT,
resourceId: "test-456",
details: { test: "nodejs runtime" },
});
console.log("✅ Node.js Runtime logging successful (database + console)");
} catch (error) {
console.log("❌ Node.js Runtime logging failed:", error.message);
}
// Test 4: Test constants accessibility
console.log("\n4. Testing constants accessibility...");
try {
const { AUDIT_ACTIONS, RESOURCE_TYPES } = await import(
"./src/lib/auditLogSafe.js"
);
console.log("✅ Constants accessible:");
console.log(` LOGIN action: ${AUDIT_ACTIONS.LOGIN}`);
console.log(` PROJECT resource: ${RESOURCE_TYPES.PROJECT}`);
console.log(` NOTE_CREATE action: ${AUDIT_ACTIONS.NOTE_CREATE}`);
} catch (error) {
console.log("❌ Constants not accessible:", error.message);
}
console.log("\n✅ Safe Audit Logging test completed!");
console.log("\nKey features verified:");
console.log("- ✅ No static database imports");
console.log("- ✅ Edge Runtime compatibility");
console.log("- ✅ Graceful fallbacks");
console.log("- ✅ Constants always available");
console.log("- ✅ Async/await support");
console.log("\nThe middleware should now work without Edge Runtime errors!");

View File

@@ -1,44 +0,0 @@
// Test the project-tasks API endpoints
async function testAPI() {
const baseURL = "http://localhost:3000";
console.log("Testing project-tasks API endpoints...\n");
try {
// Test 1: Check if users endpoint exists
console.log("1. Testing /api/project-tasks/users:");
const usersResponse = await fetch(`${baseURL}/api/project-tasks/users`);
console.log("Status:", usersResponse.status);
if (usersResponse.ok) {
const users = await usersResponse.json();
console.log("Users found:", users.length);
console.log("First user:", users[0]);
} else {
const error = await usersResponse.text();
console.log("Error:", error);
}
// Test 2: Try to create a task (this will fail without auth, but let's see the response)
console.log("\n2. Testing POST /api/project-tasks:");
const taskData = {
project_id: 1,
task_template_id: 1,
priority: "normal",
};
const createResponse = await fetch(`${baseURL}/api/project-tasks`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(taskData),
});
console.log("Status:", createResponse.status);
const responseText = await createResponse.text();
console.log("Response:", responseText);
} catch (error) {
console.error("Error testing API:", error.message);
}
}
testAPI();

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env node
// Test script to verify task sets functionality
import { getAllTaskSets, createTaskSet } from './src/lib/queries/tasks.js';
import initializeDatabase from './src/lib/init-db.js';
async function testTaskSets() {
console.log('Testing Task Sets Database Functions...\n');
try {
// Initialize database
initializeDatabase();
// Test 1: Get all task sets
console.log('1. Getting all task sets...');
const taskSets = getAllTaskSets();
console.log(`Found ${taskSets.length} task sets:`);
taskSets.forEach(set => {
console.log(` - ${set.name} (${set.task_category}):`, JSON.stringify(set));
});
// Test 2: Create a new task set (design)
console.log('\n2. Creating design task set...');
const designSetId = createTaskSet({
name: 'Test Design Set',
description: 'Test task set for design tasks',
task_category: 'design',
templates: []
});
console.log(`Created task set with ID: ${designSetId}`);
// Test 3: Create a construction task set
console.log('\n3. Creating construction task set...');
const constructionSetId = createTaskSet({
name: 'Test Construction Set',
description: 'Test task set for construction tasks',
task_category: 'construction',
templates: []
});
console.log(`Created task set with ID: ${constructionSetId}`);
// Test 4: Try to create invalid task set (should fail)
console.log('\n4. Testing invalid task category (should fail)...');
try {
const invalidSetId = createTaskSet({
name: 'Invalid Set',
description: 'This should fail',
task_category: 'design+construction',
templates: []
});
console.log('✗ Should have failed to create invalid task set');
} catch (error) {
console.log('✓ Correctly rejected invalid task category:', error.message);
}
// Test 5: Get all task sets again
console.log('\n5. Getting all task sets after creation...');
const updatedTaskSets = getAllTaskSets();
console.log(`Found ${updatedTaskSets.length} task sets:`);
updatedTaskSets.forEach(set => {
console.log(` - ${set.name} (${set.task_category})`);
});
console.log('\n✅ All tests passed! Task sets functionality is working correctly.');
} catch (error) {
console.error('Test failed:', error.message);
}
}
testTaskSets();

View File

@@ -1,27 +0,0 @@
import {
getAllProjects,
getAllUsersForAssignment,
} from "./src/lib/queries/projects.js";
import initializeDatabase from "./src/lib/init-db.js";
// Initialize database
initializeDatabase();
console.log("Testing user tracking in projects...\n");
console.log("1. Available users for assignment:");
const users = getAllUsersForAssignment();
console.log(JSON.stringify(users, null, 2));
console.log("\n2. Current projects with user information:");
const projects = getAllProjects();
console.log("Total projects:", projects.length);
if (projects.length > 0) {
console.log("\nFirst project details:");
console.log(JSON.stringify(projects[0], null, 2));
} else {
console.log("No projects found.");
}
console.log("\n✅ User tracking implementation test completed!");

View File

@@ -1,28 +0,0 @@
import Database from "better-sqlite3";
const db = new Database("./data/database.sqlite");
console.log("🔄 Updating admin username...");
try {
// Update admin username from email to simple "admin"
const result = db.prepare('UPDATE users SET username = ? WHERE username = ?').run('admin', 'admin@localhost.com');
if (result.changes > 0) {
console.log('✅ Admin username updated to "admin"');
} else {
console.log(' No admin user found with email "admin@localhost.com"');
}
// Show current users
const users = db.prepare("SELECT name, username, role FROM users").all();
console.log("\nCurrent users:");
users.forEach(user => {
console.log(` - ${user.name} (${user.role}): username="${user.username}"`);
});
} catch (error) {
console.error("❌ Error:", error.message);
} finally {
db.close();
}

View File

@@ -1,20 +0,0 @@
$files = @(
"d:\panel\src\lib\queries\tasks.js",
"d:\panel\src\lib\userManagement.js"
)
foreach ($file in $files) {
if (Test-Path $file) {
Write-Host "Updating $file..."
$content = Get-Content $file -Raw
$content = $content -replace "creator\.email as created_by_email", "creator.username as created_by_username"
$content = $content -replace "assignee\.email as assigned_to_email", "assignee.username as assigned_to_username"
$content = $content -replace "u\.email as created_by_email", "u.username as created_by_username"
$content = $content -replace "SELECT id, name, email, role", "SELECT id, name, username, role"
$content = $content -replace "name, email, role", "name, username, role"
Set-Content $file $content -NoNewline
Write-Host "Updated $file"
}
}
Write-Host "All files updated!"

View File

@@ -1,101 +0,0 @@
import {
logAuditEvent,
getAuditLogs,
AUDIT_ACTIONS,
RESOURCE_TYPES,
} from "./src/lib/auditLog.js";
console.log("=== FINAL AUDIT LOGGING VERIFICATION ===\n");
async function verifyAuditLogging() {
try {
// 1. Check recent audit logs
console.log("1. Checking recent audit logs for user ID issues...");
const recentLogs = await getAuditLogs({ limit: 10 });
console.log(`Found ${recentLogs.length} recent audit events:`);
recentLogs.forEach((log, index) => {
const userDisplay = log.user_id ? `user ${log.user_id}` : "NULL USER ID";
console.log(
`${index + 1}. ${log.timestamp} - ${log.action} by ${userDisplay} on ${
log.resource_type
}:${log.resource_id || "N/A"}`
);
});
// 2. Count null user IDs
const allLogs = await getAuditLogs();
const nullUserCount = allLogs.filter((log) => log.user_id === null).length;
const totalCount = allLogs.length;
const nullPercentage = ((nullUserCount / totalCount) * 100).toFixed(2);
console.log(`\n2. Audit Log Statistics:`);
console.log(` Total audit logs: ${totalCount}`);
console.log(` Logs with NULL user_id: ${nullUserCount}`);
console.log(` Percentage with NULL user_id: ${nullPercentage}%`);
// 3. Check distribution by action type
console.log(`\n3. Action distribution for NULL user_id logs:`);
const nullUserLogs = allLogs.filter((log) => log.user_id === null);
const actionCounts = {};
nullUserLogs.forEach((log) => {
actionCounts[log.action] = (actionCounts[log.action] || 0) + 1;
});
Object.entries(actionCounts).forEach(([action, count]) => {
console.log(` ${action}: ${count} events`);
});
// 4. Test new audit event with valid user ID
console.log(`\n4. Testing new audit event with valid user ID...`);
await logAuditEvent({
action: AUDIT_ACTIONS.LOGIN,
userId: "test-user-123",
resourceType: RESOURCE_TYPES.SESSION,
ipAddress: "127.0.0.1",
userAgent: "Test Agent",
details: {
test: "verification",
timestamp: new Date().toISOString(),
},
});
// Verify the new event was logged correctly
const verificationLogs = await getAuditLogs({ limit: 1 });
const latestLog = verificationLogs[0];
if (latestLog && latestLog.user_id === "test-user-123") {
console.log("✅ SUCCESS: New audit event logged with correct user ID");
} else {
console.log(
"❌ FAILED: New audit event has incorrect user ID:",
latestLog?.user_id
);
}
// 5. Summary
console.log(`\n5. SUMMARY:`);
if (nullPercentage < 10) {
console.log("✅ EXCELLENT: Very few NULL user IDs detected");
} else if (nullPercentage < 30) {
console.log("⚠️ GOOD: Some NULL user IDs but manageable");
} else {
console.log("❌ NEEDS ATTENTION: High percentage of NULL user IDs");
}
console.log(`\n6. RECOMMENDATIONS:`);
if (nullUserCount > 0) {
console.log(
" - The NULL user IDs are likely from before the fix was applied"
);
console.log(" - New audit events should now log user IDs correctly");
console.log(" - Monitor future logs to ensure the fix is working");
} else {
console.log(" - All audit events have valid user IDs!");
}
} catch (error) {
console.error("Verification failed:", error);
}
}
verifyAuditLogging();

View File

@@ -1,7 +0,0 @@
import { getProjectById } from "./src/lib/queries/projects.js";
console.log("Checking the created project with user tracking...\n");
const project = getProjectById(17);
console.log("Project details:");
console.log(JSON.stringify(project, null, 2));