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:
@@ -9,3 +9,14 @@ export 'src/presentation/observers/core_bloc_observer.dart';
|
|||||||
export 'src/config/app_config.dart';
|
export 'src/config/app_config.dart';
|
||||||
export 'src/routing/routing.dart';
|
export 'src/routing/routing.dart';
|
||||||
export 'src/services/api_service/api_service.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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user