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/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';
|
||||
|
||||
@@ -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