feat: Implement core API services for verification, file upload, signed URLs, and LLM, including their response models and API endpoints.

This commit is contained in:
Achintha Isuru
2026-02-25 10:44:39 -05:00
parent 77bb469186
commit ab197c154a
10 changed files with 380 additions and 0 deletions

View File

@@ -9,3 +9,14 @@ export 'src/presentation/observers/core_bloc_observer.dart';
export 'src/config/app_config.dart';
export 'src/routing/routing.dart';
export 'src/services/api_service/api_service.dart';
// Core API Services
export 'src/services/api_service/core_api_services/core_api_endpoints.dart';
export 'src/services/api_service/core_api_services/file_upload/file_upload_service.dart';
export 'src/services/api_service/core_api_services/file_upload/file_upload_response.dart';
export 'src/services/api_service/core_api_services/signed_url/signed_url_service.dart';
export 'src/services/api_service/core_api_services/signed_url/signed_url_response.dart';
export 'src/services/api_service/core_api_services/llm/llm_service.dart';
export 'src/services/api_service/core_api_services/llm/llm_response.dart';
export 'src/services/api_service/core_api_services/verification/verification_service.dart';
export 'src/services/api_service/core_api_services/verification/verification_response.dart';

View File

@@ -0,0 +1,29 @@
/// Constants for Core API endpoints.
class CoreApiEndpoints {
CoreApiEndpoints._();
/// The base URL for the Core API.
static const String baseUrl = 'https://krow-core-api-e3g6witsvq-uc.a.run.app';
/// Upload a file.
static const String uploadFile = '/core/upload-file';
/// Create a signed URL for a file.
static const String createSignedUrl = '/core/create-signed-url';
/// Invoke a Large Language Model.
static const String invokeLlm = '/core/invoke-llm';
/// Root for verification operations.
static const String verifications = '/core/verifications';
/// Get status of a verification job.
static String verificationStatus(String id) => '/core/verifications/$id';
/// Review a verification decision.
static String verificationReview(String id) =>
'/core/verifications/$id/review';
/// Retry a verification job.
static String verificationRetry(String id) => '/core/verifications/$id/retry';
}

View File

@@ -0,0 +1,54 @@
/// Response model for file upload operation.
class FileUploadResponse {
/// Creates a [FileUploadResponse].
const FileUploadResponse({
required this.fileUri,
required this.contentType,
required this.size,
required this.bucket,
required this.path,
this.requestId,
});
/// Factory to create [FileUploadResponse] from JSON.
factory FileUploadResponse.fromJson(Map<String, dynamic> json) {
return FileUploadResponse(
fileUri: json['fileUri'] as String,
contentType: json['contentType'] as String,
size: json['size'] as int,
bucket: json['bucket'] as String,
path: json['path'] as String,
requestId: json['requestId'] as String?,
);
}
/// The Cloud Storage URI of the uploaded file.
final String fileUri;
/// The MIME type of the file.
final String contentType;
/// The size of the file in bytes.
final int size;
/// The bucket where the file was uploaded.
final String bucket;
/// The path within the bucket.
final String path;
/// The unique request ID from the server.
final String? requestId;
/// Converts the response to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'fileUri': fileUri,
'contentType': contentType,
'size': size,
'bucket': bucket,
'path': path,
'requestId': requestId,
};
}
}

View File

@@ -0,0 +1,31 @@
import 'package:dio/dio.dart';
import 'package:krow_domain/krow_domain.dart';
import '../core_api_endpoints.dart';
/// Service for uploading files to the Core API.
class FileUploadService extends BaseCoreService {
/// Creates a [FileUploadService].
FileUploadService(super.api);
/// Uploads a file with optional visibility and category.
///
/// [filePath] is the local path to the file.
/// [visibility] can be 'public' or 'private'.
/// [category] is an optional metadata field.
Future<ApiResponse> uploadFile({
required String filePath,
required String fileName,
String visibility = 'private',
String? category,
}) async {
return action(() async {
final FormData formData = FormData.fromMap(<String, dynamic>{
'file': await MultipartFile.fromFile(filePath, filename: fileName),
'visibility': visibility,
if (category != null) 'category': category,
});
return api.post(CoreApiEndpoints.uploadFile, data: formData);
});
}
}

View File

@@ -0,0 +1,42 @@
/// Response model for LLM invocation.
class LlmResponse {
/// Creates an [LlmResponse].
const LlmResponse({
required this.result,
required this.model,
required this.latencyMs,
this.requestId,
});
/// Factory to create [LlmResponse] from JSON.
factory LlmResponse.fromJson(Map<String, dynamic> json) {
return LlmResponse(
result: json['result'] as Map<String, dynamic>,
model: json['model'] as String,
latencyMs: json['latencyMs'] as int,
requestId: json['requestId'] as String?,
);
}
/// The JSON result returned by the model.
final Map<String, dynamic> result;
/// The model name used for invocation.
final String model;
/// Time taken for the request in milliseconds.
final int latencyMs;
/// The unique request ID from the server.
final String? requestId;
/// Converts the response to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'result': result,
'model': model,
'latencyMs': latencyMs,
'requestId': requestId,
};
}
}

View File

@@ -0,0 +1,31 @@
import 'package:krow_domain/krow_domain.dart';
import '../core_api_endpoints.dart';
/// Service for invoking Large Language Models (LLM).
class LlmService extends BaseCoreService {
/// Creates an [LlmService].
LlmService(super.api);
/// Invokes the LLM with a [prompt] and optional [schema].
///
/// [prompt] is the text instruction for the model.
/// [responseJsonSchema] is an optional JSON schema to enforce structure.
/// [fileUrls] are optional URLs of files (images/PDFs) to include in context.
Future<ApiResponse> invokeLlm({
required String prompt,
Map<String, dynamic>? responseJsonSchema,
List<String>? fileUrls,
}) async {
return action(() async {
return api.post(
CoreApiEndpoints.invokeLlm,
data: <String, dynamic>{
'prompt': prompt,
if (responseJsonSchema != null)
'responseJsonSchema': responseJsonSchema,
if (fileUrls != null) 'fileUrls': fileUrls,
},
);
});
}
}

View File

@@ -0,0 +1,36 @@
/// Response model for creating a signed URL.
class SignedUrlResponse {
/// Creates a [SignedUrlResponse].
const SignedUrlResponse({
required this.signedUrl,
required this.expiresAt,
this.requestId,
});
/// Factory to create [SignedUrlResponse] from JSON.
factory SignedUrlResponse.fromJson(Map<String, dynamic> json) {
return SignedUrlResponse(
signedUrl: json['signedUrl'] as String,
expiresAt: DateTime.parse(json['expiresAt'] as String),
requestId: json['requestId'] as String?,
);
}
/// The generated signed URL.
final String signedUrl;
/// The timestamp when the URL expires.
final DateTime expiresAt;
/// The unique request ID from the server.
final String? requestId;
/// Converts the response to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'signedUrl': signedUrl,
'expiresAt': expiresAt.toIso8601String(),
'requestId': requestId,
};
}
}

View File

@@ -0,0 +1,27 @@
import 'package:krow_domain/krow_domain.dart';
import '../core_api_endpoints.dart';
/// Service for creating signed URLs for Cloud Storage objects.
class SignedUrlService extends BaseCoreService {
/// Creates a [SignedUrlService].
SignedUrlService(super.api);
/// Creates a signed URL for a specific [fileUri].
///
/// [fileUri] should be in gs:// format.
/// [expiresInSeconds] must be <= 900.
Future<ApiResponse> createSignedUrl({
required String fileUri,
int expiresInSeconds = 300,
}) async {
return action(() async {
return api.post(
CoreApiEndpoints.createSignedUrl,
data: <String, dynamic>{
'fileUri': fileUri,
'expiresInSeconds': expiresInSeconds,
},
);
});
}
}

View File

@@ -0,0 +1,50 @@
/// Response model for verification operations.
class VerificationResponse {
/// Creates a [VerificationResponse].
const VerificationResponse({
required this.verificationId,
required this.status,
this.type,
this.review,
this.requestId,
});
/// Factory to create [VerificationResponse] from JSON.
factory VerificationResponse.fromJson(Map<String, dynamic> json) {
return VerificationResponse(
verificationId: json['verificationId'] as String,
status: json['status'] as String,
type: json['type'] as String?,
review: json['review'] != null
? json['review'] as Map<String, dynamic>
: null,
requestId: json['requestId'] as String?,
);
}
/// The unique ID of the verification job.
final String verificationId;
/// Current status (e.g., PENDING, PROCESSING, SUCCESS, FAILED, NEEDS_REVIEW).
final String status;
/// The type of verification (e.g., attire, government_id).
final String? type;
/// Optional human review details.
final Map<String, dynamic>? review;
/// The unique request ID from the server.
final String? requestId;
/// Converts the response to a JSON map.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'verificationId': verificationId,
'status': status,
'type': type,
'review': review,
'requestId': requestId,
};
}
}

View File

@@ -0,0 +1,69 @@
import 'package:krow_domain/krow_domain.dart';
import '../core_api_endpoints.dart';
/// Service for handling async verification jobs.
class VerificationService extends BaseCoreService {
/// Creates a [VerificationService].
VerificationService(super.api);
/// Enqueues a new verification job.
///
/// [type] can be 'attire', 'government_id', etc.
/// [subjectType] is usually 'worker'.
/// [fileUri] is the gs:// path of the uploaded file.
Future<ApiResponse> createVerification({
required String type,
required String subjectType,
required String subjectId,
required String fileUri,
Map<String, dynamic>? rules,
}) async {
return action(() async {
return api.post(
CoreApiEndpoints.verifications,
data: <String, dynamic>{
'type': type,
'subjectType': subjectType,
'subjectId': subjectId,
'fileUri': fileUri,
if (rules != null) 'rules': rules,
},
);
});
}
/// Polls the status of a specific verification.
Future<ApiResponse> getStatus(String verificationId) async {
return action(() async {
return api.get(CoreApiEndpoints.verificationStatus(verificationId));
});
}
/// Submits a manual review decision.
///
/// [decision] should be 'APPROVED' or 'REJECTED'.
Future<ApiResponse> reviewVerification({
required String verificationId,
required String decision,
String? note,
String? reasonCode,
}) async {
return action(() async {
return api.post(
CoreApiEndpoints.verificationReview(verificationId),
data: <String, dynamic>{
'decision': decision,
if (note != null) 'note': note,
if (reasonCode != null) 'reasonCode': reasonCode,
},
);
});
}
/// Retries a verification job that failed or needs re-processing.
Future<ApiResponse> retryVerification(String verificationId) async {
return action(() async {
return api.post(CoreApiEndpoints.verificationRetry(verificationId));
});
}
}