Skip to content

Commit

Permalink
Migrate anonymous instances to database (#1500)
Browse files Browse the repository at this point in the history
* Migrate anonymous instances to database

* Fix tests
  • Loading branch information
micahmo authored Jul 17, 2024
1 parent e02bf30 commit 3233696
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 136 deletions.
123 changes: 108 additions & 15 deletions lib/account/models/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,121 @@ class Account {
final String? username;
final String? displayName;
final String? jwt;
final String? instance;
final String instance;
final bool anonymous;
final int? userId;
final int index;

const Account({
required this.id,
this.username,
this.displayName,
this.jwt,
this.instance,
this.anonymous = false,
required this.instance,
this.userId,
required this.index,
});

Account copyWith({String? id}) => Account(
Account copyWith({String? id, int? index}) => Account(
id: id ?? this.id,
username: username,
jwt: jwt,
instance: instance,
userId: userId,
index: index ?? this.index,
);

String get actorId => 'https://$instance/u/$username';

static Future<Account?> insertAccount(Account account) async {
// If we are given a brand new account to insert with an existing id, something is wrong.
assert(account.id.isEmpty);
assert(account.id.isEmpty && account.index == -1 && !account.anonymous);

try {
int id = await database
.into(database.accounts)
.insert(AccountsCompanion.insert(username: Value(account.username), jwt: Value(account.jwt), instance: Value(account.instance), userId: Value(account.userId)));
return account.copyWith(id: id.toString());
// Find the highest index in the current accounts
final int maxIndex = await (database.selectOnly(database.accounts)..addColumns([database.accounts.listIndex.max()])).getSingle().then((row) => row.read(database.accounts.listIndex.max()) ?? 0);

// Assign the next index
final int newIndex = maxIndex + 1;

int id = await database.into(database.accounts).insert(
AccountsCompanion.insert(
username: Value(account.username),
jwt: Value(account.jwt),
instance: Value(account.instance),
userId: Value(account.userId),
listIndex: newIndex,
),
);

return account.copyWith(id: id.toString(), index: newIndex);
} catch (e) {
debugPrint(e.toString());
return null;
}
}

static Future<Account?> insertAnonymousInstance(Account anonymousInstance) async {
// If we are given a brand new account to insert with an existing id, something is wrong.
assert(anonymousInstance.id.isEmpty && anonymousInstance.index == -1 && anonymousInstance.anonymous);

try {
// Find the highest index in the current accounts
final int maxIndex = await (database.selectOnly(database.accounts)..addColumns([database.accounts.listIndex.max()])).getSingle().then((row) => row.read(database.accounts.listIndex.max()) ?? 0);

// Assign the next index
final int newIndex = maxIndex + 1;

int id = await database.into(database.accounts).insert(
AccountsCompanion.insert(
username: Value(anonymousInstance.username),
jwt: Value(anonymousInstance.jwt),
instance: Value(anonymousInstance.instance),
userId: Value(anonymousInstance.userId),
anonymous: Value(anonymousInstance.anonymous),
listIndex: newIndex,
),
);

return anonymousInstance.copyWith(id: id.toString(), index: newIndex);
} catch (e) {
debugPrint(e.toString());
return null;
}
}

// A method that retrieves all accounts from the database
// A method that retrieves all accounts from the database. Does not include anonymous instances
static Future<List<Account>> accounts() async {
try {
return (await database.accounts.all().get())
.map((account) => Account(id: account.id.toString(), username: account.username, jwt: account.jwt, instance: account.instance, userId: account.userId))
return (await (database.select(database.accounts)..where((t) => t.anonymous.equals(false))).get())
.map((account) => Account(
id: account.id.toString(),
username: account.username,
jwt: account.jwt,
instance: account.instance ?? '',
userId: account.userId,
index: account.listIndex,
))
.toList();
} catch (e) {
debugPrint(e.toString());
return [];
}
}

// A method that retrieves all anonymous instances from the database. Does not include logged in accounts.
static Future<List<Account>> anonymousInstances() async {
try {
return (await (database.select(database.accounts)..where((t) => t.anonymous.equals(true))).get())
.map((account) => Account(
id: account.id.toString(),
username: account.username,
jwt: account.jwt,
instance: account.instance ?? '',
userId: account.userId,
index: account.listIndex,
))
.toList();
} catch (e) {
debugPrint(e.toString());
Expand All @@ -65,7 +138,14 @@ class Account {
try {
return await (database.select(database.accounts)..where((t) => t.id.equals(int.parse(accountId)))).getSingleOrNull().then((account) {
if (account == null) return null;
return Account(id: account.id.toString(), username: account.username, jwt: account.jwt, instance: account.instance, userId: account.userId);
return Account(
id: account.id.toString(),
username: account.username,
jwt: account.jwt,
instance: account.instance ?? '',
userId: account.userId,
index: account.listIndex,
);
});
} catch (e) {
debugPrint(e.toString());
Expand All @@ -75,9 +155,14 @@ class Account {

static Future<void> updateAccount(Account account) async {
try {
await database
.update(database.accounts)
.replace(AccountsCompanion(id: Value(int.parse(account.id)), username: Value(account.username), jwt: Value(account.jwt), instance: Value(account.instance), userId: Value(account.userId)));
await database.update(database.accounts).replace(AccountsCompanion(
id: Value(int.parse(account.id)),
username: Value(account.username),
jwt: Value(account.jwt),
instance: Value(account.instance),
userId: Value(account.userId),
listIndex: Value(account.index),
));
} catch (e) {
debugPrint(e.toString());
}
Expand All @@ -90,4 +175,12 @@ class Account {
debugPrint(e.toString());
}
}

static Future<void> deleteAnonymousInstance(String instance) async {
try {
await (database.delete(database.accounts)..where((t) => t.instance.equals(instance) & t.anonymous.equals(true))).go();
} catch (e) {
debugPrint(e.toString());
}
}
}
19 changes: 11 additions & 8 deletions lib/account/pages/login_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:go_router/go_router.dart';
import 'package:lemmy_api_client/v3.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:thunder/account/models/account.dart';

import 'package:thunder/core/auth/bloc/auth_bloc.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/singletons/lemmy_client.dart';
import 'package:thunder/core/singletons/preferences.dart';
import 'package:thunder/instances.dart';
import 'package:thunder/shared/dialogs.dart';
import 'package:thunder/shared/snackbar.dart';
Expand All @@ -38,6 +36,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
late TextEditingController _passwordTextEditingController;
late TextEditingController _totpTextEditingController;
late TextEditingController _instanceTextEditingController;
final FocusNode _usernameFieldFocusNode = FocusNode();

bool showPassword = false;
bool fieldsFilledIn = false;
Expand Down Expand Up @@ -327,7 +326,11 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
errorMaxLines: 2,
),
enableSuggestions: false,
onSubmitted: (controller.text.isNotEmpty && widget.anonymous) ? (_) => _addAnonymousInstance(context) : null,
onSubmitted: !widget.anonymous
? (_) => _usernameFieldFocusNode.requestFocus()
: controller.text.isNotEmpty
? (_) => _addAnonymousInstance(context)
: null,
),
suggestionsCallback: (String pattern) {
if (pattern.isNotEmpty != true) {
Expand Down Expand Up @@ -358,6 +361,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
keyboardType: TextInputType.url,
autocorrect: false,
controller: _usernameTextEditingController,
focusNode: _usernameFieldFocusNode,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
Expand Down Expand Up @@ -470,9 +474,8 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
final AppLocalizations l10n = AppLocalizations.of(context)!;

if (await isLemmyInstance(_instanceTextEditingController.text)) {
final SharedPreferences prefs = (await UserPreferences.instance).sharedPreferences;
List<String> anonymousInstances = prefs.getStringList(LocalSettings.anonymousInstances.name) ?? ['lemmy.ml'];
if (anonymousInstances.contains(_instanceTextEditingController.text)) {
final List<Account> anonymousInstances = await Account.anonymousInstances();
if (anonymousInstances.any((anonymousInstance) => anonymousInstance.instance == _instanceTextEditingController.text)) {
setState(() {
instanceValidated = false;
instanceError = AppLocalizations.of(context)!.instanceHasAlreadyBenAdded(currentInstance ?? '');
Expand Down Expand Up @@ -502,7 +505,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix

if (acceptedContentWarning) {
context.read<AuthBloc>().add(const LogOutOfAllAccounts());
context.read<ThunderBloc>().add(OnAddAnonymousInstance(_instanceTextEditingController.text));
await Account.insertAnonymousInstance(Account(id: '', instance: _instanceTextEditingController.text, index: -1, anonymous: true));
context.read<ThunderBloc>().add(OnSetCurrentAnonymousInstance(_instanceTextEditingController.text));
widget.popRegister();
}
Expand Down
2 changes: 1 addition & 1 deletion lib/account/widgets/account_placeholder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AccountPlaceholder extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
String anonymousInstance = context.watch<ThunderBloc>().state.currentAnonymousInstance;
String anonymousInstance = context.watch<ThunderBloc>().state.currentAnonymousInstance ?? '';

return Center(
child: Padding(
Expand Down
Loading

0 comments on commit 3233696

Please sign in to comment.