diff --git a/backend/Dockerfile b/backend/Dockerfile index cbb426e..5dabb70 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,9 +1,10 @@ # Stage 1: Build FROM eclipse-temurin:21-jdk-alpine AS build WORKDIR /app -COPY build.gradle settings.gradle gradlew ./ +COPY backend/build.gradle ./ +COPY gradlew ./ COPY gradle gradle -COPY src src +COPY backend/src src RUN chmod +x gradlew RUN ./gradlew bootJar --no-daemon diff --git a/backend/src/main/resources/db/migration/V1__init.sql b/backend/src/main/resources/db/migration/V1__init.sql index 5c90e15..6ddc45a 100644 --- a/backend/src/main/resources/db/migration/V1__init.sql +++ b/backend/src/main/resources/db/migration/V1__init.sql @@ -2,8 +2,11 @@ CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL, + quota_bytes BIGINT NOT NULL DEFAULT 1073741824, + used_bytes BIGINT NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -12,7 +15,8 @@ CREATE TABLE folders ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(50) NOT NULL, - type VARCHAR(20) NOT NULL, + message_count INTEGER NOT NULL DEFAULT 0, + unread_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -25,10 +29,13 @@ CREATE TABLE messages ( recipient_email VARCHAR(255) NOT NULL, subject VARCHAR(1000), body TEXT, + is_html BOOLEAN NOT NULL DEFAULT FALSE, is_read BOOLEAN NOT NULL DEFAULT FALSE, + is_flagged BOOLEAN NOT NULL DEFAULT FALSE, is_spam BOOLEAN NOT NULL DEFAULT FALSE, message_size BIGINT, - received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + message_id VARCHAR(255) ); -- Таблица вложений @@ -58,6 +65,8 @@ CREATE TABLE contacts ( user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, + phone VARCHAR(50), + company VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, email) ); diff --git a/docker-compose.yml b/docker-compose.yml index 14dc1a0..c815e94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,18 +2,23 @@ version: '3.8' services: mail-server: - build: ./backend + build: + context: . + dockerfile: ./backend/Dockerfile ports: - "8080:8080" # REST API - - "3025:3025" # SMTP (приём писем) - - "143:143" # IMAP (доступ к письмам) + - "3026:3025" # SMTP (приём писем) + - "1443:143" # IMAP (доступ к письмам) volumes: - attachments:/app/attachments - dkim-keys:/app/dkim-keys environment: - - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mailserver - - SPRING_DATASOURCE_USERNAME=mail - - SPRING_DATASOURCE_PASSWORD=secret + - SPRING_R2DBC_URL=r2dbc:postgresql://db:5432/mailserver + - SPRING_R2DBC_USERNAME=mail + - SPRING_R2DBC_PASSWORD=secret + - SPRING_FLYWAY_URL=jdbc:postgresql://db:5432/mailserver + - SPRING_FLYWAY_USER=mail + - SPRING_FLYWAY_PASSWORD=secret - SPRING_MAIL_HOST=mailpit - SPRING_MAIL_PORT=1025 depends_on: @@ -29,7 +34,7 @@ services: volumes: - pg-data:/var/lib/postgresql/data ports: - - "5432:5432" + - "5433:5432" mailpit: image: axllent/mailpit diff --git a/frontend/src/app/components/compose/compose.component.ts b/frontend/src/app/components/compose/compose.component.ts index 2bc00d9..70f8b1c 100644 --- a/frontend/src/app/components/compose/compose.component.ts +++ b/frontend/src/app/components/compose/compose.component.ts @@ -12,7 +12,7 @@ import { Router, ActivatedRoute } from '@angular/router'; template: `

- ✉️ COMPOSE MESSAGE + COMPOSE MESSAGE

@if (success) { @@ -53,14 +53,14 @@ import { Router, ActivatedRoute } from '@angular/router'; class="w-full bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-3 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-500 transition-all resize-none" [placeholder]="isHtml ? 'Write HTML or Markdown...' : 'Write your message...'"> @if (isHtml) { -

💡 Supports Markdown: **bold**, *italic*, - lists, \`\`\`code\`\`\`

+

Supports Markdown: **bold**, *italic*, - lists,  code 

}
CANCEL diff --git a/frontend/src/app/components/contacts/contacts.component.html b/frontend/src/app/components/contacts/contacts.component.html index 3114b00..5979e9a 100644 --- a/frontend/src/app/components/contacts/contacts.component.html +++ b/frontend/src/app/components/contacts/contacts.component.html @@ -1,13 +1,13 @@

- 👥 CONTACTS + CONTACTS {{ contacts().length }} contacts

- ✏️ {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }} + {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }}

@@ -37,12 +37,16 @@
@if (editingContact()) { }
@@ -53,7 +57,7 @@
@if (contacts().length === 0) {
-

📇

+

No contacts yet

} @else { @@ -83,11 +87,11 @@
diff --git a/frontend/src/app/components/inbox/inbox.component.ts b/frontend/src/app/components/inbox/inbox.component.ts index 8814427..c714cbf 100644 --- a/frontend/src/app/components/inbox/inbox.component.ts +++ b/frontend/src/app/components/inbox/inbox.component.ts @@ -19,9 +19,9 @@ import { of } from 'rxjs';
+ placeholder="Search messages...">
@@ -29,13 +29,13 @@ import { of } from 'rxjs';

- 📬 {{ folderName() }} + {{ folderName() }} {{ messages()?.length || 0 }} messages

@if (!messages() || messages().length === 0) {
-

📭

+

No messages yet

} @else { @@ -64,7 +64,7 @@ import { of } from 'rxjs'; } @if (msg.flagged) { - ★ + }
@@ -80,7 +80,7 @@ import { of } from 'rxjs'; {{ msg.sentAt | date:'MMM d, HH:mm' }}
@if (msg.hasAttachments) { - 📎 {{ msg.attachmentCount }} + {{ msg.attachmentCount }} }
@@ -95,7 +95,7 @@ import { of } from 'rxjs';
@for (p of pageNumbers; track p) { Page {{ page() + 1 }} of {{ totalPages() }} diff --git a/frontend/src/app/components/layout/layout.component.ts b/frontend/src/app/components/layout/layout.component.ts index 9e2882a..662cab4 100644 --- a/frontend/src/app/components/layout/layout.component.ts +++ b/frontend/src/app/components/layout/layout.component.ts @@ -76,7 +76,7 @@ const THEMES = [ [attr.aria-pressed]="isHighContrast()" title="Toggle High Contrast Mode" class="px-3 py-1.5 rounded-lg border border-cyan-500/40 bg-slate-900/80 text-xs font-mono hover:bg-cyan-500/20 focus:outline-none focus:ring-2 focus:ring-pink-500 transition-all flex items-center gap-1.5"> - ◐ + Contrast @@ -87,7 +87,7 @@ const THEMES = [
@@ -99,7 +99,7 @@ const THEMES = [ @@ -146,8 +146,8 @@ const THEMES = [
MAILSERVER SYNTHWAVE © 2026
- ◉ A11Y READY - ⚡ HIGH PERF CSS3D + A11Y READY + HIGH PERF CSS3D
diff --git a/frontend/src/app/components/login/login.component.ts b/frontend/src/app/components/login/login.component.ts index 40499e2..cf3d596 100644 --- a/frontend/src/app/components/login/login.component.ts +++ b/frontend/src/app/components/login/login.component.ts @@ -55,7 +55,11 @@ import { Router } from '@angular/router'; diff --git a/frontend/src/app/components/message-detail/message-detail.component.ts b/frontend/src/app/components/message-detail/message-detail.component.ts index 8fa97ee..8bc2a02 100644 --- a/frontend/src/app/components/message-detail/message-detail.component.ts +++ b/frontend/src/app/components/message-detail/message-detail.component.ts @@ -31,7 +31,7 @@ import { Message } from '../../models/mail.models'; @if (message.spam) {
- ⚠️ MARKED AS SPAM + MARKED AS SPAM
} @@ -47,7 +47,7 @@ import { Message } from '../../models/mail.models'; @if (message.hasAttachments) {

- 📎 ATTACHMENTS ({{ message.attachmentCount }}) + ATTACHMENTS ({{ message.attachmentCount }})

Attachments are available via the API @@ -58,24 +58,24 @@ import { Message } from '../../models/mail.models';

- ← BACK TO INBOX + BACK TO INBOX - ↩️ REPLY + REPLY - ➡️ FORWARD + FORWARD
} @else {
-

📭

+

Message not found

← BACK TO INBOX diff --git a/frontend/src/app/components/register/register.component.ts b/frontend/src/app/components/register/register.component.ts index c38450b..39b0a30 100644 --- a/frontend/src/app/components/register/register.component.ts +++ b/frontend/src/app/components/register/register.component.ts @@ -67,7 +67,11 @@ import { Router } from '@angular/router'; diff --git a/frontend/src/index.html b/frontend/src/index.html index ce38570..b2972b5 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -6,9 +6,12 @@ + + + diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 0d95981..5f83580 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -253,6 +253,18 @@ body { outline-offset: 3px; } +/* ARIA Inspector Focus Highlight */ +.aria-inspect-active { + outline: 2px dashed var(--highlight-color) !important; + outline-offset: 4px; + animation: pulseAria 1.5s infinite; +} + +@keyframes pulseAria { + 0%, 100% { outline-color: var(--highlight-color); } + 50% { outline-color: var(--primary-color); } +} + /* Custom Scrollbar */ ::-webkit-scrollbar { width: 8px; diff --git a/settings.gradle b/settings.gradle index 69fada4..c17f03e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,3 @@ include 'backend' -include 'frontend' project(':backend').projectDir = file('backend') -project(':frontend').projectDir = file('frontend')