Files
sharedinbox/lib/data/db/database.dart
T
Thomas GüttlerandClaude Sonnet 4.6 b144dba5ec feat: username field separate from email, try-connection button, JMAP auth verification
- Account model gains `username` field (default empty → falls back to email then local-part)
- ConnectionTestService returns the effective username that succeeded; tries email then local-part when blank
- JMAP connection probe uses Basic-auth GET to /.well-known/jmap (401/403 = auth failure)
- IMAP/SMTP factory passes explicit username parameter
- Add/edit account screens show username field and "Try connection" button
- EditAccountScreen reuses stored password when no new password is entered
- Unit tests for ConnectionTestServiceImpl (IMAP + JMAP paths, fallback logic)
- Fix unit test lambda signatures for updated ImapConnectFn/SmtpConnectFn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 15:37:11 +02:00

113 lines
4.0 KiB
Dart

import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
part 'database.g.dart';
// ── Tables ────────────────────────────────────────────────────────────────────
class Accounts extends Table {
TextColumn get id => text()();
TextColumn get displayName => text()();
TextColumn get email => text()();
TextColumn get imapHost => text()();
IntColumn get imapPort => integer()();
BoolColumn get imapSsl => boolean()();
TextColumn get smtpHost => text()();
IntColumn get smtpPort => integer()();
BoolColumn get smtpSsl => boolean()();
// Added in schema v2:
TextColumn get accountType =>
text().withDefault(const Constant('imap'))();
TextColumn get jmapUrl => text().nullable()();
// Added in schema v3:
TextColumn get username => text().withDefault(const Constant(''))();
@override
Set<Column> get primaryKey => {id};
}
@DataClassName('MailboxRow')
class Mailboxes extends Table {
TextColumn get id => text()();
TextColumn get accountId =>
text().references(Accounts, #id, onDelete: KeyAction.cascade)();
TextColumn get path => text()();
TextColumn get name => text()();
IntColumn get unreadCount => integer().withDefault(const Constant(0))();
IntColumn get totalCount => integer().withDefault(const Constant(0))();
@override
Set<Column> get primaryKey => {id};
}
class Emails extends Table {
TextColumn get id => text()();
TextColumn get accountId =>
text().references(Accounts, #id, onDelete: KeyAction.cascade)();
TextColumn get mailboxPath => text()();
IntColumn get uid => integer()();
TextColumn get subject => text().nullable()();
DateTimeColumn get sentAt => dateTime().nullable()();
DateTimeColumn get receivedAt => dateTime()();
// JSON-encoded List<{name,email}>
TextColumn get fromJson => text().withDefault(const Constant('[]'))();
TextColumn get toAddresses => text().withDefault(const Constant('[]'))();
TextColumn get ccJson => text().withDefault(const Constant('[]'))();
TextColumn get preview => text().nullable()();
BoolColumn get isSeen => boolean().withDefault(const Constant(false))();
BoolColumn get isFlagged => boolean().withDefault(const Constant(false))();
BoolColumn get hasAttachment =>
boolean().withDefault(const Constant(false))();
@override
Set<Column> get primaryKey => {id};
}
class EmailBodies extends Table {
TextColumn get emailId =>
text().references(Emails, #id, onDelete: KeyAction.cascade)();
TextColumn get textBody => text().nullable()();
TextColumn get htmlBody => text().nullable()();
// JSON-encoded List<{filename,contentType,size}>
TextColumn get attachmentsJson =>
text().withDefault(const Constant('[]'))();
@override
Set<Column> get primaryKey => {emailId};
}
// ── Database ──────────────────────────────────────────────────────────────────
@DriftDatabase(tables: [Accounts, Mailboxes, Emails, EmailBodies])
class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 3;
@override
MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (m, from, to) async {
if (from < 2) {
await m.addColumn(accounts, accounts.accountType);
await m.addColumn(accounts, accounts.jmapUrl);
}
if (from < 3) {
await m.addColumn(accounts, accounts.username);
}
},
);
}
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dir = await getApplicationSupportDirectory();
final file = File(p.join(dir.path, 'sharedinbox.db'));
return NativeDatabase.createInBackground(file);
});
}