mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-14 18:35:16 -04:00
358 lines
15 KiB
Swift
358 lines
15 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 and multi-value field grouping.
|
|
/// - 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 by ItemId and FieldKey (to handle multi-value fields)
|
|
var fieldsByItem: [String: [ItemField]] = [:]
|
|
var fieldValuesByKey: [String: [String]] = [:]
|
|
|
|
for field in processedFields {
|
|
let key = "\(field.itemId)_\(field.fieldKey)"
|
|
|
|
// Accumulate values for the same field
|
|
if fieldValuesByKey[key] == nil {
|
|
fieldValuesByKey[key] = []
|
|
}
|
|
fieldValuesByKey[key]!.append(field.value)
|
|
|
|
// Create ItemField entry only once per unique FieldKey per item
|
|
if fieldsByItem[field.itemId] == nil {
|
|
fieldsByItem[field.itemId] = []
|
|
}
|
|
|
|
let itemFields = fieldsByItem[field.itemId]!
|
|
let existingField = itemFields.first { $0.fieldKey == field.fieldKey }
|
|
|
|
if existingField == nil {
|
|
let itemField = ItemField(
|
|
fieldKey: field.fieldKey,
|
|
label: field.label,
|
|
fieldType: field.fieldType,
|
|
value: "", // Will be set below
|
|
isHidden: field.isHidden,
|
|
displayOrder: field.displayOrder,
|
|
isCustomField: field.isCustomField,
|
|
enableHistory: field.enableHistory
|
|
)
|
|
fieldsByItem[field.itemId]!.append(itemField)
|
|
}
|
|
}
|
|
|
|
// Set Values (using first value for single value or concatenated for multi-value)
|
|
for (itemId, fields) in fieldsByItem {
|
|
var updatedFields: [ItemField] = []
|
|
for field in fields {
|
|
let key = "\(itemId)_\(field.fieldKey)"
|
|
let values = fieldValuesByKey[key] ?? []
|
|
|
|
// Use first value (multi-value fields would need different handling in the model)
|
|
let value = values.first ?? ""
|
|
|
|
let updatedField = ItemField(
|
|
fieldKey: field.fieldKey,
|
|
label: field.label,
|
|
fieldType: field.fieldType,
|
|
value: value,
|
|
isHidden: field.isHidden,
|
|
displayOrder: field.displayOrder,
|
|
isCustomField: field.isCustomField,
|
|
enableHistory: field.enableHistory
|
|
)
|
|
updatedFields.append(updatedField)
|
|
}
|
|
fieldsByItem[itemId] = updatedFields
|
|
}
|
|
|
|
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.
|
|
/// - Parameter rows: Raw field rows for a single item
|
|
/// - Returns: Array of ItemField objects
|
|
public static func processFieldRowsForSingleItem(_ rows: [SingleItemFieldRow]) -> [ItemField] {
|
|
var fieldValuesByKey: [String: [String]] = [:]
|
|
var uniqueFields: [String: UniqueFieldData] = [:]
|
|
|
|
for row in rows {
|
|
let fieldKey = row.fieldKey ?? row.fieldDefinitionId ?? ""
|
|
|
|
// Accumulate values
|
|
if fieldValuesByKey[fieldKey] == nil {
|
|
fieldValuesByKey[fieldKey] = []
|
|
}
|
|
fieldValuesByKey[fieldKey]!.append(row.value)
|
|
|
|
// Store field metadata (only once per FieldKey)
|
|
if uniqueFields[fieldKey] == nil {
|
|
if let rowFieldKey = row.fieldKey, !rowFieldKey.isEmpty {
|
|
// System field
|
|
let metadata = resolveFieldMetadata(
|
|
fieldKey: rowFieldKey,
|
|
customLabel: nil,
|
|
customFieldType: nil,
|
|
customIsHidden: false,
|
|
customEnableHistory: false,
|
|
isCustomField: false
|
|
)
|
|
uniqueFields[fieldKey] = UniqueFieldData(
|
|
fieldKey: rowFieldKey,
|
|
label: metadata.label,
|
|
fieldType: metadata.fieldType,
|
|
isHidden: metadata.isHidden,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: false,
|
|
enableHistory: metadata.enableHistory
|
|
)
|
|
} else {
|
|
// Custom field
|
|
uniqueFields[fieldKey] = UniqueFieldData(
|
|
fieldKey: fieldKey,
|
|
label: row.customLabel ?? "",
|
|
fieldType: row.customFieldType ?? FieldType.text,
|
|
isHidden: row.customIsHidden == 1,
|
|
displayOrder: row.displayOrder,
|
|
isCustomField: true,
|
|
enableHistory: row.customEnableHistory == 1
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build fields array with proper single/multi values
|
|
return uniqueFields.map { (fieldKey, fieldData) in
|
|
let values = fieldValuesByKey[fieldKey] ?? []
|
|
let value = values.first ?? ""
|
|
|
|
return ItemField(
|
|
fieldKey: fieldData.fieldKey,
|
|
label: fieldData.label,
|
|
fieldType: fieldData.fieldType,
|
|
value: value,
|
|
isHidden: fieldData.isHidden,
|
|
displayOrder: fieldData.displayOrder,
|
|
isCustomField: fieldData.isCustomField,
|
|
enableHistory: fieldData.enableHistory
|
|
)
|
|
}.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
|
|
}
|
|
|
|
/// Helper struct to hold unique field data for single item processing.
|
|
private struct UniqueFieldData {
|
|
let fieldKey: String
|
|
let label: String
|
|
let fieldType: String
|
|
let isHidden: Bool
|
|
let displayOrder: Int
|
|
let isCustomField: 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)
|
|
}
|
|
}
|
|
}
|