/// 照片域模型(P3 计划 Task 2):管理端照片记录与照片枚举。 /// /// 契约来源:`packages/contracts/openapi.yaml` tag `photos`。 library; /// 照片对外可见性。 /// /// 契约 `components.schemas.PhotoVisibility`:unpublished/published/hidden/ /// deleted;deleted 为软删除。管理端 PATCH /photos/{photoId} 仅允许在 /// hidden 与 published 间切换。 enum PhotoVisibility { unpublished('unpublished'), published('published'), hidden('hidden'), deleted('deleted'); const PhotoVisibility(this.wire); /// 线上(JSON)取值。 final String wire; static PhotoVisibility fromJson(String value) => values.firstWhere( (v) => v.wire == value, orElse: () => throw FormatException('未知 PhotoVisibility: $value'), ); } /// 照片服务端处理状态。 /// /// 契约 `components.schemas.PhotoProcessingState`(技术实施方案 §5.5 /// 「服务端处理」链路)。 enum PhotoProcessingState { pendingUpload('pending_upload'), uploaded('uploaded'), processing('processing'), ready('ready'), processingFailed('processing_failed'); const PhotoProcessingState(this.wire); /// 线上(JSON)取值。 final String wire; static PhotoProcessingState fromJson(String value) => values.firstWhere( (v) => v.wire == value, orElse: () => throw FormatException('未知 PhotoProcessingState: $value'), ); } /// 管理端照片记录。 /// /// 契约 `components.schemas.PhotoRecord`(技术实施方案 §4 photos 表字段)。 /// 作为 GET /albums/{albumId}/photos 条目时经 [AdminPhoto] 内嵌 uploader; /// PATCH /photos/{photoId}(operationId `updatePhoto`)的响应为裸 /// PhotoRecord。 class PhotoRecord { const PhotoRecord({ required this.photoId, required this.albumId, required this.categoryId, required this.uploaderId, required this.visibility, required this.processingState, required this.bytes, required this.width, required this.height, required this.shotAt, required this.uploadedAt, required this.publishedAt, required this.likeCount, }); factory PhotoRecord.fromJson(Map json) => PhotoRecord( photoId: json['photoId'] as String, albumId: json['albumId'] as String, categoryId: json['categoryId'] as String?, uploaderId: json['uploaderId'] as String, visibility: PhotoVisibility.fromJson(json['visibility'] as String), processingState: PhotoProcessingState.fromJson(json['processingState'] as String), bytes: json['bytes'] as int, width: json['width'] as int?, height: json['height'] as int?, shotAt: json['shotAt'] == null ? null : DateTime.parse(json['shotAt'] as String), uploadedAt: DateTime.parse(json['uploadedAt'] as String), publishedAt: json['publishedAt'] == null ? null : DateTime.parse(json['publishedAt'] as String), likeCount: json['likeCount'] as int, ); /// 照片 ID。 final String photoId; /// 所属相册 ID。 final String albumId; /// 分类 ID;null 表示未分类。 final String? categoryId; /// 上传人用户 ID。 final String uploaderId; /// 对外可见性。 final PhotoVisibility visibility; /// 服务端处理状态。 final PhotoProcessingState processingState; /// 原图字节数。 final int bytes; /// 原图宽度(像素);处理前为 null。 final int? width; /// 原图高度(像素);处理前为 null。 final int? height; /// 拍摄时间(EXIF);无有效值时为 null。 final DateTime? shotAt; /// 上传完成时间。 final DateTime uploadedAt; /// 发布时间;未发布为 null。 final DateTime? publishedAt; /// 累计点赞数。 final int likeCount; Map toJson() => { 'photoId': photoId, 'albumId': albumId, 'categoryId': categoryId, 'uploaderId': uploaderId, 'visibility': visibility.wire, 'processingState': processingState.wire, 'bytes': bytes, 'width': width, 'height': height, 'shotAt': shotAt?.toUtc().toIso8601String(), 'uploadedAt': uploadedAt.toUtc().toIso8601String(), 'publishedAt': publishedAt?.toUtc().toIso8601String(), 'likeCount': likeCount, }; } /// 管理列表条目内嵌的上传人脱敏信息。 /// /// 契约 `components.schemas.AdminPhotoUploader`(不含邮箱、手机等凭据字段)。 class AdminPhotoUploader { const AdminPhotoUploader({ required this.userId, required this.account, required this.displayName, }); factory AdminPhotoUploader.fromJson(Map json) => AdminPhotoUploader( userId: json['userId'] as String, account: json['account'] as String, displayName: json['displayName'] as String, ); /// 上传人用户 ID(与 PhotoRecord.uploaderId 一致)。 final String userId; /// 登录账号。 final String account; /// 展示姓名(来自用户资料)。 final String displayName; Map toJson() => { 'userId': userId, 'account': account, 'displayName': displayName, }; } /// 管理端照片列表条目。 /// /// 契约 `components.schemas.AdminPhoto`(= PhotoRecord + uploader 内嵌), /// GET /albums/{albumId}/photos(operationId `listAlbumPhotos`)的条目。 class AdminPhoto extends PhotoRecord { const AdminPhoto({ required this.uploader, required super.photoId, required super.albumId, required super.categoryId, required super.uploaderId, required super.visibility, required super.processingState, required super.bytes, required super.width, required super.height, required super.shotAt, required super.uploadedAt, required super.publishedAt, required super.likeCount, }); factory AdminPhoto.fromJson(Map json) => AdminPhoto( uploader: AdminPhotoUploader.fromJson( json['uploader'] as Map, ), photoId: json['photoId'] as String, albumId: json['albumId'] as String, categoryId: json['categoryId'] as String?, uploaderId: json['uploaderId'] as String, visibility: PhotoVisibility.fromJson(json['visibility'] as String), processingState: PhotoProcessingState.fromJson(json['processingState'] as String), bytes: json['bytes'] as int, width: json['width'] as int?, height: json['height'] as int?, shotAt: json['shotAt'] == null ? null : DateTime.parse(json['shotAt'] as String), uploadedAt: DateTime.parse(json['uploadedAt'] as String), publishedAt: json['publishedAt'] == null ? null : DateTime.parse(json['publishedAt'] as String), likeCount: json['likeCount'] as int, ); /// 上传人脱敏信息。 final AdminPhotoUploader uploader; @override Map toJson() => {...super.toJson(), 'uploader': uploader.toJson()}; }