continuous-integration/drone/pr Build is passing
- /api/oauth/start → JAMBOTRON_URL/oauth/authorize (client mail, PKCE S256); /api/oauth/callback: код меняется на сервере у Jambotron /api/oauth/token (Bearer SERVICE_TOKEN) → рабочий ящик сотрудника → сессия, ADMIN Jambotron — ADMIN. - Вход по паролю в веб — только встроенный администратор (когда вход через Jambotron включён). - Пароли приложений (V9 app_passwords): User panel → APP PASSWORDS, только SMTP/IMAP. - Системные ящики (V9 users.is_system): без входа в веб, открывают встроенные администраторы, не в USERS; Admin panel → MAILBOXES → кнопка SYSTEM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
4.8 KiB
TypeScript
163 lines
4.8 KiB
TypeScript
export interface Folder {
|
|
id: number;
|
|
name: string;
|
|
system: boolean;
|
|
messageCount: number;
|
|
unreadCount: number;
|
|
}
|
|
|
|
export interface Message {
|
|
id: number;
|
|
folderId: number;
|
|
senderEmail: string;
|
|
senderName?: string | null;
|
|
recipientEmail: string;
|
|
ccEmail?: string | null;
|
|
subject: string;
|
|
body: string;
|
|
html: boolean;
|
|
read: boolean;
|
|
flagged: boolean;
|
|
spam: boolean;
|
|
messageSize?: number | null;
|
|
receivedAt: string;
|
|
hasAttachments: boolean;
|
|
attachmentCount: number;
|
|
answeredBy?: string | null; // login that answered it from the web client (shared mailboxes)
|
|
}
|
|
|
|
/** MailboxAccess.Entry — a mailbox of the logged-in user (left menu) */
|
|
export interface MailboxEntry {
|
|
email: string;
|
|
displayName: string;
|
|
kind: 'OWN' | 'MEMBER' | 'ATTACHED';
|
|
shared: boolean;
|
|
unread: number;
|
|
}
|
|
|
|
export interface Attachment {
|
|
id: number;
|
|
filename: string;
|
|
contentType: string;
|
|
sizeBytes: number;
|
|
}
|
|
|
|
export interface Contact {
|
|
id?: number;
|
|
name: string;
|
|
email: string;
|
|
phone?: string;
|
|
company?: string;
|
|
}
|
|
|
|
export interface BlacklistEntry {
|
|
id: number;
|
|
senderEmail: string;
|
|
reason?: string | null;
|
|
createdAt?: string;
|
|
}
|
|
|
|
export interface Profile {
|
|
email: string;
|
|
displayName: string;
|
|
usedBytes: number;
|
|
quotaBytes: number;
|
|
createdAt: string;
|
|
admin?: boolean; // may manage mailboxes (Settings → Mailboxes)
|
|
}
|
|
|
|
/** MailboxAdminController.MailboxView */
|
|
export interface Mailbox {
|
|
id: number;
|
|
email: string;
|
|
displayName: string;
|
|
active: boolean;
|
|
admin: boolean;
|
|
trap: boolean; // what it sends to outside addresses stays in its own INBOX (staging@)
|
|
shared: boolean; // shared mailbox: no login of its own, members open it
|
|
members: string[]; // who may open it
|
|
usedBytes: number;
|
|
quotaBytes: number;
|
|
createdAt: string;
|
|
role: 'ADMIN' | 'USER'; // ADMIN — Admin panel (users, mailboxes, lists)
|
|
builtIn: boolean; // administrator from the server configuration — stays ADMIN
|
|
system: boolean; // no-reply@, monitor@ …: no web login, the built-in administrators open it
|
|
}
|
|
|
|
/** MailLists.MailList — mail to the address → a copy to every member, into the folder (null — INBOX) */
|
|
export interface MailList {
|
|
id: number;
|
|
address: string;
|
|
folder: string | null;
|
|
description: string | null;
|
|
openToOutside: boolean;
|
|
members: string[];
|
|
}
|
|
|
|
/** password — generated by the server, shown once; null when the administrator chose it */
|
|
export interface MailboxCreated {
|
|
mailbox: Mailbox;
|
|
password: string | null;
|
|
}
|
|
|
|
export interface ServerConfig {
|
|
registrationEnabled: boolean;
|
|
domains: string[];
|
|
ssoEnabled?: boolean; // «Sign in with Jambotron»; then the web password is only for the built-in administrator
|
|
}
|
|
|
|
/** AppPasswords.AppPassword — a password for one mail program (SMTP / IMAP only) */
|
|
export interface AppPassword {
|
|
id: number;
|
|
name: string;
|
|
createdAt: string;
|
|
lastUsedAt: string | null;
|
|
}
|
|
|
|
export interface Draft {
|
|
to: string;
|
|
cc: string;
|
|
subject: string;
|
|
body: string;
|
|
html: boolean;
|
|
}
|
|
|
|
/** System folders: label and icon in the web client. */
|
|
export const FOLDER_VIEW: Record<string, { label: string; icon: string }> = {
|
|
INBOX: { label: 'Inbox', icon: 'inbox' },
|
|
Drafts: { label: 'Drafts', icon: 'edit_note' },
|
|
Sent: { label: 'Sent', icon: 'send' },
|
|
Spam: { label: 'Spam', icon: 'report' },
|
|
Trash: { label: 'Trash', icon: 'delete' }
|
|
};
|
|
|
|
export function folderLabel(folder: Folder): string {
|
|
return folder.system ? (FOLDER_VIEW[folder.name]?.label ?? folder.name) : folder.name;
|
|
}
|
|
|
|
export function folderIcon(folder: Folder): string {
|
|
return folder.system ? (FOLDER_VIEW[folder.name]?.icon ?? 'folder') : 'folder';
|
|
}
|
|
|
|
export function formatBytes(bytes: number | null | undefined): string {
|
|
const value = bytes ?? 0;
|
|
if (value < 1024) return `${value} B`;
|
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
|
if (value < 1024 * 1024 * 1024) return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
|
return `${(value / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
}
|
|
|
|
/** Text of an API error: our {error}, Spring's {message}, or the status. */
|
|
export function errorText(error: unknown): string {
|
|
const e = error as { error?: { error?: string; message?: string; status?: number } | string; status?: number; message?: string };
|
|
if (e?.error && typeof e.error === 'object') {
|
|
// Spring's error body: {status, error: "Bad Request", message: "No such mailbox: …"} — the reason is in message
|
|
const body = e.error;
|
|
const springBody = typeof body.status === 'number';
|
|
return (springBody ? body.message || body.error : body.error || body.message) || `Error ${e.status ?? ''}`.trim();
|
|
}
|
|
if (typeof e?.error === 'string' && e.error) return e.error;
|
|
if (e?.status === 0) return 'Server is not reachable';
|
|
return e?.message || 'Something went wrong';
|
|
}
|