This commit is contained in:
Dmitry 2026-01-24 19:07:29 +03:00
parent f665c75eeb
commit 63524c2575
10 changed files with 109 additions and 28 deletions

View file

@ -22,6 +22,9 @@ RUN dart pub get
WORKDIR /app/mnemo_cards_backend
RUN dart pub get
# Generate code (drift, json_serializable, etc.)
RUN dart run build_runner build --delete-conflicting-outputs
# Compile to native executable
RUN dart compile exe lib/main.dart -o /app/server

View file

@ -642,7 +642,8 @@ class AdminTestsApiV2 {
'error': 'Invalid expiresAt format',
'message': 'expiresAt must be a valid ISO 8601 datetime string',
'field': 'expiresAt',
'details': 'Please provide expiresAt in ISO 8601 format (e.g., "2025-01-24T12:00:00Z").',
'details':
'Please provide expiresAt in ISO 8601 format (e.g., "2025-01-24T12:00:00Z").',
}, statusCode: 400);
}
}
@ -799,7 +800,8 @@ class AdminTestsApiV2 {
'version': updatedTest.version,
'time': updatedTest.time,
'timeSubtitle': updatedTest.timeSubtitle,
'expiresAt': updatedTest.expiresAt?.dateTime.toIso8601String(),
'expiresAt': updatedTest.expiresAt?.dateTime
.toIso8601String(),
}
: {
'id': testId,
@ -933,7 +935,8 @@ class AdminTestsApiV2 {
'version': createdTest.version,
'time': createdTest.time,
'timeSubtitle': createdTest.timeSubtitle,
'expiresAt': createdTest.expiresAt?.dateTime.toIso8601String(),
'expiresAt': createdTest.expiresAt?.dateTime
.toIso8601String(),
}
: {
'id': newTest,

View file

@ -125,12 +125,10 @@ class MediaApiV2 {
}
/// Parses multipart/form-data request and extracts file and optional objectId
Future<({
Uint8List bytes,
String contentType,
String? filename,
String? objectId,
})?> _parseMultipartFileWithObjectId(Request request) async {
Future<
({Uint8List bytes, String contentType, String? filename, String? objectId})?
>
_parseMultipartFileWithObjectId(Request request) async {
try {
final multipart = request.multipart();
if (multipart == null) {

View file

@ -13,5 +13,6 @@ Router _$MediaApiV2Router(MediaApiV2 service) {
router.add('POST', r'/media/upload/voice', service.uploadVoice);
router.add('GET', r'/media/<bucket>/<objectId>/url', service.getPresignedUrl);
router.add('DELETE', r'/media/<bucket>/<objectId>', service.deleteFile);
router.add('POST', r'/media/generate-tts', service.generateTTS);
return router;
}

View file

@ -67,12 +67,13 @@ class TestDao extends DatabaseAccessor<AppDatabase> with _$TestDaoMixin {
Future<int> softDeleteExpiredTests() async {
final now = DateTime.now();
final expiredTests = await (select(tests)..where(
(t) =>
t.expiresAt.isSmallerOrEqualValue(PgDateTime(now)) &
t.isDeleted.equals(false),
))
.get();
final expiredTests =
await (select(tests)..where(
(t) =>
t.expiresAt.isSmallerOrEqualValue(PgDateTime(now)) &
t.isDeleted.equals(false),
))
.get();
var deleted = 0;
for (final test in expiredTests) {

View file

@ -8548,6 +8548,18 @@ class $TestsTable extends Tests with TableInfo<$TestsTable, Test> {
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _expiresAtMeta = const VerificationMeta(
'expiresAt',
);
@override
late final GeneratedColumn<PgDateTime> expiresAt =
GeneratedColumn<PgDateTime>(
'expires_at',
aliasedName,
true,
type: PgTypes.timestampWithTimezone,
requiredDuringInsert: false,
);
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@ -8608,6 +8620,7 @@ class $TestsTable extends Tests with TableInfo<$TestsTable, Test> {
version,
time,
timeSubtitle,
expiresAt,
createdAt,
updatedAt,
isDeleted,
@ -8669,6 +8682,12 @@ class $TestsTable extends Tests with TableInfo<$TestsTable, Test> {
),
);
}
if (data.containsKey('expires_at')) {
context.handle(
_expiresAtMeta,
expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta),
);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
@ -8730,6 +8749,10 @@ class $TestsTable extends Tests with TableInfo<$TestsTable, Test> {
DriftSqlType.string,
data['${effectivePrefix}time_subtitle'],
),
expiresAt: attachedDatabase.typeMapping.read(
PgTypes.timestampWithTimezone,
data['${effectivePrefix}expires_at'],
),
createdAt: attachedDatabase.typeMapping.read(
PgTypes.timestampWithTimezone,
data['${effectivePrefix}created_at'],
@ -8763,6 +8786,7 @@ class Test extends DataClass implements Insertable<Test> {
final String? version;
final String? time;
final String? timeSubtitle;
final PgDateTime? expiresAt;
final PgDateTime createdAt;
final PgDateTime updatedAt;
final bool isDeleted;
@ -8775,6 +8799,7 @@ class Test extends DataClass implements Insertable<Test> {
this.version,
this.time,
this.timeSubtitle,
this.expiresAt,
required this.createdAt,
required this.updatedAt,
required this.isDeleted,
@ -8800,6 +8825,12 @@ class Test extends DataClass implements Insertable<Test> {
if (!nullToAbsent || timeSubtitle != null) {
map['time_subtitle'] = Variable<String>(timeSubtitle);
}
if (!nullToAbsent || expiresAt != null) {
map['expires_at'] = Variable<PgDateTime>(
expiresAt,
PgTypes.timestampWithTimezone,
);
}
map['created_at'] = Variable<PgDateTime>(
createdAt,
PgTypes.timestampWithTimezone,
@ -8835,6 +8866,9 @@ class Test extends DataClass implements Insertable<Test> {
timeSubtitle: timeSubtitle == null && nullToAbsent
? const Value.absent()
: Value(timeSubtitle),
expiresAt: expiresAt == null && nullToAbsent
? const Value.absent()
: Value(expiresAt),
createdAt: Value(createdAt),
updatedAt: Value(updatedAt),
isDeleted: Value(isDeleted),
@ -8857,6 +8891,7 @@ class Test extends DataClass implements Insertable<Test> {
version: serializer.fromJson<String?>(json['version']),
time: serializer.fromJson<String?>(json['time']),
timeSubtitle: serializer.fromJson<String?>(json['timeSubtitle']),
expiresAt: serializer.fromJson<PgDateTime?>(json['expiresAt']),
createdAt: serializer.fromJson<PgDateTime>(json['createdAt']),
updatedAt: serializer.fromJson<PgDateTime>(json['updatedAt']),
isDeleted: serializer.fromJson<bool>(json['isDeleted']),
@ -8874,6 +8909,7 @@ class Test extends DataClass implements Insertable<Test> {
'version': serializer.toJson<String?>(version),
'time': serializer.toJson<String?>(time),
'timeSubtitle': serializer.toJson<String?>(timeSubtitle),
'expiresAt': serializer.toJson<PgDateTime?>(expiresAt),
'createdAt': serializer.toJson<PgDateTime>(createdAt),
'updatedAt': serializer.toJson<PgDateTime>(updatedAt),
'isDeleted': serializer.toJson<bool>(isDeleted),
@ -8889,6 +8925,7 @@ class Test extends DataClass implements Insertable<Test> {
Value<String?> version = const Value.absent(),
Value<String?> time = const Value.absent(),
Value<String?> timeSubtitle = const Value.absent(),
Value<PgDateTime?> expiresAt = const Value.absent(),
PgDateTime? createdAt,
PgDateTime? updatedAt,
bool? isDeleted,
@ -8901,6 +8938,7 @@ class Test extends DataClass implements Insertable<Test> {
version: version.present ? version.value : this.version,
time: time.present ? time.value : this.time,
timeSubtitle: timeSubtitle.present ? timeSubtitle.value : this.timeSubtitle,
expiresAt: expiresAt.present ? expiresAt.value : this.expiresAt,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
isDeleted: isDeleted ?? this.isDeleted,
@ -8917,6 +8955,7 @@ class Test extends DataClass implements Insertable<Test> {
timeSubtitle: data.timeSubtitle.present
? data.timeSubtitle.value
: this.timeSubtitle,
expiresAt: data.expiresAt.present ? data.expiresAt.value : this.expiresAt,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
isDeleted: data.isDeleted.present ? data.isDeleted.value : this.isDeleted,
@ -8934,6 +8973,7 @@ class Test extends DataClass implements Insertable<Test> {
..write('version: $version, ')
..write('time: $time, ')
..write('timeSubtitle: $timeSubtitle, ')
..write('expiresAt: $expiresAt, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('isDeleted: $isDeleted, ')
@ -8951,6 +8991,7 @@ class Test extends DataClass implements Insertable<Test> {
version,
time,
timeSubtitle,
expiresAt,
createdAt,
updatedAt,
isDeleted,
@ -8967,6 +9008,7 @@ class Test extends DataClass implements Insertable<Test> {
other.version == this.version &&
other.time == this.time &&
other.timeSubtitle == this.timeSubtitle &&
other.expiresAt == this.expiresAt &&
other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt &&
other.isDeleted == this.isDeleted &&
@ -8981,6 +9023,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
final Value<String?> version;
final Value<String?> time;
final Value<String?> timeSubtitle;
final Value<PgDateTime?> expiresAt;
final Value<PgDateTime> createdAt;
final Value<PgDateTime> updatedAt;
final Value<bool> isDeleted;
@ -8994,6 +9037,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
this.version = const Value.absent(),
this.time = const Value.absent(),
this.timeSubtitle = const Value.absent(),
this.expiresAt = const Value.absent(),
this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(),
this.isDeleted = const Value.absent(),
@ -9008,6 +9052,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
this.version = const Value.absent(),
this.time = const Value.absent(),
this.timeSubtitle = const Value.absent(),
this.expiresAt = const Value.absent(),
this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(),
this.isDeleted = const Value.absent(),
@ -9022,6 +9067,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
Expression<String>? version,
Expression<String>? time,
Expression<String>? timeSubtitle,
Expression<PgDateTime>? expiresAt,
Expression<PgDateTime>? createdAt,
Expression<PgDateTime>? updatedAt,
Expression<bool>? isDeleted,
@ -9036,6 +9082,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
if (version != null) 'version': version,
if (time != null) 'time': time,
if (timeSubtitle != null) 'time_subtitle': timeSubtitle,
if (expiresAt != null) 'expires_at': expiresAt,
if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt,
if (isDeleted != null) 'is_deleted': isDeleted,
@ -9052,6 +9099,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
Value<String?>? version,
Value<String?>? time,
Value<String?>? timeSubtitle,
Value<PgDateTime?>? expiresAt,
Value<PgDateTime>? createdAt,
Value<PgDateTime>? updatedAt,
Value<bool>? isDeleted,
@ -9066,6 +9114,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
version: version ?? this.version,
time: time ?? this.time,
timeSubtitle: timeSubtitle ?? this.timeSubtitle,
expiresAt: expiresAt ?? this.expiresAt,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
isDeleted: isDeleted ?? this.isDeleted,
@ -9098,6 +9147,12 @@ class TestsCompanion extends UpdateCompanion<Test> {
if (timeSubtitle.present) {
map['time_subtitle'] = Variable<String>(timeSubtitle.value);
}
if (expiresAt.present) {
map['expires_at'] = Variable<PgDateTime>(
expiresAt.value,
PgTypes.timestampWithTimezone,
);
}
if (createdAt.present) {
map['created_at'] = Variable<PgDateTime>(
createdAt.value,
@ -9135,6 +9190,7 @@ class TestsCompanion extends UpdateCompanion<Test> {
..write('version: $version, ')
..write('time: $time, ')
..write('timeSubtitle: $timeSubtitle, ')
..write('expiresAt: $expiresAt, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('isDeleted: $isDeleted, ')
@ -27689,6 +27745,7 @@ typedef $$TestsTableCreateCompanionBuilder =
Value<String?> version,
Value<String?> time,
Value<String?> timeSubtitle,
Value<PgDateTime?> expiresAt,
Value<PgDateTime> createdAt,
Value<PgDateTime> updatedAt,
Value<bool> isDeleted,
@ -27704,6 +27761,7 @@ typedef $$TestsTableUpdateCompanionBuilder =
Value<String?> version,
Value<String?> time,
Value<String?> timeSubtitle,
Value<PgDateTime?> expiresAt,
Value<PgDateTime> createdAt,
Value<PgDateTime> updatedAt,
Value<bool> isDeleted,
@ -27819,6 +27877,11 @@ class $$TestsTableFilterComposer extends Composer<_$AppDatabase, $TestsTable> {
builder: (column) => ColumnFilters(column),
);
ColumnFilters<PgDateTime> get expiresAt => $composableBuilder(
column: $table.expiresAt,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<PgDateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnFilters(column),
@ -27959,6 +28022,11 @@ class $$TestsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<PgDateTime> get expiresAt => $composableBuilder(
column: $table.expiresAt,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<PgDateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnOrderings(column),
@ -28012,6 +28080,9 @@ class $$TestsTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<PgDateTime> get expiresAt =>
$composableBuilder(column: $table.expiresAt, builder: (column) => column);
GeneratedColumn<PgDateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
@ -28140,6 +28211,7 @@ class $$TestsTableTableManager
Value<String?> version = const Value.absent(),
Value<String?> time = const Value.absent(),
Value<String?> timeSubtitle = const Value.absent(),
Value<PgDateTime?> expiresAt = const Value.absent(),
Value<PgDateTime> createdAt = const Value.absent(),
Value<PgDateTime> updatedAt = const Value.absent(),
Value<bool> isDeleted = const Value.absent(),
@ -28153,6 +28225,7 @@ class $$TestsTableTableManager
version: version,
time: time,
timeSubtitle: timeSubtitle,
expiresAt: expiresAt,
createdAt: createdAt,
updatedAt: updatedAt,
isDeleted: isDeleted,
@ -28168,6 +28241,7 @@ class $$TestsTableTableManager
Value<String?> version = const Value.absent(),
Value<String?> time = const Value.absent(),
Value<String?> timeSubtitle = const Value.absent(),
Value<PgDateTime?> expiresAt = const Value.absent(),
Value<PgDateTime> createdAt = const Value.absent(),
Value<PgDateTime> updatedAt = const Value.absent(),
Value<bool> isDeleted = const Value.absent(),
@ -28181,6 +28255,7 @@ class $$TestsTableTableManager
version: version,
time: time,
timeSubtitle: timeSubtitle,
expiresAt: expiresAt,
createdAt: createdAt,
updatedAt: updatedAt,
isDeleted: isDeleted,

View file

@ -76,8 +76,8 @@ class MinioService {
final cacheControl = contentType.startsWith('image/')
? 'public, max-age=604800, immutable' // 7 дней для изображений
: contentType.startsWith('audio/')
? 'public, max-age=604800' // 7 дней для аудио
: 'public, max-age=86400'; // 1 день для других
? 'public, max-age=604800' // 7 дней для аудио
: 'public, max-age=86400'; // 1 день для других
await _client.putObject(
bucket,