mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-10 08:18:08 -04:00
285 lines
12 KiB
Swift
285 lines
12 KiB
Swift
import Foundation
|
|
import VaultModels
|
|
|
|
/// Raw field row from database query.
|
|
public struct FieldRow {
|
|
public let itemId: String
|
|
public let fieldKey: String?
|
|
public let fieldDefinitionId: String?
|
|
public let customLabel: String?
|
|
public let customFieldType: String?
|
|
public let customIsHidden: Int64?
|
|
public let customEnableHistory: Int64?
|
|
public let value: String
|
|
public let displayOrder: Int
|
|
|
|
public init(
|
|
itemId: String,
|
|
fieldKey: String?,
|
|
fieldDefinitionId: String?,
|
|
customLabel: String?,
|
|
customFieldType: String?,
|
|
customIsHidden: Int64?,
|
|
customEnableHistory: Int64?,
|
|
value: String,
|
|
displayOrder: Int
|
|
) {
|
|
self.itemId = itemId
|
|
self.fieldKey = fieldKey
|
|
self.fieldDefinitionId = fieldDefinitionId
|
|
self.customLabel = customLabel
|
|
self.customFieldType = customFieldType
|
|
self.customIsHidden = customIsHidden
|
|
self.customEnableHistory = customEnableHistory
|
|
self.value = value
|
|
self.displayOrder = displayOrder
|
|
}
|
|
|
|
/// Initialize from a database row dictionary.
|
|
public init?(from row: [String: Any]) {
|
|
guard let itemId = row["ItemId"] as? String else { return nil }
|
|
|
|
self.itemId = itemId
|
|
self.fieldKey = row["FieldKey"] as? String
|
|
self.fieldDefinitionId = row["FieldDefinitionId"] as? String
|
|
self.customLabel = row["CustomLabel"] as? String
|
|
self.customFieldType = row["CustomFieldType"] as? String
|
|
self.customIsHidden = row["CustomIsHidden"] as? Int64
|
|
self.customEnableHistory = row["CustomEnableHistory"] as? Int64
|
|
self.value = row["Value"] as? String ?? ""
|
|
self.displayOrder = Int(row["DisplayOrder"] as? Int64 ?? 0)
|
|
}
|
|
}
|
|
|
|
/// Raw field row for single item queries (without ItemId).
|
|
public struct SingleItemFieldRow {
|
|
public let fieldKey: String?
|
|
public let fieldDefinitionId: String?
|
|
public let customLabel: String?
|
|
public let customFieldType: String?
|
|
public let customIsHidden: Int64?
|
|
public let customEnableHistory: Int64?
|
|
public let value: String
|
|
public let displayOrder: Int
|
|
|
|
/// Initialize from a database row dictionary.
|
|
public init?(from row: [String: Any]) {
|
|
self.fieldKey = row["FieldKey"] as? String
|
|
self.fieldDefinitionId = row["FieldDefinitionId"] as? String
|
|
self.customLabel = row["CustomLabel"] as? String
|
|
self.customFieldType = row["CustomFieldType"] as? String
|
|
self.customIsHidden = row["CustomIsHidden"] as? Int64
|
|
self.customEnableHistory = row["CustomEnableHistory"] as? Int64
|
|
self.value = row["Value"] as? String ?? ""
|
|
self.displayOrder = Int(row["DisplayOrder"] as? Int64 ?? 0)
|
|
}
|
|
}
|
|
|
|
/// Intermediate field representation before grouping.
|
|
public struct ProcessedField {
|
|
public let itemId: String
|
|
public let fieldKey: String
|
|
public let label: String
|
|
public let fieldType: String
|
|
public let isHidden: Bool
|
|
public let value: String
|
|
public let displayOrder: Int
|
|
public let isCustomField: Bool
|
|
public let enableHistory: Bool
|
|
}
|
|
|
|
/// Mapper class for processing database field rows into ItemField objects.
|
|
/// Handles both system fields (with FieldKey) and custom fields (with FieldDefinitionId).
|
|
public struct FieldMapper {
|
|
/// Process raw field rows from database into a map of ItemId -> [ItemField].
|
|
/// Handles system vs custom fields. Multi-value fields (like URLs) create separate ItemField entries
|
|
/// with the same fieldKey but different values, allowing Item.getFieldValues() to return all values.
|
|
/// - Parameter rows: Raw field rows from database
|
|
/// - Returns: Dictionary of ItemId to array of ItemField objects
|
|
public static func processFieldRows(_ rows: [FieldRow]) -> [String: [ItemField]] {
|
|
// First, convert rows to processed fields with proper metadata
|
|
let processedFields = rows.map { processFieldRow($0) }
|
|
|
|
// Group fields by ItemId, keeping separate entries for multi-value fields
|
|
var fieldsByItem: [String: [ItemField]] = [:]
|
|
|
|
for field in processedFields {
|
|
if fieldsByItem[field.itemId] == nil {
|
|
fieldsByItem[field.itemId] = []
|
|
}
|
|
|
|
// Create an ItemField for each row (including duplicates for multi-value fields)
|
|
let itemField = ItemField(
|
|
fieldKey: field.fieldKey,
|
|
label: field.label,
|
|
fieldType: field.fieldType,
|
|
value: field.value,
|
|
isHidden: field.isHidden,
|
|
displayOrder: field.displayOrder,
|
|
isCustomField: field.isCustomField,
|
|
enableHistory: field.enableHistory
|
|
)
|
|
fieldsByItem[field.itemId]!.append(itemField)
|
|
}
|
|
|
|
return fieldsByItem
|
|
}
|
|
|
|
/// Process a single field row to extract proper metadata.
|
|
/// System fields use FieldKey and get metadata from SystemFieldRegistry.
|
|
/// Custom fields use FieldDefinitionId and get metadata from the row.
|
|
/// - Parameter row: Raw field row
|
|
/// - Returns: Processed field with proper metadata
|
|
private static func processFieldRow(_ row: FieldRow) -> ProcessedField {
|
|
if let fieldKey = row.fieldKey, !fieldKey.isEmpty {
|
|
// System field: has FieldKey, get metadata from field metadata resolver
|
|
let metadata = resolveFieldMetadata(
|
|
fieldKey: fieldKey,
|
|
customLabel: nil,
|
|
customFieldType: nil,
|
|
customIsHidden: false,
|
|
customEnableHistory: false,
|
|
isCustomField: false
|
|
)
|
|
return ProcessedField(
|
|
itemId: row.itemId,
|
|
fieldKey: fieldKey,
|
|
label: metadata.label,
|
|
fieldType: metadata.fieldType,
|
|
isHidden: metadata.isHidden,
|
|
value: row.value,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: false,
|
|
enableHistory: metadata.enableHistory
|
|
)
|
|
} else {
|
|
// Custom field: has FieldDefinitionId, get metadata from FieldDefinitions
|
|
let fieldKey = row.fieldDefinitionId ?? ""
|
|
return ProcessedField(
|
|
itemId: row.itemId,
|
|
fieldKey: fieldKey,
|
|
label: row.customLabel ?? "",
|
|
fieldType: row.customFieldType ?? FieldType.text,
|
|
isHidden: row.customIsHidden == 1,
|
|
value: row.value,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: true,
|
|
enableHistory: row.customEnableHistory == 1
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Process field rows for a single item (without ItemId in result).
|
|
/// Used when fetching a single item by ID.
|
|
/// Multi-value fields (like URLs) create separate ItemField entries with the same fieldKey.
|
|
/// - Parameter rows: Raw field rows for a single item
|
|
/// - Returns: Array of ItemField objects
|
|
public static func processFieldRowsForSingleItem(_ rows: [SingleItemFieldRow]) -> [ItemField] {
|
|
// Create an ItemField for each row (including duplicates for multi-value fields)
|
|
return rows.map { row in
|
|
let fieldKey = row.fieldKey ?? row.fieldDefinitionId ?? ""
|
|
let isCustomField = row.fieldKey == nil || row.fieldKey!.isEmpty
|
|
|
|
if !isCustomField, let rowFieldKey = row.fieldKey {
|
|
// System field
|
|
let metadata = resolveFieldMetadata(
|
|
fieldKey: rowFieldKey,
|
|
customLabel: nil,
|
|
customFieldType: nil,
|
|
customIsHidden: false,
|
|
customEnableHistory: false,
|
|
isCustomField: false
|
|
)
|
|
return ItemField(
|
|
fieldKey: rowFieldKey,
|
|
label: metadata.label,
|
|
fieldType: metadata.fieldType,
|
|
value: row.value,
|
|
isHidden: metadata.isHidden,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: false,
|
|
enableHistory: metadata.enableHistory
|
|
)
|
|
} else {
|
|
// Custom field
|
|
return ItemField(
|
|
fieldKey: fieldKey,
|
|
label: row.customLabel ?? "",
|
|
fieldType: row.customFieldType ?? FieldType.text,
|
|
value: row.value,
|
|
isHidden: row.customIsHidden == 1,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: true,
|
|
enableHistory: row.customEnableHistory == 1
|
|
)
|
|
}
|
|
}.sorted { $0.displayOrder < $1.displayOrder }
|
|
}
|
|
|
|
// MARK: - Field Metadata Resolution
|
|
|
|
/// Helper struct to hold resolved field metadata.
|
|
private struct FieldMetadata {
|
|
let label: String
|
|
let fieldType: String
|
|
let isHidden: Bool
|
|
let enableHistory: Bool
|
|
}
|
|
|
|
/// Resolve field metadata for system fields and custom fields.
|
|
private static func resolveFieldMetadata(
|
|
fieldKey: String,
|
|
customLabel: String?,
|
|
customFieldType: String?,
|
|
customIsHidden: Bool,
|
|
customEnableHistory: Bool,
|
|
isCustomField: Bool
|
|
) -> FieldMetadata {
|
|
if isCustomField {
|
|
return FieldMetadata(
|
|
label: customLabel ?? fieldKey,
|
|
fieldType: customFieldType ?? FieldType.text,
|
|
isHidden: customIsHidden,
|
|
enableHistory: customEnableHistory
|
|
)
|
|
}
|
|
|
|
// System field metadata based on FieldKey constants
|
|
switch fieldKey {
|
|
case FieldKey.loginUsername:
|
|
return FieldMetadata(label: "Username", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.loginPassword:
|
|
return FieldMetadata(label: "Password", fieldType: FieldType.password, isHidden: true, enableHistory: true)
|
|
case FieldKey.loginEmail:
|
|
return FieldMetadata(label: "Email", fieldType: FieldType.email, isHidden: false, enableHistory: false)
|
|
case FieldKey.loginUrl:
|
|
return FieldMetadata(label: "URL", fieldType: FieldType.uRL, isHidden: false, enableHistory: false)
|
|
case FieldKey.cardNumber:
|
|
return FieldMetadata(label: "Card Number", fieldType: FieldType.text, isHidden: true, enableHistory: false)
|
|
case FieldKey.cardCardholderName:
|
|
return FieldMetadata(label: "Cardholder Name", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.cardExpiryMonth:
|
|
return FieldMetadata(label: "Expiry Month", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.cardExpiryYear:
|
|
return FieldMetadata(label: "Expiry Year", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.cardCvv:
|
|
return FieldMetadata(label: "CVV", fieldType: FieldType.password, isHidden: true, enableHistory: false)
|
|
case FieldKey.cardPin:
|
|
return FieldMetadata(label: "PIN", fieldType: FieldType.password, isHidden: true, enableHistory: false)
|
|
case FieldKey.aliasFirstName:
|
|
return FieldMetadata(label: "First Name", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.aliasLastName:
|
|
return FieldMetadata(label: "Last Name", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.aliasGender:
|
|
return FieldMetadata(label: "Gender", fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
case FieldKey.aliasBirthdate:
|
|
return FieldMetadata(label: "Birth Date", fieldType: FieldType.date, isHidden: false, enableHistory: false)
|
|
case FieldKey.notesContent:
|
|
return FieldMetadata(label: "Notes", fieldType: FieldType.textArea, isHidden: false, enableHistory: false)
|
|
default:
|
|
// Unknown system field - use field key as label
|
|
return FieldMetadata(label: fieldKey, fieldType: FieldType.text, isHidden: false, enableHistory: false)
|
|
}
|
|
}
|
|
}
|