Initial and done prolly

This commit is contained in:
Andrew 2025-01-05 16:01:21 +07:00
commit 6f88b9966f
175 changed files with 15445 additions and 0 deletions

View file

@ -0,0 +1,52 @@
part of '../database.dart';
extension ProductCategoryCrud on AppDatabase {
Stream<List<ProductCategoryData>> get productsCategoriesSubscription =>
managers.productCategory.watch();
Future<void> addProductCategory({
required String name,
required String icon,
}) async {
await managers.productCategory.create((c) => c(
name: name,
icon: icon,
));
}
Future<void> updateProductCategory({
required int id,
String? name,
String? icon,
}) async {
await managers.productCategory.filter((f) => f.id(id)).update((u) => u(
id: Value(id),
name: Value.absentIfNull(name),
icon: Value.absentIfNull(icon),
));
}
Future<void> deleteProductCategory(ProductCategoryData item) async {
await managers.productCategory.filter((f) => f.id(item.id)).delete();
}
Future<List<ProductCategoryData>> getProductCategories() async {
final categories = await managers.productCategory.get();
return categories;
}
Future<List<(ProductCategoryData, int)>>
getProductCategoriesWithCounts() async {
final categories = await managers.productCategory.get();
return [
for (final category in categories)
(
category,
await managers.product
.filter((f) => f.category.id(category.id))
.count()
),
];
}
}

View file

@ -0,0 +1,137 @@
part of '../database.dart';
extension ProductCrud on AppDatabase {
Stream<List<(ProductData, ProductCategoryData, StorageLocationData)>>
get productsSubscription => managers.product
.orderBy((o) => o.expiryDate.asc())
.withReferences((prefetch) => prefetch(
category: true,
storage: true,
))
.watch()
.asyncMap(
(products) async => [
for (final (item, refs) in products)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
],
);
Stream<List<(ProductData, ProductCategoryData, StorageLocationData)>>
get soonExpirySubscription => managers.product
.filter((f) =>
f.expiryDate.isAfter(DateTime.now().add(const Duration(days: 3))))
.orderBy((o) => o.expiryDate.asc())
.withReferences((prefetch) => prefetch(
category: true,
storage: true,
))
.watch()
.asyncMap(
(products) async => [
for (final (item, refs) in products)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
],
);
Future<void> addProduct({
required String name,
required ProductCategoryData category,
required StorageLocationData storage,
required double quantity,
required String unit,
DateTime? purchaseDate,
required DateTime expiryDate,
required String barcode,
}) async {
await managers.product.create((c) => c(
name: name,
category: category.id,
storage: storage.id,
quantity: quantity,
unit: unit,
barcode: barcode,
purchaseDate: Value(purchaseDate ?? DateTime.now()),
expiryDate: Value(expiryDate),
));
}
Future<void> updateProduct({
required int id,
String? name,
ProductCategoryData? category,
StorageLocationData? storage,
double? quantity,
String? unit,
DateTime? purchaseDate,
DateTime? expiryDate,
String? barcode,
}) async {
await managers.product.filter((f) => f.id(id)).update((u) => u(
name: Value.absentIfNull(name),
category: Value.absentIfNull(category?.id),
storage: Value.absentIfNull(storage?.id),
quantity: Value.absentIfNull(quantity),
unit: Value.absentIfNull(unit),
barcode: Value.absentIfNull(barcode),
purchaseDate: Value.absentIfNull(purchaseDate),
expiryDate: Value.absentIfNull(expiryDate),
));
}
Future<void> deleteProduct(ProductData item) async {
await managers.product.filter((f) => f.id(item.id)).delete();
}
Future<List<(ProductData, ProductCategoryData, StorageLocationData)>>
getProducts() async {
final products = await managers.product
.orderBy((o) => o.purchaseDate.asc())
.withReferences((prefetch) => prefetch(
category: true,
storage: true,
))
.get();
return [
for (final (item, refs) in products)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
];
}
Future<List<(ProductData, ProductCategoryData, StorageLocationData)>>
getSoonExpiryProducts() async {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final products = await managers.product
.filter(
(f) => f.expiryDate.isBefore(today.add(const Duration(days: 3))),
)
.orderBy((o) => o.expiryDate.desc())
.withReferences((prefetch) => prefetch(
category: true,
storage: true,
))
.get();
return [
for (final (item, refs) in products)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
];
}
}

View file

@ -0,0 +1,84 @@
part of '../database.dart';
extension ShoppingListItemCrud on AppDatabase {
Stream<List<(ShoppingListItemData, ProductCategoryData, StorageLocationData)>>
get shoppingListSubscription => managers.shoppingListItem
.withReferences(
(prefetch) => prefetch(
category: true,
storage: true,
),
)
.watch()
.asyncMap(
(shoppingList) async => [
for (final (item, refs) in shoppingList)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
],
);
Future<void> addShoppingListItem({
required double quantity,
required String name,
required ProductCategoryData category,
required StorageLocationData storage,
required String unit,
}) async {
await managers.shoppingListItem.create((c) => c(
quantity: quantity,
name: name,
category: category.id,
storage: storage.id,
unit: unit,
));
}
Future<void> updateShoppingListItem({
required int id,
double? quantity,
String? name,
ProductCategoryData? category,
StorageLocationData? storage,
String? unit,
bool? isPurchased,
}) async {
await managers.shoppingListItem.filter((f) => f.id(id)).update((u) => u(
id: Value(id),
quantity: Value.absentIfNull(quantity),
isPurchased: Value.absentIfNull(isPurchased),
name: Value.absentIfNull(name),
category: Value.absentIfNull(category?.id),
storage: Value.absentIfNull(storage?.id),
unit: Value.absentIfNull(unit),
));
}
Future<void> deleteShoppingListItem(ShoppingListItemData item) async {
await managers.shoppingListItem.filter((f) => f.id(item.id)).delete();
}
Future<List<(ShoppingListItemData, ProductCategoryData, StorageLocationData)>>
getShoppingList() async {
final shoppingList = await managers.shoppingListItem
.withReferences(
(prefetch) => prefetch(
category: true,
storage: true,
),
)
.get();
return [
for (final (item, refs) in shoppingList)
(
item,
await refs.category.getSingle(),
await refs.storage.getSingle(),
),
];
}
}

View file

@ -0,0 +1,88 @@
part of '../database.dart';
extension StorageLocationsCrud on AppDatabase {
Stream<List<StorageLocationData>> get storageLocationsSubscription =>
managers.storageLocation.watch();
Future<int> addStorageLocation({
required String name,
required String description,
required TemperatureMode temperatureMode,
required String icon,
}) async {
return await managers.storageLocation.create((c) => c(
name: name,
description: description,
temperatureMode: temperatureMode.name,
icon: icon,
));
}
Future<int> updateStorageLocation({
required int id,
String? name,
String? description,
TemperatureMode? temperatureMode,
double? capacity,
String? icon,
}) async {
return await managers.storageLocation
.filter((f) => f.id(id))
.update((u) => u(
name: Value.absentIfNull(name),
description: Value.absentIfNull(description),
temperatureMode: Value.absentIfNull(temperatureMode?.name),
icon: Value.absentIfNull(icon),
));
}
Future<void> deleteStorageLocation(StorageLocationData item) async {
await managers.storageLocation.filter((f) => f.id(item.id)).delete();
}
Future<List<(StorageLocationData, List<(ProductData, ProductCategoryData)>)>>
getStorageLocations() async {
final storageLocations = await managers.storageLocation
.withReferences((prefetch) => prefetch(productRefs: true))
.get();
final result =
<(StorageLocationData, List<(ProductData, ProductCategoryData)>)>[];
for (final (storage, refs) in storageLocations) {
final products = await refs.productRefs
.withReferences((prefetch) => prefetch(category: true))
.get();
final productsWithCategories = <(ProductData, ProductCategoryData)>[];
for (final (product, refs) in products) {
productsWithCategories.add((
product,
await refs.category.getSingle(),
));
}
result.add((
storage,
productsWithCategories,
));
}
return result;
}
Future<bool> defaultExists() async {
final exists = await managers.storageLocation
.filter((f) => f.isDefault(true))
.exists();
return exists;
}
Future<void> switchDefault(int newDefaultId) async {
await managers.storageLocation
.filter((f) => f.isDefault(true))
.update((u) => u(isDefault: const Value(false)));
await managers.storageLocation
.filter((f) => f.id(newDefaultId))
.update((u) => u(isDefault: const Value(true)));
}
}

View file

@ -0,0 +1,84 @@
part of '../database.dart';
extension UserCrud on AppDatabase {
String hashPassword(String password) {
final passwordBytes = utf8.encode(password);
final hashedPassword = sha512224.convert(passwordBytes);
return hashedPassword.toString();
}
Future<UserData?> addUser({
required String login,
required String password,
}) async {
return await managers.user.createReturningOrNull((c) => c(
login: login,
password: hashPassword(password),
));
}
Future<List<UserData>> getUsers() async {
return await managers.user.get();
}
Future<bool> anyUserExists() async {
return await managers.user.exists();
}
Future<UserData?> findUser({
required String login,
required String password,
}) async {
return await managers.user
.filter((f) => f.login(login))
.filter((f) => f.password(hashPassword(password)))
.getSingleOrNull();
}
Future<UserData> getUser(UserData user) async {
return await managers.user.filter((f) => f.id(user.id)).getSingle();
}
Future<bool> updateUser(
UserData currentUser,
UserData updatedUser, {
String? login,
String? password,
}) async {
if (currentUser.id == updatedUser.id) {
return false;
}
await managers.user.filter((f) => f.id(updatedUser.id)).update((u) => u(
login: Value.absentIfNull(login),
password: Value.absentIfNull(
password != null ? hashPassword(password) : null),
));
return true;
}
Future<bool> updateUserPassword(
UserData user, {
required String password,
}) async {
await managers.user.filter((f) => f.id(user.id)).update((u) => u(
password: Value(hashPassword(password)),
));
return true;
}
Future<bool> deleteUser(UserData user) async {
final usersCount = await managers.user.count();
if (usersCount == 0) {
return false;
}
final deletedCount = await managers.user
.filter(
(f) => f.id(user.id),
)
.delete();
return deletedCount != 0;
}
}

50
lib/db/database.dart Normal file
View file

@ -0,0 +1,50 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import '../models/enums/temperature_mode.dart';
import 'database.steps.dart';
import 'tables/product.dart';
import 'tables/product_category.dart';
import 'tables/shopping_list_item.dart';
import 'tables/storage_location.dart';
import 'tables/users_table.dart';
part 'database.g.dart';
part 'crud/product_crud.dart';
part 'crud/product_category_crud.dart';
part 'crud/shopping_list_crud.dart';
part 'crud/storage_locations_crud.dart';
part 'crud/user_crud.dart';
@DriftDatabase(
tables: [ProductCategory, Product, ShoppingListItem, StorageLocation, User])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 2;
static QueryExecutor _openConnection() {
return driftDatabase(name: "groceries_manager_db");
}
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: stepByStep(
from1To2: (m, schema) async {
m.createTable(schema.user);
},
),
);
}
}
extension BetterConversionStorageLocation on StorageLocationData {
TemperatureMode get temperatureModeE =>
TemperatureMode.fromName(temperatureMode);
}

3265
lib/db/database.g.dart Normal file

File diff suppressed because it is too large Load diff

273
lib/db/database.steps.dart Normal file
View file

@ -0,0 +1,273 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
productCategory,
storageLocation,
product,
shoppingListItem,
user,
];
late final Shape0 productCategory = Shape0(
source: i0.VersionedTable(
entityName: 'product_category',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 storageLocation = Shape1(
source: i0.VersionedTable(
entityName: 'storage_location',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_3,
_column_4,
_column_2,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 product = Shape2(
source: i0.VersionedTable(
entityName: 'product',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_6,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 shoppingListItem = Shape3(
source: i0.VersionedTable(
entityName: 'shopping_list_item',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_6,
_column_7,
_column_8,
_column_9,
_column_13,
_column_14,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 user = Shape4(
source: i0.VersionedTable(
entityName: 'user',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_15,
_column_16,
],
attachedDatabase: database,
),
alias: null);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get icon =>
columnsByName['icon']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('name', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_2(String aliasedName) =>
i1.GeneratedColumn<String>('icon', aliasedName, false,
type: i1.DriftSqlType.string);
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get description =>
columnsByName['description']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get temperatureMode =>
columnsByName['temperature_mode']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get icon =>
columnsByName['icon']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isDefault =>
columnsByName['is_default']! as i1.GeneratedColumn<bool>;
}
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>('description', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_4(String aliasedName) =>
i1.GeneratedColumn<String>('temperature_mode', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<bool> _column_5(String aliasedName) =>
i1.GeneratedColumn<bool>('is_default', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("is_default" IN (0, 1))'),
defaultValue: const Constant(false));
class Shape2 extends i0.VersionedTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get category =>
columnsByName['category']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get storage =>
columnsByName['storage']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<double> get quantity =>
columnsByName['quantity']! as i1.GeneratedColumn<double>;
i1.GeneratedColumn<String> get unit =>
columnsByName['unit']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get purchaseDate =>
columnsByName['purchase_date']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get expiryDate =>
columnsByName['expiry_date']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<String> get barcode =>
columnsByName['barcode']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<int> _column_6(String aliasedName) =>
i1.GeneratedColumn<int>('category', aliasedName, false,
type: i1.DriftSqlType.int,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'REFERENCES product_category (id)'));
i1.GeneratedColumn<int> _column_7(String aliasedName) =>
i1.GeneratedColumn<int>('storage', aliasedName, false,
type: i1.DriftSqlType.int,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'REFERENCES storage_location (id)'));
i1.GeneratedColumn<double> _column_8(String aliasedName) =>
i1.GeneratedColumn<double>('quantity', aliasedName, false,
type: i1.DriftSqlType.double);
i1.GeneratedColumn<String> _column_9(String aliasedName) =>
i1.GeneratedColumn<String>('unit', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<DateTime> _column_10(String aliasedName) =>
i1.GeneratedColumn<DateTime>('purchase_date', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime> _column_11(String aliasedName) =>
i1.GeneratedColumn<DateTime>('expiry_date', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<String> _column_12(String aliasedName) =>
i1.GeneratedColumn<String>('barcode', aliasedName, false,
additionalChecks: i1.GeneratedColumn.checkTextLength(maxTextLength: 20),
type: i1.DriftSqlType.string);
class Shape3 extends i0.VersionedTable {
Shape3({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get category =>
columnsByName['category']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get storage =>
columnsByName['storage']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<double> get quantity =>
columnsByName['quantity']! as i1.GeneratedColumn<double>;
i1.GeneratedColumn<String> get unit =>
columnsByName['unit']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isPurchased =>
columnsByName['is_purchased']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<DateTime> get dateAdded =>
columnsByName['date_added']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<bool> _column_13(String aliasedName) =>
i1.GeneratedColumn<bool>('is_purchased', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("is_purchased" IN (0, 1))'),
defaultValue: const Constant(false));
i1.GeneratedColumn<DateTime> _column_14(String aliasedName) =>
i1.GeneratedColumn<DateTime>('date_added', aliasedName, true,
type: i1.DriftSqlType.dateTime, defaultValue: currentDateAndTime);
class Shape4 extends i0.VersionedTable {
Shape4({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get login =>
columnsByName['login']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get password =>
columnsByName['password']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_15(String aliasedName) =>
i1.GeneratedColumn<String>('login', aliasedName, false,
type: i1.DriftSqlType.string,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways('UNIQUE'));
i1.GeneratedColumn<String> _column_16(String aliasedName) =>
i1.GeneratedColumn<String>('password', aliasedName, false,
type: i1.DriftSqlType.string);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = Schema2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
};
}
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
));

View file

@ -0,0 +1,15 @@
import 'package:drift/drift.dart';
import 'package:groceries_manager/db/tables/product_category.dart';
import 'package:groceries_manager/db/tables/storage_location.dart';
class Product extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
IntColumn get category => integer().references(ProductCategory, #id)();
IntColumn get storage => integer().references(StorageLocation, #id)();
RealColumn get quantity => real()();
TextColumn get unit => text()();
DateTimeColumn get purchaseDate => dateTime().nullable()();
DateTimeColumn get expiryDate => dateTime().nullable()();
TextColumn get barcode => text().withLength(max: 20)();
}

View file

@ -0,0 +1,7 @@
import 'package:drift/drift.dart';
class ProductCategory extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get icon => text()();
}

View file

@ -0,0 +1,16 @@
import 'package:drift/drift.dart';
import 'product_category.dart';
import 'storage_location.dart';
class ShoppingListItem extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
IntColumn get category => integer().references(ProductCategory, #id)();
IntColumn get storage => integer().references(StorageLocation, #id)();
RealColumn get quantity => real()();
TextColumn get unit => text()();
BoolColumn get isPurchased => boolean().withDefault(const Constant(false))();
DateTimeColumn get dateAdded =>
dateTime().withDefault(currentDateAndTime).nullable()();
}

View file

@ -0,0 +1,10 @@
import 'package:drift/drift.dart';
class StorageLocation extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get description => text()();
TextColumn get temperatureMode => text()();
TextColumn get icon => text()();
BoolColumn get isDefault => boolean().withDefault(const Constant(false))();
}

View file

@ -0,0 +1,7 @@
import 'package:drift/drift.dart';
class User extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get login => text().unique()();
TextColumn get password => text()();
}