feat: Enhance certificate upload process with file change verification and signed URL generation
This commit is contained in:
@@ -35,46 +35,63 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
String? certificateNumber,
|
||||
}) async {
|
||||
return _service.run(() async {
|
||||
// 1. Upload the file to cloud storage
|
||||
final FileUploadResponse uploadRes = await _uploadService.uploadFile(
|
||||
filePath: filePath,
|
||||
fileName:
|
||||
'staff_cert_${certificationType.name}_${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
visibility: domain.FileVisibility.private,
|
||||
);
|
||||
// Get existing certificate to check if file has changed
|
||||
final List<domain.StaffCertificate> existingCerts = await getCertificates();
|
||||
domain.StaffCertificate? existingCert;
|
||||
try {
|
||||
existingCert = existingCerts.firstWhere(
|
||||
(domain.StaffCertificate c) => c.certificationType == certificationType,
|
||||
);
|
||||
} catch (e) {
|
||||
// Certificate doesn't exist yet
|
||||
}
|
||||
|
||||
// 2. Generate a signed URL for verification service to access the file
|
||||
// Wait, verification service might need this or just the URI.
|
||||
// Following DocumentRepository behavior:
|
||||
final SignedUrlResponse signedUrlRes = await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
||||
String? signedUrl = existingCert?.certificateUrl;
|
||||
String? verificationId = existingCert?.verificationId;
|
||||
final bool fileChanged = existingCert == null || existingCert.certificateUrl != filePath;
|
||||
|
||||
// 3. Initiate verification
|
||||
final String staffId = await _service.getStaffId();
|
||||
final VerificationResponse verificationRes = await _verificationService
|
||||
.createVerification(
|
||||
fileUri: uploadRes.fileUri,
|
||||
type: 'certification',
|
||||
subjectType: 'worker',
|
||||
subjectId: staffId,
|
||||
rules: <String, dynamic>{
|
||||
'certificateName': name,
|
||||
'certificateIssuer': issuer,
|
||||
'certificateNumber': certificateNumber,
|
||||
},
|
||||
);
|
||||
// Only upload and verify if file path has changed
|
||||
if (fileChanged) {
|
||||
// 1. Upload the file to cloud storage
|
||||
final FileUploadResponse uploadRes = await _uploadService.uploadFile(
|
||||
filePath: filePath,
|
||||
fileName:
|
||||
'staff_cert_${certificationType.name}_${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
visibility: domain.FileVisibility.private,
|
||||
);
|
||||
|
||||
// 2. Generate a signed URL for verification service to access the file
|
||||
final SignedUrlResponse signedUrlRes = await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
||||
signedUrl = signedUrlRes.signedUrl;
|
||||
|
||||
// 3. Initiate verification
|
||||
final String staffId = await _service.getStaffId();
|
||||
final VerificationResponse verificationRes = await _verificationService
|
||||
.createVerification(
|
||||
fileUri: uploadRes.fileUri,
|
||||
type: 'certification',
|
||||
subjectType: 'worker',
|
||||
subjectId: staffId,
|
||||
rules: <String, dynamic>{
|
||||
'certificateName': name,
|
||||
'certificateIssuer': issuer,
|
||||
'certificateNumber': certificateNumber,
|
||||
},
|
||||
);
|
||||
verificationId = verificationRes.verificationId;
|
||||
}
|
||||
|
||||
// 4. Update/Create Certificate in Data Connect
|
||||
await _service.getStaffRepository().upsertStaffCertificate(
|
||||
certificationType: certificationType,
|
||||
name: name,
|
||||
status: domain.StaffCertificateStatus.pending,
|
||||
fileUrl: signedUrlRes.signedUrl,
|
||||
status: existingCert?.status ?? domain.StaffCertificateStatus.pending,
|
||||
fileUrl: signedUrl,
|
||||
expiry: expiryDate,
|
||||
issuer: issuer,
|
||||
certificateNumber: certificateNumber,
|
||||
validationStatus:
|
||||
domain.StaffCertificateValidationStatus.pendingExpertReview,
|
||||
verificationId: verificationRes.verificationId,
|
||||
validationStatus: existingCert?.validationStatus ?? domain.StaffCertificateValidationStatus.pendingExpertReview,
|
||||
verificationId: verificationId,
|
||||
);
|
||||
|
||||
// 5. Return updated list or the specific certificate
|
||||
|
||||
@@ -78,22 +78,16 @@ class BankAccountPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
SecurityNotice(strings: strings),
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
if (state.accounts.isEmpty)
|
||||
if (state.accounts.isEmpty) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
const UiEmptyState(
|
||||
icon: UiIcons.building,
|
||||
title: 'No accounts yet',
|
||||
description:
|
||||
'Add your first bank account to get started',
|
||||
)
|
||||
else ...<Widget>[
|
||||
Text(
|
||||
strings.linked_accounts,
|
||||
style: UiTypography.headline4m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
] else ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
...state.accounts.map<Widget>(
|
||||
(StaffBankAccount account) =>
|
||||
AccountCard(account: account, strings: strings),
|
||||
|
||||
@@ -19,11 +19,12 @@ class TimeCardPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TimeCardPageState extends State<TimeCardPage> {
|
||||
final TimeCardBloc _bloc = Modular.get<TimeCardBloc>();
|
||||
late final TimeCardBloc _bloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bloc = Modular.get<TimeCardBloc>();
|
||||
_bloc.add(LoadTimeCards(DateTime.now()));
|
||||
}
|
||||
|
||||
@@ -33,25 +34,9 @@ class _TimeCardPageState extends State<TimeCardPage> {
|
||||
return BlocProvider.value(
|
||||
value: _bloc,
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgPrimary,
|
||||
appBar: AppBar(
|
||||
backgroundColor: UiColors.bgPopup,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
UiIcons.chevronLeft,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onPressed: () => Modular.to.popSafe(),
|
||||
),
|
||||
title: Text(
|
||||
t.staff_time_card.title,
|
||||
style: UiTypography.headline4m.textPrimary,
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_time_card.title,
|
||||
showBackButton: true,
|
||||
),
|
||||
body: BlocConsumer<TimeCardBloc, TimeCardState>(
|
||||
listener: (BuildContext context, TimeCardState state) {
|
||||
|
||||
@@ -17,10 +17,6 @@ class FaqsPage extends StatelessWidget {
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_faqs.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<FaqsBloc>(
|
||||
create: (BuildContext context) =>
|
||||
|
||||
@@ -18,10 +18,6 @@ class PrivacyPolicyPage extends StatelessWidget {
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_privacy_security.privacy_policy.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<PrivacyPolicyCubit>(
|
||||
create: (BuildContext context) => Modular.get<PrivacyPolicyCubit>()..fetchPrivacyPolicy(),
|
||||
|
||||
@@ -18,10 +18,6 @@ class TermsOfServicePage extends StatelessWidget {
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_privacy_security.terms_of_service.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<TermsCubit>(
|
||||
create: (BuildContext context) => Modular.get<TermsCubit>()..fetchTerms(),
|
||||
|
||||
@@ -18,10 +18,6 @@ class PrivacySecurityPage extends StatelessWidget {
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_privacy_security.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<PrivacySecurityBloc>.value(
|
||||
value: Modular.get<PrivacySecurityBloc>()
|
||||
|
||||
Reference in New Issue
Block a user