From eff7e40198be055633edab5313c88032249eead4 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 2 Jun 2025 16:15:01 +0200 Subject: [PATCH] Improve generator to reference relationships both ways --- PadelClubData/Data/Gen/BaseClub.swift | 18 +- PadelClubData/Data/Gen/BaseCourt.swift | 15 +- PadelClubData/Data/Gen/BaseCustomUser.swift | 17 +- PadelClubData/Data/Gen/BaseDateInterval.swift | 8 + PadelClubData/Data/Gen/BaseDrawLog.swift | 15 +- PadelClubData/Data/Gen/BaseEvent.swift | 19 +- PadelClubData/Data/Gen/BaseGroupStage.swift | 18 +- PadelClubData/Data/Gen/BaseMatch.swift | 19 +- .../Data/Gen/BaseMatchScheduler.swift | 15 +- PadelClubData/Data/Gen/BaseMonthData.swift | 8 + .../Data/Gen/BasePlayerRegistration.swift | 15 +- PadelClubData/Data/Gen/BasePurchase.swift | 15 +- PadelClubData/Data/Gen/BaseRound.swift | 17 +- .../Data/Gen/BaseTeamRegistration.swift | 22 +- PadelClubData/Data/Gen/BaseTeamScore.swift | 17 +- PadelClubData/Data/Gen/BaseTournament.swift | 21 +- PadelClubData/Data/Gen/generator.py | 214 ++++++++++++++++-- PadelClubDataTests/SyncDataAccessTests.swift | 99 ++++++++ 18 files changed, 519 insertions(+), 53 deletions(-) diff --git a/PadelClubData/Data/Gen/BaseClub.swift b/PadelClubData/Data/Gen/BaseClub.swift index 96e9585..de694d4 100644 --- a/PadelClubData/Data/Gen/BaseClub.swift +++ b/PadelClubData/Data/Gen/BaseClub.swift @@ -142,10 +142,24 @@ public class BaseClub: SyncedModelObject, SyncedStorable { self.timezone = club.timezone } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: CustomUser.self, keyPath: \BaseClub.creator, storeLookup: .same), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: CustomUser.self, keyPath: \BaseClub.creator, mainStoreLookup: false), + Relationship(type: Event.self, keyPath: \BaseEvent.club, storeLookup: .same), + Relationship(type: Court.self, keyPath: \BaseCourt.club, storeLookup: .same), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseCourt.swift b/PadelClubData/Data/Gen/BaseCourt.swift index 087f3f7..68c6786 100644 --- a/PadelClubData/Data/Gen/BaseCourt.swift +++ b/PadelClubData/Data/Gen/BaseCourt.swift @@ -85,10 +85,21 @@ public class BaseCourt: SyncedModelObject, SyncedStorable { self.indoor = court.indoor } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: Club.self, keyPath: \BaseCourt.club, mainStoreLookup: false), + Relationship(type: Club.self, keyPath: \BaseCourt.club, storeLookup: .same), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseCustomUser.swift b/PadelClubData/Data/Gen/BaseCustomUser.swift index 7b0e4fe..44019c6 100644 --- a/PadelClubData/Data/Gen/BaseCustomUser.swift +++ b/PadelClubData/Data/Gen/BaseCustomUser.swift @@ -256,8 +256,23 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable { self.hideUmpirePhone = customuser.hideUmpirePhone } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [] } + public static func childrenRelationships() -> [Relationship] { + return [ + Relationship(type: Club.self, keyPath: \BaseClub.creator, storeLookup: .same), + Relationship(type: Event.self, keyPath: \BaseEvent.creator, storeLookup: .same), + Relationship(type: Purchase.self, keyPath: \BasePurchase.user, storeLookup: .same), + ] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseDateInterval.swift b/PadelClubData/Data/Gen/BaseDateInterval.swift index 891bac6..0db1c77 100644 --- a/PadelClubData/Data/Gen/BaseDateInterval.swift +++ b/PadelClubData/Data/Gen/BaseDateInterval.swift @@ -74,6 +74,14 @@ public class BaseDateInterval: SyncedModelObject, SyncedStorable { self.endDate = dateinterval.endDate } + public static func parentRelationships() -> [Relationship] { + return [] + } + + public static func childrenRelationships() -> [Relationship] { + return [] + } + public static func relationships() -> [Relationship] { return [] } diff --git a/PadelClubData/Data/Gen/BaseDrawLog.swift b/PadelClubData/Data/Gen/BaseDrawLog.swift index 00a5898..7794699 100644 --- a/PadelClubData/Data/Gen/BaseDrawLog.swift +++ b/PadelClubData/Data/Gen/BaseDrawLog.swift @@ -92,10 +92,21 @@ public class BaseDrawLog: SyncedModelObject, SyncedStorable { self.drawType = drawlog.drawType } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament, mainStoreLookup: true), + Relationship(type: Tournament.self, keyPath: \BaseDrawLog.tournament, storeLookup: .main), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseEvent.swift b/PadelClubData/Data/Gen/BaseEvent.swift index 23532e1..566f9bf 100644 --- a/PadelClubData/Data/Gen/BaseEvent.swift +++ b/PadelClubData/Data/Gen/BaseEvent.swift @@ -91,11 +91,24 @@ public class BaseEvent: SyncedModelObject, SyncedStorable { self.tenupId = event.tenupId } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator, storeLookup: .same), + Relationship(type: Club.self, keyPath: \BaseEvent.club, storeLookup: .same), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: CustomUser.self, keyPath: \BaseEvent.creator, mainStoreLookup: false), - Relationship(type: Club.self, keyPath: \BaseEvent.club, mainStoreLookup: false), + Relationship(type: Tournament.self, keyPath: \BaseTournament.event, storeLookup: .same), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseGroupStage.swift b/PadelClubData/Data/Gen/BaseGroupStage.swift index 78bc5af..b978226 100644 --- a/PadelClubData/Data/Gen/BaseGroupStage.swift +++ b/PadelClubData/Data/Gen/BaseGroupStage.swift @@ -112,10 +112,24 @@ public class BaseGroupStage: SyncedModelObject, SyncedStorable { self.plannedStartDate = groupstage.plannedStartDate } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament, storeLookup: .main), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: Tournament.self, keyPath: \BaseGroupStage.tournament, mainStoreLookup: true), + Relationship(type: Match.self, keyPath: \BaseMatch.groupStage, storeLookup: .same), + Relationship(type: TeamRegistration.self, keyPath: \BaseTeamRegistration.groupStage, storeLookup: .same), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseMatch.swift b/PadelClubData/Data/Gen/BaseMatch.swift index d88612c..6e166e6 100644 --- a/PadelClubData/Data/Gen/BaseMatch.swift +++ b/PadelClubData/Data/Gen/BaseMatch.swift @@ -160,11 +160,24 @@ public class BaseMatch: SyncedModelObject, SyncedStorable { self.plannedStartDate = match.plannedStartDate } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: Round.self, keyPath: \BaseMatch.round, storeLookup: .same), + Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage, storeLookup: .same), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: Round.self, keyPath: \BaseMatch.round, mainStoreLookup: false), - Relationship(type: GroupStage.self, keyPath: \BaseMatch.groupStage, mainStoreLookup: false), + Relationship(type: TeamScore.self, keyPath: \BaseTeamScore.match, storeLookup: .same), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseMatchScheduler.swift b/PadelClubData/Data/Gen/BaseMatchScheduler.swift index e4503a1..0059196 100644 --- a/PadelClubData/Data/Gen/BaseMatchScheduler.swift +++ b/PadelClubData/Data/Gen/BaseMatchScheduler.swift @@ -155,10 +155,21 @@ public class BaseMatchScheduler: BaseModelObject, Storable { self.simultaneousStart = matchscheduler.simultaneousStart } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament, mainStoreLookup: true), + Relationship(type: Tournament.self, keyPath: \BaseMatchScheduler.tournament, storeLookup: .main), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseMonthData.swift b/PadelClubData/Data/Gen/BaseMonthData.swift index ea27ce0..c547450 100644 --- a/PadelClubData/Data/Gen/BaseMonthData.swift +++ b/PadelClubData/Data/Gen/BaseMonthData.swift @@ -116,6 +116,14 @@ public class BaseMonthData: BaseModelObject, Storable { self.fileModelIdentifier = monthdata.fileModelIdentifier } + public static func parentRelationships() -> [Relationship] { + return [] + } + + public static func childrenRelationships() -> [Relationship] { + return [] + } + public static func relationships() -> [Relationship] { return [] } diff --git a/PadelClubData/Data/Gen/BasePlayerRegistration.swift b/PadelClubData/Data/Gen/BasePlayerRegistration.swift index 7661e49..22d3177 100644 --- a/PadelClubData/Data/Gen/BasePlayerRegistration.swift +++ b/PadelClubData/Data/Gen/BasePlayerRegistration.swift @@ -219,10 +219,21 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable { self.paymentId = playerregistration.paymentId } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration, mainStoreLookup: false), + Relationship(type: TeamRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration, storeLookup: .same), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BasePurchase.swift b/PadelClubData/Data/Gen/BasePurchase.swift index cc878bc..2af24e4 100644 --- a/PadelClubData/Data/Gen/BasePurchase.swift +++ b/PadelClubData/Data/Gen/BasePurchase.swift @@ -91,10 +91,21 @@ public class BasePurchase: SyncedModelObject, SyncedStorable { self.expirationDate = purchase.expirationDate } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: CustomUser.self, keyPath: \BasePurchase.user, mainStoreLookup: false), + Relationship(type: CustomUser.self, keyPath: \BasePurchase.user, storeLookup: .same), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseRound.swift b/PadelClubData/Data/Gen/BaseRound.swift index 815a3e0..64d0288 100644 --- a/PadelClubData/Data/Gen/BaseRound.swift +++ b/PadelClubData/Data/Gen/BaseRound.swift @@ -112,10 +112,23 @@ public class BaseRound: SyncedModelObject, SyncedStorable { self.plannedStartDate = round.plannedStartDate } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseRound.tournament, storeLookup: .main), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: Tournament.self, keyPath: \BaseRound.tournament, mainStoreLookup: true), + Relationship(type: Match.self, keyPath: \BaseMatch.round, storeLookup: .same), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseTeamRegistration.swift b/PadelClubData/Data/Gen/BaseTeamRegistration.swift index 284ee08..ce7a2b5 100644 --- a/PadelClubData/Data/Gen/BaseTeamRegistration.swift +++ b/PadelClubData/Data/Gen/BaseTeamRegistration.swift @@ -195,11 +195,25 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable { self.pointsEarned = teamregistration.pointsEarned } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: Tournament.self, keyPath: \BaseTeamRegistration.tournament, storeLookup: .main), + Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage, storeLookup: .same), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: Tournament.self, keyPath: \BaseTeamRegistration.tournament, mainStoreLookup: true), - Relationship(type: GroupStage.self, keyPath: \BaseTeamRegistration.groupStage, mainStoreLookup: false), + Relationship(type: TeamScore.self, keyPath: \BaseTeamScore.teamRegistration, storeLookup: .same), + Relationship(type: PlayerRegistration.self, keyPath: \BasePlayerRegistration.teamRegistration, storeLookup: .same), ] } -} + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + +} \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseTeamScore.swift b/PadelClubData/Data/Gen/BaseTeamScore.swift index 14f17b6..ac0bff2 100644 --- a/PadelClubData/Data/Gen/BaseTeamScore.swift +++ b/PadelClubData/Data/Gen/BaseTeamScore.swift @@ -90,11 +90,22 @@ public class BaseTeamScore: SyncedModelObject, SyncedStorable { self.luckyLoser = teamscore.luckyLoser } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { return [ - Relationship(type: Match.self, keyPath: \BaseTeamScore.match, mainStoreLookup: false), - Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration, mainStoreLookup: false), + Relationship(type: Match.self, keyPath: \BaseTeamScore.match, storeLookup: .same), + Relationship(type: TeamRegistration.self, keyPath: \BaseTeamScore.teamRegistration, storeLookup: .same), ] } + public static func childrenRelationships() -> [Relationship] { + return [] + } + + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/BaseTournament.swift b/PadelClubData/Data/Gen/BaseTournament.swift index 0df1d12..457749f 100644 --- a/PadelClubData/Data/Gen/BaseTournament.swift +++ b/PadelClubData/Data/Gen/BaseTournament.swift @@ -596,10 +596,27 @@ public class BaseTournament: SyncedModelObject, SyncedStorable { self.showTeamsInProg = tournament.showTeamsInProg } - public static func relationships() -> [Relationship] { + public static func parentRelationships() -> [Relationship] { + return [ + Relationship(type: Event.self, keyPath: \BaseTournament.event, storeLookup: .same), + ] + } + + public static func childrenRelationships() -> [Relationship] { return [ - Relationship(type: Event.self, keyPath: \BaseTournament.event, mainStoreLookup: false), + Relationship(type: GroupStage.self, keyPath: \BaseGroupStage.tournament, storeLookup: .child), + Relationship(type: MatchScheduler.self, keyPath: \BaseMatchScheduler.tournament, storeLookup: .child), + Relationship(type: Round.self, keyPath: \BaseRound.tournament, storeLookup: .child), + Relationship(type: TeamRegistration.self, keyPath: \BaseTeamRegistration.tournament, storeLookup: .child), + Relationship(type: DrawLog.self, keyPath: \BaseDrawLog.tournament, storeLookup: .child), ] } + public static func relationships() -> [Relationship] { + var relationships: [Relationship] = [] + relationships.append(contentsOf: parentRelationships()) + relationships.append(contentsOf: childrenRelationships()) + return relationships + } + } \ No newline at end of file diff --git a/PadelClubData/Data/Gen/generator.py b/PadelClubData/Data/Gen/generator.py index 22348fa..33e300a 100644 --- a/PadelClubData/Data/Gen/generator.py +++ b/PadelClubData/Data/Gen/generator.py @@ -3,15 +3,78 @@ import re import os from pathlib import Path from typing import Dict, List, Any +from collections import defaultdict import argparse import sys import logging from datetime import datetime import inflect +class RelationshipAnalyzer: + def __init__(self): + self.parent_relationships = defaultdict(list) # model_name -> list of parent relationships + self.children_relationships = defaultdict(list) # model_name -> list of children relationships + + def analyze_all_models(self, input_dir: str) -> None: + """Analyze all JSON files to build complete relationship map.""" + input_path = Path(input_dir) + json_files = list(input_path.glob("*.json")) + + for json_file in json_files: + with open(json_file, 'r') as f: + json_data = json.load(f) + + for model in json_data["models"]: + model_name = model["name"] + properties = model.get("properties", []) + + # Find foreign key properties (parents) + for prop in properties: + if "foreignKey" in prop: + foreign_key = prop["foreignKey"].rstrip('###') + located_on_main_store = prop["foreignKey"].endswith('###') + + if located_on_main_store: + # Store parent relationship + self.parent_relationships[model_name].append({ + "name": prop["name"], + "foreignKey": foreign_key, + "storeLookup": ".main" + }) + + # Store children relationship (reverse) + self.children_relationships[foreign_key].append({ + "name": prop["name"], + "childModel": model_name, + "storeLookup": ".child" + }) + else: + # Store parent relationship + self.parent_relationships[model_name].append({ + "name": prop["name"], + "foreignKey": foreign_key, + "storeLookup": ".same" + }) + + # Store children relationship (reverse) + self.children_relationships[foreign_key].append({ + "name": prop["name"], + "childModel": model_name, + "storeLookup": ".same" + }) + + def get_parent_relationships(self, model_name: str) -> List[Dict[str, Any]]: + """Get parent relationships for a model.""" + return self.parent_relationships.get(model_name, []) + + def get_children_relationships(self, model_name: str) -> List[Dict[str, Any]]: + """Get children relationships for a model.""" + return self.children_relationships.get(model_name, []) + class LeStorageGenerator: - def __init__(self, json_data: Dict[str, Any]): - self.data = json_data + def __init__(self, json_data: Dict[str, Any], relationship_analyzer: RelationshipAnalyzer = None): + self.json_data = json_data + self.relationship_analyzer = relationship_analyzer self.pluralizer = inflect.engine() def generate_model(self, model_data: Dict[str, Any]) -> str: @@ -117,7 +180,7 @@ class LeStorageGenerator: def _generate_constructor(self, model_name: str, properties: List[Dict[str, Any]]) -> List[str]: """Generate a constructor with all properties as parameters with default values.""" - + lines = [" public init("] # Generate parameter list @@ -158,15 +221,15 @@ class LeStorageGenerator: lines.append(f" self.{name} = {name}") lines.append(" }") - + lines.extend([ " required public override init() {", " super.init()", " }", ]) - - + + return lines def _generate_didset_methods(self, properties) -> List[str]: @@ -410,7 +473,7 @@ class LeStorageGenerator: name = prop["name"] foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present foreign_variable = name + "Value" - + # Generate the foreign key check and deletion logic lines.append(f" if {model_variable}.{name} != self.{name}, let {name}Object = self.{foreign_variable}(), {name}Object.shared == true, let store = {name}Object.store {{") lines.append(f" store.deleteUnusedShared({name}Object)") @@ -441,30 +504,45 @@ class LeStorageGenerator: ] def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: - # Find all properties with foreign keys - foreign_key_props = [p for p in properties if "foreignKey" in p] +# if not self.relationship_analyzer: +# # Fallback to old behavior if no analyzer provided +# return self._generate_legacy_relationships(model_name, properties) + + lines = [] - if not foreign_key_props: - # If no foreign keys, return empty array + # Generate parentRelationships method + lines.extend(self._generate_parent_relationships(model_name)) + lines.append("") + + # Generate childrenRelationships method + lines.extend(self._generate_children_relationships(model_name)) + lines.append("") + + # Generate combined relationships method + lines.extend(self._generate_combined_relationships(model_name)) + + return lines + + def _generate_parent_relationships(self, model_name: str) -> List[str]: + """Generate parentRelationships() method.""" + parent_rels = self.relationship_analyzer.get_parent_relationships(model_name) + + if not parent_rels: return [ - " public static func relationships() -> [Relationship] {", + " public static func parentRelationships() -> [Relationship] {", " return []", " }" ] lines = [ - " public static func relationships() -> [Relationship] {", + " public static func parentRelationships() -> [Relationship] {", " return [" ] - # Generate relationship entries - for prop in foreign_key_props: - name = prop["name"] - located_on_main_store = "true" if prop["foreignKey"].endswith('###') else "false" - foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present - lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, mainStoreLookup: {located_on_main_store}),") + for rel in parent_rels: +# main_store = "true" if rel["storeLookup"] else "false" + lines.append(f" Relationship(type: {rel['foreignKey']}.self, keyPath: \\Base{model_name}.{rel['name']}, storeLookup: {rel["storeLookup"]}),") - # Close the array and function lines.extend([ " ]", " }" @@ -472,6 +550,93 @@ class LeStorageGenerator: return lines + def _generate_children_relationships(self, model_name: str) -> List[str]: + """Generate childrenRelationships() method.""" + children_rels = self.relationship_analyzer.get_children_relationships(model_name) + + if not children_rels: + return [ + " public static func childrenRelationships() -> [Relationship] {", + " return []", + " }" + ] + + lines = [ + " public static func childrenRelationships() -> [Relationship] {", + " return [" + ] + + for rel in children_rels: + # main_store = "true" if rel["storeLookup"] else "false" + lines.append(f" Relationship(type: {rel['childModel']}.self, keyPath: \\Base{rel['childModel']}.{rel['name']}, storeLookup: {rel["storeLookup"]}),") + + lines.extend([ + " ]", + " }" + ]) + + return lines + + def _generate_combined_relationships(self, model_name: str) -> List[str]: + """Generate relationships() method that combines parent and children.""" + parent_rels = self.relationship_analyzer.get_parent_relationships(model_name) + children_rels = self.relationship_analyzer.get_children_relationships(model_name) + + if not parent_rels and not children_rels: + return [ + " public static func relationships() -> [Relationship] {", + " return []", + " }" + ] + + lines = [ + " public static func relationships() -> [Relationship] {", + " var relationships: [Relationship] = []", + " relationships.append(contentsOf: parentRelationships())", + " relationships.append(contentsOf: childrenRelationships())", + " return relationships", + " }" + ] + + return lines + +# def _generate_legacy_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: +# """Legacy relationship generation for backward compatibility.""" +# # Find all properties with foreign keys +# foreign_key_props = [p for p in properties if "foreignKey" in p] +# +# if not foreign_key_props: +# # If no foreign keys, return empty array +# return [ +# " public static func relationships() -> [Relationship] {", +# " return []", +# " }" +# ] +# +# lines = [ +# " public static func relationships() -> [Relationship] {", +# " return [" +# ] +# +# # Generate relationship entries +# for prop in foreign_key_props: +# name = prop["name"] +# located_on_main_store = "true" if prop["foreignKey"].endswith('###') else "false" +# foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present +# +# if located_on_main_store: +# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),") +# else: +# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),") +# +# # Close the array and function +# lines.extend([ +# " ]", +# " }" +# ]) +# +# return lines + def _get_default_value(self, type_name: str) -> str: """Get default value for non-optional types""" if "String" in type_name: @@ -516,14 +681,21 @@ def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, d return 0 logger.info(f"Found {len(json_files)} JSON files to process") + + # First pass: Analyze all relationships + logger.info("Analyzing relationships across all models...") + relationship_analyzer = RelationshipAnalyzer() + relationship_analyzer.analyze_all_models(input_dir) + successful_files = 0 + # Second pass: Generate models with complete relationship information for json_file in json_files: try: with open(json_file, 'r') as f: json_data = json.load(f) - generator = LeStorageGenerator(json_data) + generator = LeStorageGenerator(json_data, relationship_analyzer) # Generate each model in the JSON file for model in json_data["models"]: diff --git a/PadelClubDataTests/SyncDataAccessTests.swift b/PadelClubDataTests/SyncDataAccessTests.swift index 621ced2..1e4b3e5 100644 --- a/PadelClubDataTests/SyncDataAccessTests.swift +++ b/PadelClubDataTests/SyncDataAccessTests.swift @@ -293,6 +293,65 @@ struct SyncDataAccessTests { #expect(eventColB.first?.club == club2A.id) } + /// In this test, the first user: + /// - creates one event and 2 clubs + /// - shares the event with a second user + /// - changes the club on the event + /// Here we want to test that the first Club is removed and the second one is received + @Test func testRelationshipChangeFromSharedUser() async throws { + + guard let userId1 = self.storeCenterA.userId else { + throw TestError.notAuthenticated + } + guard let userId2 = self.storeCenterB.userId else { + throw TestError.notAuthenticated + } + + // Setup + let eventColA: SyncedCollection = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection() + let clubColA: SyncedCollection = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection() + let eventColB: SyncedCollection = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection() + let clubColB: SyncedCollection = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection() + + if let dataAccessCollection = self.storeCenterA.dataAccessCollection { + try await dataAccessCollection.deleteAsync(contentOfs: Array(dataAccessCollection)) + } + + try await eventColA.deleteAsync(contentOfs: Array(eventColA)) + try await clubColA.deleteAsync(contentOfs: Array(clubColA)) + + let _ = try await self.storeCenterB.testSynchronizeOnceAsync() + + #expect(eventColB.count == 0) + #expect(clubColB.count == 0) + + let eventA = Event(creator: userId1, name: "event 1") + try await eventColA.addOrUpdateAsync(instance: eventA) + + // Share with user2 + try await self.storeCenterA.setAuthorizedUsersAsync(for: eventA, users: [userId2]) + let _ = try await self.storeCenterB.testSynchronizeOnceAsync() + + #expect(eventColB.count == 1) + let eventB = eventColB.first! + + // Create + let club1B = Club(creator: userId2, name: "Club 1", acronym: "C1") + try await clubColB.addOrUpdateAsync(instance: club1B) + + // Change the club + eventB.club = club1B.id + try await eventColB.addOrUpdateAsync(instance: eventB) + + let dataA = try await self.storeCenterA.testSynchronizeOnceAsync() + + print("club1A = \(club1B.id)") + #expect(eventColA.first != nil) + #expect(eventColA.first?.club == club1B.id) + #expect(clubColA.first?.id == club1B.id) + + } + /// In this test, the first user: /// - creates one event /// - shares the event with a second user @@ -429,6 +488,46 @@ struct SyncDataAccessTests { #expect(matchesColB.count == 44) } + + @Test func testBuildEverythingFromShared() async throws { + + guard let _ = StoreCenter.main.userId else { + throw TestError.notAuthenticated + } + guard let userId2 = self.storeCenterB.userId else { + throw TestError.notAuthenticated + } + + // Cleanup + let tournamentColA: SyncedCollection = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection() + let tournamentColB: SyncedCollection = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection() + + let tournamentsToDelete: [Tournament] = try await StoreCenter.main.service().get() + try await tournamentColA.deleteAsync(contentOfs: tournamentsToDelete) + + // Setup tournament + build everything + let tournament = Tournament() + try await tournamentColA.addOrUpdateAsync(instance: tournament) + + // todo add sharing + try await StoreCenter.main.setAuthorizedUsersAsync(for: tournament, users: [userId2]) + + // Sync with 2nd store + try await self.storeCenterB.testSynchronizeOnceAsync() + #expect(tournamentColB.count == 1) + + let tournamentB = tournamentColB.first! + try await tournamentB.deleteAndBuildEverythingAsync() + + #expect(tournamentColB.count == 1) + + // Sync with 2nd store + let data = try await self.storeCenterA.testSynchronizeOnceAsync() + let _ = try SyncData(data: data, storeCenter: self.storeCenterA) + + #expect(tournamentColA.count == 1) + + } // needs to run on a postgreSQL, otherwise fails because of sqlite database locks @Test func testDataAccessForChildren() async throws {