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

30
TODO.md
View File

@ -3,39 +3,50 @@
## Code Formatting Issues (deno task check)
### Critical Issues
- **5 files not formatted properly** - Need to run `deno fmt` to fix formatting
- Files affected:
- `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
- Additional files with formatting problems (truncated output)
### Specific Formatting Problems
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
4. **Line Break Issues** - Missing or extra line breaks in various files
## E2E Test Failures (deno task e2e)
### Failed Tests
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
- **Status**: Timeout after 5000ms
2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord communication"
- **Error**: Element with role "heading" and name matching /messages/i not found
2. **tenant-dashboard.spec.ts:50** - "tenant messages list shows landlord
communication"
- **Error**: Element with role "heading" and name matching /messages/i not
found
- **Expected**: Messages heading should be visible
- **Status**: Timeout after 5000ms
### 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)"
- Path: `/Users/nanderson/nathanAndrinoid/cyberhybridhub.com/static`
- Impact: Tests failing due to missing static assets
### Root Cause Analysis
- Both failed tests are looking for a "Messages" heading that doesn't exist
- This suggests either:
- The messages page/component is not rendering properly
@ -45,20 +56,25 @@
## Priority Actions
### 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
3. **Run code formatting** - Execute `deno fmt` to fix all formatting issues
### Medium Priority
1. **Review test expectations** - Verify if test assertions match actual UI
2. **Check navigation flow** - Ensure users can properly navigate to messages
3. **Fix import statements** - Correct import order in affected files
### Low Priority
1. **Code style consistency** - Ensure all files follow consistent formatting
2. **Add error handling** - Improve error handling for missing static files
## Next Steps
1. Create `/static` directory if missing
2. Run `deno fmt` to fix formatting
3. Debug messages page rendering issue

View File

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

View File

@ -49,7 +49,9 @@ export const handler = define.handlers({
return page<PropertyPageData>({
lease: currentLease,
property,
propertyPhotos: propertyPhotos.filter((m: PropertyMedia) => m.media_type === "photo"),
propertyPhotos: propertyPhotos.filter((m: PropertyMedia) =>
m.media_type === "photo"
),
leaseDocuments,
});
} catch (error) {
@ -148,159 +150,167 @@ function DocumentList({ documents }: { documents: LeaseDocument[] }) {
);
}
export default define.page(function TenantPropertyPage({ data }: { data: PropertyPageData }) {
const { lease, property, propertyPhotos, leaseDocuments, error } =
data as PropertyPageData;
export default define.page(
function TenantPropertyPage({ data }: { data: PropertyPageData }) {
const { lease, property, propertyPhotos, leaseDocuments, error } =
data as PropertyPageData;
if (error) {
return (
<div class="min-h-screen bg-gray-50 p-8">
<AlertMessage type="error">{error}</AlertMessage>
<div class="mt-6">
<a
href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium"
>
Go to Dashboard
</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>
if (error) {
return (
<div class="min-h-screen bg-gray-50 p-8">
<AlertMessage type="error">{error}</AlertMessage>
<div class="mt-6">
<a
href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium"
>
Go to Dashboard
</a>
</div>
</div>
</div>
);
}
{/* Main Content */}
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8">
{/* Property Photos */}
{photoItems.length > 0 && (
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2">
Property Photos
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{photoItems.map((photo) => (
<img
src={photo.file_url}
alt={photo.file_name || "Property Photo"}
class="w-full h-48 object-cover rounded-lg shadow-md"
/>
))}
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>
)}
{/* 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>
{/* Amenities */}
<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">
Amenities
</h3>
</div>
<div class="border-t border-gray-200 p-6">
{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>
{/* Main Content */}
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 py-8 space-y-8">
{/* Property Photos */}
{photoItems.length > 0 && (
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 border-b pb-2">
Property Photos
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{photoItems.map((photo) => (
<img
src={photo.file_url}
alt={photo.file_name || "Property Photo"}
class="w-full h-48 object-cover rounded-lg shadow-md"
/>
))}
</div>
</div>
)}
{/* 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>
{/* 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 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>
{/* Amenities */}
<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">
Amenities
</h3>
</div>
<div class="border-t border-gray-200 p-6">
{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 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>
);
});
);
},
);

View File

@ -56,46 +56,48 @@ export const handler = define.handlers({
},
});
export default define.page(function TenantSettingsPage({ data }: { data: SettingsPageData }) {
const { user, error } = data as SettingsPageData;
export default define.page(
function TenantSettingsPage({ data }: { data: SettingsPageData }) {
const { user, error } = data as SettingsPageData;
if (error) {
return (
<div class="min-h-screen bg-gray-50 p-8">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1>
<p class="text-gray-700">{error}</p>
<div class="mt-6">
<a
href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium"
>
Go to Dashboard
</a>
if (error) {
return (
<div class="min-h-screen bg-gray-50 p-8">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-red-600 mb-4">Error</h1>
<p class="text-gray-700">{error}</p>
<div class="mt-6">
<a
href="/tenant/dashboard"
class="text-blue-600 hover:text-blue-500 font-medium"
>
Go to Dashboard
</a>
</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>
);
}
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,
deleteE2EMessage,
deleteE2EMessagesForUser,
getE2EMessageById,
getE2EMessageThread,
getE2ELeaseById,
getE2EMessageById,
getE2EMessagesByLease,
getE2EMessagesByUser,
getE2EMessageThread,
getE2EPropertyById,
getE2EUserById,
isE2EEnabled,