twonly-app/lib/src/services/user_study.service.dart
2026-06-20 13:09:53 +02:00

141 lines
5.2 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/keyvalue.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
const userStudySurveyKey = 'user_study_survey';
// PLEASE DO NOT SPAM OR TRY SENDING DIRECTLY TO THIS URL!
// You're just making my master's thesis more difficult and destroy scientific data. :/
const surveyUrlBase = 'https://survey.twonly.org/upload.php';
Future<void> handleUserStudyUpload() async {
try {
final token = userService.currentUser.userStudyParticipantsToken;
if (token == null) return;
// in case the survey was taken offline try again
final userStudySurvey = await KeyValueStore.get(userStudySurveyKey);
if (userStudySurvey != null) {
final response = await http.post(
Uri.parse('$surveyUrlBase/create/$token'),
body: jsonEncode(userStudySurvey),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode != 200) {
Log.warn(
'Got different status code for survey upload: ${response.statusCode}',
);
return;
}
await KeyValueStore.delete(userStudySurveyKey);
}
if (userService.currentUser.lastUserStudyDataUpload != null &&
isToday(userService.currentUser.lastUserStudyDataUpload!)) {
// Only send updates once a day.
// This enables to see if improvements to actually work.
return;
}
final contacts = await twonlyDB.contactsDao.getAllContacts();
final verifications = await twonlyDB.keyVerificationDao
.getFirstVerificationTypeByContacts();
final udVerifiedByContactsCount = await twonlyDB.keyVerificationDao
.getTransferredTrustVerificationsCount();
final totalContactsWithVerificationBadge = await twonlyDB.keyVerificationDao
.getCountOfContactsWithVerificationBadge();
final udFriendsShared = await twonlyDB.contactsDao
.getContactsAnnouncedViaUserDiscovery();
final udAllAnnouncedUsers = await twonlyDB.userDiscoveryDao
.getAllAnnouncedUsersWithRelations();
var udUnknownAnnouncedUsers = 0;
for (final udUser in udAllAnnouncedUsers.keys) {
if (!contacts.any((c) => c.userId == udUser.announcedUserId)) {
udUnknownAnnouncedUsers += 1;
}
}
final dataCollection = {
'total_contacts': contacts.length,
'user_discovery_enabled': userService.currentUser.isUserDiscoveryEnabled,
'user_discovery_required_send_images':
userService.currentUser.requiredSendImages,
'user_discovery_threshold':
userService.currentUser.userDiscoveryThreshold,
'user_discovery_requires_manual_approval':
userService.currentUser.userDiscoveryRequiresManualApproval,
'user_discovery_share_promotion':
userService.currentUser.userDiscoverySharePromotion,
'user_discovery_count_friends_shared': udFriendsShared.length,
'user_discovery_count_announced_users': udAllAnnouncedUsers.length,
'user_discovery_count_unknown_announced_users': udUnknownAnnouncedUsers,
'user_study_count_new_friends_via_suggestion':
userService.currentUser.userStudyCountNewFriendsViaSuggestion,
'user_discovery_count_verified_by_contacts': udVerifiedByContactsCount,
'accepted_contacts': contacts.where((c) => c.accepted).length,
'verified_contacts': verifications.length,
'total_contacts_with_verification_badge':
totalContactsWithVerificationBadge,
'verified_contacts_via_migrated_from_old_version': verifications.values
.where((c) => c == VerificationType.migratedFromOldVersion)
.length,
'verified_contacts_via_qr_scanned': verifications.values
.where((c) => c == VerificationType.qrScanned)
.length,
'verified_contacts_via_link': verifications.values
.where((c) => c == VerificationType.link)
.length,
'verified_contacts_via_secret_qr_token': verifications.values
.where((c) => c == VerificationType.secretQrToken)
.length,
'verified_contacts_via_contact_shared_by_verified': verifications.values
.where((c) => c == VerificationType.contactSharedByVerified)
.length,
};
final response = await http.post(
Uri.parse('$surveyUrlBase/push/$token'),
body: jsonEncode(dataCollection),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
await UserService.update((u) {
u.lastUserStudyDataUpload = DateTime.now();
});
}
if (response.statusCode == 404) {
// Token is unknown to the server...
await UserService.update((u) {
u
..lastUserStudyDataUpload = null
..userStudyParticipantsToken = null;
});
}
} catch (e) {
if (e is http.ClientException ||
e is SocketException ||
e is TimeoutException) {
Log.warn('Error uploading user study data: $e');
} else {
Log.error(e);
}
}
}