Apply patch [skip ci]

This commit is contained in:
open-swe-dev-andrinoid[bot] 2026-01-20 16:52:24 +00:00
parent 27bac30445
commit 726358ce8f
6 changed files with 237 additions and 194 deletions

View File

@ -1,13 +1,17 @@
# CyberHybridHub - Deno Fresh Web App Setup Guide # CyberHybridHub - Deno Fresh Web App Setup Guide
## Overview ## Overview
CyberHybridHub is a modern web application built with Deno Fresh framework, utilizing Drizzle ORM for database management, TailwindCSS for styling, and designed for Datona sandbox environments.
CyberHybridHub is a modern web application built with Deno Fresh framework,
utilizing Drizzle ORM for database management, TailwindCSS for styling, and
designed for Datona sandbox environments.
<dependencies_and_installation> <dependencies_and_installation>
**Package Manager**: Use Deno CLI exclusively (no npm/yarn required) **Package Manager**: Use Deno CLI exclusively (no npm/yarn required)
**Core Dependencies**: **Core Dependencies**:
- **Deno**: Latest runtime (https://deno.land/) - **Deno**: Latest runtime (https://deno.land/)
- **Fresh Framework**: `jsr:@fresh/core@^2.2.0` - **Fresh Framework**: `jsr:@fresh/core@^2.2.0`
- **Database**: Deno KV (built-in key-value storage) - **Database**: Deno KV (built-in key-value storage)
@ -16,18 +20,20 @@ CyberHybridHub is a modern web application built with Deno Fresh framework, util
- **Authentication**: Local authentication system - **Authentication**: Local authentication system
**Database Setup**: **Database Setup**:
- Deno KV is used for Datona sandbox compatibility - Deno KV is used for Datona sandbox compatibility
- Database initialized automatically on startup - Database initialized automatically on startup
- No external database server required - No external database server required
- Built-in Deno KV storage handles all data persistence - Built-in Deno KV storage handles all data persistence
**External Services**: **External Services**:
- **Local File Storage**: Used for blob storage in sandbox - **Local File Storage**: Used for blob storage in sandbox
- **In-Memory Cache**: Built-in Deno KV for caching - **In-Memory Cache**: Built-in Deno KV for caching
- No external service dependencies required for sandbox - No external service dependencies required for sandbox
**Environment Variables**: **Environment Variables**: Set the following environment variables before
Set the following environment variables before running the application: running the application:
```bash ```bash
# Server Configuration # Server Configuration
@ -46,6 +52,7 @@ LOCAL_DEV_MODE="true"
``` ```
**Startup Sequence**: **Startup Sequence**:
1. Install Deno: 1. Install Deno:
```bash ```bash
curl -LO https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip curl -LO https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip
@ -58,6 +65,7 @@ LOCAL_DEV_MODE="true"
4. Start development server: `deno task dev` (Deno KV initializes automatically) 4. Start development server: `deno task dev` (Deno KV initializes automatically)
**Available Deno Tasks**: **Available Deno Tasks**:
- `deno task dev` - Start development server with Vite - `deno task dev` - Start development server with Vite
- `deno task build` - Build for production - `deno task build` - Build for production
- `deno task start` - Start production server - `deno task start` - Start production server
@ -65,12 +73,14 @@ LOCAL_DEV_MODE="true"
- `deno task e2e` - Run Playwright end-to-end tests - `deno task e2e` - Run Playwright end-to-end tests
**Development Workflow**: **Development Workflow**:
1. Make code changes 1. Make code changes
2. Run `deno task check` to validate code quality 2. Run `deno task check` to validate code quality
3. Test with `deno task test` and `deno task e2e` 3. Test with `deno task test` and `deno task e2e`
4. Deno KV handles data persistence automatically 4. Deno KV handles data persistence automatically
**Datona Sandbox Deployment**: **Datona Sandbox Deployment**:
1. Set `SANDBOX_MODE="true"` in environment 1. Set `SANDBOX_MODE="true"` in environment
2. Ensure all external API calls are disabled 2. Ensure all external API calls are disabled
3. Use local file storage instead of cloud services 3. Use local file storage instead of cloud services
@ -82,6 +92,7 @@ LOCAL_DEV_MODE="true"
## Architecture Overview ## Architecture Overview
### Directory Structure ### Directory Structure
- `routes/` - Fresh framework route handlers and pages - `routes/` - Fresh framework route handlers and pages
- `islands/` - Interactive Preact components - `islands/` - Interactive Preact components
- `components/` - Reusable UI components - `components/` - Reusable UI components
@ -93,6 +104,7 @@ LOCAL_DEV_MODE="true"
- `drizzle/` - Database migration files - `drizzle/` - Database migration files
### Key Technologies ### Key Technologies
- **Fresh**: Full-stack web framework with edge-first architecture - **Fresh**: Full-stack web framework with edge-first architecture
- **Deno KV**: Built-in key-value storage for data persistence - **Deno KV**: Built-in key-value storage for data persistence
- **TailwindCSS**: Utility-first CSS framework - **TailwindCSS**: Utility-first CSS framework
@ -100,6 +112,7 @@ LOCAL_DEV_MODE="true"
- **Local Authentication**: JWT-based authentication system - **Local Authentication**: JWT-based authentication system
### Authentication Flow ### Authentication Flow
1. User registers or logs in via local authentication 1. User registers or logs in via local authentication
2. Credentials validated against Deno KV storage 2. Credentials validated against Deno KV storage
3. JWT token generated for session management 3. JWT token generated for session management
@ -109,6 +122,7 @@ LOCAL_DEV_MODE="true"
## Development Guidelines ## Development Guidelines
### Code Style ### Code Style
- Use TypeScript for all new code - Use TypeScript for all new code
- Follow Fresh framework conventions - Follow Fresh framework conventions
- Utilize TailwindCSS for styling - Utilize TailwindCSS for styling
@ -116,6 +130,7 @@ LOCAL_DEV_MODE="true"
- Write tests for new features and functions - Write tests for new features and functions
### Database Operations ### Database Operations
- All data operations use Deno KV key-value storage - All data operations use Deno KV key-value storage
- No schema migrations required - No schema migrations required
- Automatic data persistence and backup - Automatic data persistence and backup
@ -123,10 +138,10 @@ LOCAL_DEV_MODE="true"
- Simple API for CRUD operations - Simple API for CRUD operations
### Security Considerations ### Security Considerations
- Never commit secrets or API keys - Never commit secrets or API keys
- Use environment variables for sensitive configuration - Use environment variables for sensitive configuration
- Implement proper input validation and sanitization - Implement proper input validation and sanitization
- Follow OWASP security guidelines - Follow OWASP security guidelines
- Use local authentication to avoid external dependencies - Use local authentication to avoid external dependencies
- Regular security audits and dependency updates - Regular security audits and dependency updates

30
TODO.md
View File

@ -3,39 +3,50 @@
## Code Formatting Issues (deno task check) ## Code Formatting Issues (deno task check)
### Critical Issues ### Critical Issues
- **5 files not formatted properly** - Need to run `deno fmt` to fix formatting - **5 files not formatted properly** - Need to run `deno fmt` to fix formatting
- Files affected: - Files affected:
- `routes/tenant/messages/index.tsx` - Import order and formatting issues - `routes/tenant/messages/index.tsx` - Import order and formatting issues
- `routes/tenant/settings/index.tsx` - Function definition formatting and indentation - `routes/tenant/settings/index.tsx` - Function definition formatting and
indentation
- `services/messageService.ts` - Import order issues - `services/messageService.ts` - Import order issues
- Additional files with formatting problems (truncated output) - Additional files with formatting problems (truncated output)
### Specific Formatting Problems ### Specific Formatting Problems
1. **Import Statement Order** - Multiple files have incorrect import ordering 1. **Import Statement Order** - Multiple files have incorrect import ordering
2. **Function Definition Formatting** - Functions not properly formatted according to Deno standards 2. **Function Definition Formatting** - Functions not properly formatted
according to Deno standards
3. **Indentation Issues** - Inconsistent indentation in JSX components 3. **Indentation Issues** - Inconsistent indentation in JSX components
4. **Line Break Issues** - Missing or extra line breaks in various files 4. **Line Break Issues** - Missing or extra line breaks in various files
## E2E Test Failures (deno task e2e) ## E2E Test Failures (deno task e2e)
### Failed Tests ### Failed Tests
1. **reminders.spec.ts:4** - "landlord can send reminder and tenant receives it" 1. **reminders.spec.ts:4** - "landlord can send reminder and tenant receives it"
- **Error**: Element with role "heading" and name matching /messages/i not found - **Error**: Element with role "heading" and name matching /messages/i not
found
- **Expected**: Messages heading should be visible - **Expected**: Messages heading should be visible
- **Status**: Timeout after 5000ms - **Status**: Timeout after 5000ms
2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord communication" 2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord
- **Error**: Element with role "heading" and name matching /messages/i not found communication"
- **Error**: Element with role "heading" and name matching /messages/i not
found
- **Expected**: Messages heading should be visible - **Expected**: Messages heading should be visible
- **Status**: Timeout after 5000ms - **Status**: Timeout after 5000ms
### Test Infrastructure Issues ### Test Infrastructure Issues
- **Static File Serving Error**: Server crashes when trying to serve static files
- **Static File Serving Error**: Server crashes when trying to serve static
files
- Error: "Failed to read directory: No such file or directory (os error 2)" - Error: "Failed to read directory: No such file or directory (os error 2)"
- Path: `/Users/nanderson/nathanAndrinoid/cyberhybridhub.com/static` - Path: `/Users/nanderson/nathanAndrinoid/cyberhybridhub.com/static`
- Impact: Tests failing due to missing static assets - Impact: Tests failing due to missing static assets
### Root Cause Analysis ### Root Cause Analysis
- Both failed tests are looking for a "Messages" heading that doesn't exist - Both failed tests are looking for a "Messages" heading that doesn't exist
- This suggests either: - This suggests either:
- The messages page/component is not rendering properly - The messages page/component is not rendering properly
@ -45,20 +56,25 @@
## Priority Actions ## Priority Actions
### High Priority ### High Priority
1. **Fix static file serving** - Create missing `/static` directory or fix configuration
1. **Fix static file serving** - Create missing `/static` directory or fix
configuration
2. **Investigate messages page** - Check why messages heading is not rendering 2. **Investigate messages page** - Check why messages heading is not rendering
3. **Run code formatting** - Execute `deno fmt` to fix all formatting issues 3. **Run code formatting** - Execute `deno fmt` to fix all formatting issues
### Medium Priority ### Medium Priority
1. **Review test expectations** - Verify if test assertions match actual UI 1. **Review test expectations** - Verify if test assertions match actual UI
2. **Check navigation flow** - Ensure users can properly navigate to messages 2. **Check navigation flow** - Ensure users can properly navigate to messages
3. **Fix import statements** - Correct import order in affected files 3. **Fix import statements** - Correct import order in affected files
### Low Priority ### Low Priority
1. **Code style consistency** - Ensure all files follow consistent formatting 1. **Code style consistency** - Ensure all files follow consistent formatting
2. **Add error handling** - Improve error handling for missing static files 2. **Add error handling** - Improve error handling for missing static files
## Next Steps ## Next Steps
1. Create `/static` directory if missing 1. Create `/static` directory if missing
2. Run `deno fmt` to fix formatting 2. Run `deno fmt` to fix formatting
3. Debug messages page rendering issue 3. Debug messages page rendering issue

View File

@ -3,10 +3,10 @@ import { define } from "../../../utils.ts";
import { page } from "fresh"; import { page } from "fresh";
import { SessionCookie } from "../../auth/callback.ts"; import { SessionCookie } from "../../auth/callback.ts";
import { import {
DashboardMessage,
deleteMessagesForUser, deleteMessagesForUser,
getMessagesForUser, getMessagesForUser,
markMessagesAsRead, markMessagesAsRead,
DashboardMessage,
MessagePage, MessagePage,
} from "../../../services/messageService.ts"; } from "../../../services/messageService.ts";
import LandlordMessageSelection from "../../../islands/LandlordMessageSelection.tsx"; import LandlordMessageSelection from "../../../islands/LandlordMessageSelection.tsx";

View File

@ -49,7 +49,9 @@ export const handler = define.handlers({
return page<PropertyPageData>({ return page<PropertyPageData>({
lease: currentLease, lease: currentLease,
property, property,
propertyPhotos: propertyPhotos.filter((m: PropertyMedia) => m.media_type === "photo"), propertyPhotos: propertyPhotos.filter((m: PropertyMedia) =>
m.media_type === "photo"
),
leaseDocuments, leaseDocuments,
}); });
} catch (error) { } catch (error) {
@ -148,159 +150,167 @@ function DocumentList({ documents }: { documents: LeaseDocument[] }) {
); );
} }
export default define.page(function TenantPropertyPage({ data }: { data: PropertyPageData }) { export default define.page(
const { lease, property, propertyPhotos, leaseDocuments, error } = function TenantPropertyPage({ data }: { data: PropertyPageData }) {
data as PropertyPageData; const { lease, property, propertyPhotos, leaseDocuments, error } =
data as PropertyPageData;
if (error) { if (error) {
return ( return (
<div class="min-h-screen bg-gray-50 p-8"> <div class="min-h-screen bg-gray-50 p-8">
<AlertMessage type="error">{error}</AlertMessage> <AlertMessage type="error">{error}</AlertMessage>
<div class="mt-6"> <div class="mt-6">
<a <a
href="/tenant/dashboard" href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium" class="text-blue-600 hover:text-blue-500 font-medium"
> >
Go to Dashboard Go to Dashboard
</a> </a>
</div>
</div>
);
}
if (!property) {
return (
<div class="min-h-screen bg-gray-50 p-8">
<AlertMessage type="error">
Property details could not be loaded.
</AlertMessage>
</div>
);
}
const addressLine = formatPropertyAddress(property);
const amenities = parseList(property.amenities);
const rules = parseList(lease?.late_fee_rules ?? "");
const photoItems = (propertyPhotos ?? []).filter(
(photo): photo is PropertyMedia & { file_url: string } =>
Boolean(photo.file_url),
);
return (
<div class="min-h-screen bg-gray-50">
{/* Header */}
<div class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6">
<h1 class="text-3xl font-bold text-gray-900">
Your Current Residence
</h1>
<p class="mt-1 text-sm text-gray-500">
{addressLine || "Address not available"}
</p>
</div> </div>
</div> </div>
</div> );
}
{/* Main Content */} if (!property) {
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8"> return (
{/* Property Photos */} <div class="min-h-screen bg-gray-50 p-8">
{photoItems.length > 0 && ( <AlertMessage type="error">
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-6"> Property details could not be loaded.
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2"> </AlertMessage>
Property Photos </div>
</h3> );
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"> }
{photoItems.map((photo) => (
<img const addressLine = formatPropertyAddress(property);
src={photo.file_url} const amenities = parseList(property.amenities);
alt={photo.file_name || "Property Photo"} const rules = parseList(lease?.late_fee_rules ?? "");
class="w-full h-48 object-cover rounded-lg shadow-md" const photoItems = (propertyPhotos ?? []).filter(
/> (photo): photo is PropertyMedia & { file_url: string } =>
))} Boolean(photo.file_url),
);
return (
<div class="min-h-screen bg-gray-50">
{/* Header */}
<div class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6">
<h1 class="text-3xl font-bold text-gray-900">
Your Current Residence
</h1>
<p class="mt-1 text-sm text-gray-500">
{addressLine || "Address not available"}
</p>
</div> </div>
</div> </div>
)}
{/* Property Details */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Property Details
</h3>
</div>
<div class="border-t border-gray-200">
<dl>
<DetailItem
label="Address"
value={addressLine || "Address not available"}
/>
<DetailItem
label="Unit Number"
value={property.address_2}
/>
<DetailItem label="Bedrooms" value={property.bedrooms} />
<DetailItem label="Bathrooms" value={property.bathrooms} />
<DetailItem
label="Square Footage"
value={property.square_footage}
/>
<DetailItem label="Year Built" value={property.year_built} />
</dl>
</div>
</div> </div>
{/* Amenities */} {/* Main Content */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8">
<div class="px-4 py-5 sm:px-6"> {/* Property Photos */}
<h3 class="text-lg leading-6 font-medium text-gray-900"> {photoItems.length > 0 && (
Amenities <div class="bg-white shadow overflow-hidden sm:rounded-lg p-6">
</h3> <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2">
</div> Property Photos
<div class="border-t border-gray-200 p-6"> </h3>
{amenities.length > 0 <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
? ( {photoItems.map((photo) => (
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700"> <img
{amenities.map((amenity: string) => <li key={amenity}>{amenity}</li>)} src={photo.file_url}
</ul> alt={photo.file_name || "Property Photo"}
) class="w-full h-48 object-cover rounded-lg shadow-md"
: <p class="text-sm text-gray-500">No amenities listed.</p>} />
</div> ))}
</div> </div>
</div>
)}
{/* Rules */} {/* Property Details */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg"> <div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6"> <div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
Community Rules Property Details
</h3> </h3>
</div>
<div class="border-t border-gray-200">
<dl>
<DetailItem
label="Address"
value={addressLine || "Address not available"}
/>
<DetailItem
label="Unit Number"
value={property.address_2}
/>
<DetailItem label="Bedrooms" value={property.bedrooms} />
<DetailItem label="Bathrooms" value={property.bathrooms} />
<DetailItem
label="Square Footage"
value={property.square_footage}
/>
<DetailItem label="Year Built" value={property.year_built} />
</dl>
</div>
</div> </div>
<div class="border-t border-gray-200 p-6">
{rules.length > 0
? (
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
{rules.map((rule: string) => <li key={rule}>{rule}</li>)}
</ul>
)
: <p class="text-sm text-gray-500">No specific rules listed.</p>}
</div>
</div>
{/* Lease Documents */} {/* Amenities */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg"> <div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6"> <div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
Lease Documents Amenities
</h3> </h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500"> </div>
Your current lease agreement and related documents. <div class="border-t border-gray-200 p-6">
</p> {amenities.length > 0
? (
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
{amenities.map((amenity: string) => (
<li key={amenity}>{amenity}</li>
))}
</ul>
)
: <p class="text-sm text-gray-500">No amenities listed.</p>}
</div>
</div> </div>
<div class="border-t border-gray-200">
<DocumentList documents={leaseDocuments || []} /> {/* Rules */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Community Rules
</h3>
</div>
<div class="border-t border-gray-200 p-6">
{rules.length > 0
? (
<ul class="list-disc list-inside space-y-1 text-sm text-gray-700">
{rules.map((rule: string) => <li key={rule}>{rule}</li>)}
</ul>
)
: (
<p class="text-sm text-gray-500">
No specific rules listed.
</p>
)}
</div>
</div>
{/* Lease Documents */}
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Lease Documents
</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">
Your current lease agreement and related documents.
</p>
</div>
<div class="border-t border-gray-200">
<DocumentList documents={leaseDocuments || []} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div> );
); },
}); );

View File

@ -56,46 +56,48 @@ export const handler = define.handlers({
}, },
}); });
export default define.page(function TenantSettingsPage({ data }: { data: SettingsPageData }) { export default define.page(
const { user, error } = data as SettingsPageData; function TenantSettingsPage({ data }: { data: SettingsPageData }) {
const { user, error } = data as SettingsPageData;
if (error) { if (error) {
return ( return (
<div class="min-h-screen bg-gray-50 p-8"> <div class="min-h-screen bg-gray-50 p-8">
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1> <h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1>
<p class="text-gray-700">{error}</p> <p class="text-gray-700">{error}</p>
<div class="mt-6"> <div class="mt-6">
<a <a
href="/tenant/dashboard" href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium" class="text-blue-600 hover:text-blue-500 font-medium"
> >
Go to Dashboard Go to Dashboard
</a> </a>
</div>
</div> </div>
</div> </div>
);
}
return (
<div class="min-h-screen bg-gray-50">
{/* Header */}
<div class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6">
<h1 class="text-3xl font-bold text-gray-900">Tenant Settings</h1>
<p class="mt-1 text-sm text-gray-500">
Manage your notification and payment preferences.
</p>
</div>
</div>
</div>
{/* Main Content */}
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 py-8">
<TenantSettings user={user} />
</div>
</div> </div>
); );
} },
);
return (
<div class="min-h-screen bg-gray-50">
{/* Header */}
<div class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6">
<h1 class="text-3xl font-bold text-gray-900">Tenant Settings</h1>
<p class="mt-1 text-sm text-gray-500">
Manage your notification and payment preferences.
</p>
</div>
</div>
</div>
{/* Main Content */}
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 py-8">
<TenantSettings user={user} />
</div>
</div>
);
});

View File

@ -9,11 +9,11 @@ import {
addE2EMessage, addE2EMessage,
deleteE2EMessage, deleteE2EMessage,
deleteE2EMessagesForUser, deleteE2EMessagesForUser,
getE2EMessageById,
getE2EMessageThread,
getE2ELeaseById, getE2ELeaseById,
getE2EMessageById,
getE2EMessagesByLease, getE2EMessagesByLease,
getE2EMessagesByUser, getE2EMessagesByUser,
getE2EMessageThread,
getE2EPropertyById, getE2EPropertyById,
getE2EUserById, getE2EUserById,
isE2EEnabled, isE2EEnabled,