Skip to content

Commit cf29ce6

Browse files
authored
feat: create db from json schema (#3)
* wip: prompt user to create db from json definition * implement and invoke CreateDatabase * refactor CreateCollectionAttributes to be more dynamic * add documentation comments to CreateCollectionAttributes * chore: update comment * chore: remove CollectionDTO from Program.cs * create or reset database from schema * make input errors more prominent * refactor: descriptive binding names, prune redundant statements
1 parent 62f32cb commit cf29ce6

File tree

3 files changed

+108
-114
lines changed

3 files changed

+108
-114
lines changed

AppwriteClient/AppwriteService.cs

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ public AppwriteService(IConfiguration config)
2323
databaseClient = new Databases(_client);
2424
}
2525

26+
public async Task CreateDatabase(DatabaseDTO databaseDTO)
27+
{
28+
await databaseClient.Create(databaseDTO.DatabaseId, databaseDTO.Name, false);
29+
}
30+
31+
public async Task DeleteDatabase(string databaseId)
32+
{
33+
await databaseClient.Delete(databaseId);
34+
}
35+
2636
/// <summary>
2737
/// Retrieves a database by its ID.
2838
/// </summary>
@@ -64,61 +74,51 @@ public async Task CreateCollections(string databaseId, List<CollectionDTO> colle
6474
foreach (var collection in collectionList)
6575
{
6676
await databaseClient.CreateCollection(databaseId, collection.CollectionId, collection.Name);
67-
await CreateCollectionAttributes(databaseId, collection.CollectionId, collection.StringAttributes, collection.EmailAttributes, collection.EnumAttributes, collection.IPAddressAttributes, collection.URLAttributes, collection.IntegerAttributes, collection.FloatAttributes, collection.BooleanAttributes, collection.DatetimeAttributes, collection.RelationshipAttributes);
77+
await CreateCollectionAttributes(databaseId, collection.CollectionId, collection);
6878
}
6979
}
7080

71-
private async Task CreateCollectionAttributes(string databaseId, string collectionId, List<StringAttribute> stringAttributes, List<EmailAttribute> emailAttributes, List<EnumAttribute> enumAttributes, List<IPAddressAttribute> iPAddressAttributes, List<URLAttribute> uRLAttributes, List<IntegerAttribute> integerAttributes, List<FloatAttribute> floatAttributes, List<BooleanAttribute> booleanAttributes, List<DatetimeAttribute> datetimeAttributes, List<RelationshipAttribute> relationshipAttributes)
81+
/// <summary>
82+
/// Creates the attributes for a collection in the Appwrite database.
83+
/// </summary>
84+
/// <param name="databaseId">The ID of the database.</param>
85+
/// <param name="collectionId">The ID of the collection.</param>
86+
/// <param name="collection">The collection DTO object.</param>
87+
private async Task CreateCollectionAttributes(string databaseId, string collectionId, CollectionDTO collection)
7288
{
73-
74-
if (stringAttributes != null && stringAttributes.Count > 0)
75-
{
76-
await CreateAttribute(stringAttributes, databaseId, collectionId);
77-
}
78-
79-
if (emailAttributes != null && emailAttributes.Count > 0)
80-
{
81-
await CreateAttribute(emailAttributes, databaseId, collectionId);
82-
}
83-
84-
if (enumAttributes != null && enumAttributes.Count > 0)
85-
{
86-
await CreateAttribute(enumAttributes, databaseId, collectionId);
87-
}
88-
89-
if (iPAddressAttributes != null && iPAddressAttributes.Count > 0)
89+
var attributeMap = new Dictionary<Type, Func<Task>>
9090
{
91-
await CreateAttribute(iPAddressAttributes, databaseId, collectionId);
92-
}
93-
94-
if (uRLAttributes != null && uRLAttributes.Count > 0)
95-
{
96-
await CreateAttribute(uRLAttributes, databaseId, collectionId);
97-
}
98-
99-
if (integerAttributes != null && integerAttributes.Count > 0)
100-
{
101-
await CreateAttribute(integerAttributes, databaseId, collectionId);
102-
}
103-
104-
if (floatAttributes != null && floatAttributes.Count > 0)
105-
{
106-
await CreateAttribute(floatAttributes, databaseId, collectionId);
107-
}
108-
109-
if (booleanAttributes != null && booleanAttributes.Count > 0)
110-
{
111-
await CreateAttribute(booleanAttributes, databaseId, collectionId);
112-
}
113-
114-
if (datetimeAttributes != null && datetimeAttributes.Count > 0)
115-
{
116-
await CreateAttribute(datetimeAttributes, databaseId, collectionId);
117-
}
118-
119-
if (relationshipAttributes != null && relationshipAttributes.Count > 0)
91+
{ typeof(StringAttribute), () => CreateAttribute(collection.StringAttributes, databaseId, collectionId) },
92+
{ typeof(EmailAttribute), () => CreateAttribute(collection.EmailAttributes, databaseId, collectionId) },
93+
{ typeof(EnumAttribute), () => CreateAttribute(collection.EnumAttributes, databaseId, collectionId) },
94+
{ typeof(IPAddressAttribute), () => CreateAttribute(collection.IPAddressAttributes, databaseId, collectionId) },
95+
{ typeof(URLAttribute), () => CreateAttribute(collection.URLAttributes, databaseId, collectionId) },
96+
{ typeof(IntegerAttribute), () => CreateAttribute(collection.IntegerAttributes, databaseId, collectionId) },
97+
{ typeof(FloatAttribute), () => CreateAttribute(collection.FloatAttributes, databaseId, collectionId) },
98+
{ typeof(BooleanAttribute), () => CreateAttribute(collection.BooleanAttributes, databaseId, collectionId) },
99+
{ typeof(DatetimeAttribute), () => CreateAttribute(collection.DatetimeAttributes, databaseId, collectionId) },
100+
{ typeof(RelationshipAttribute), () => CreateAttribute(collection.RelationshipAttributes, databaseId, collectionId) },
101+
};
102+
103+
foreach (var attributeType in attributeMap.Keys)
120104
{
121-
await CreateAttribute(relationshipAttributes, databaseId, collectionId);
105+
// Get the properties of the collection object that are of type List<T>
106+
var listProperties = collection.GetType().GetProperties()
107+
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>));
108+
109+
// Filter the properties to only those where the generic type argument is the current attribute type
110+
var attributeListProperty = listProperties
111+
.Where(p => p.PropertyType.GenericTypeArguments[0] == attributeType)
112+
.FirstOrDefault();
113+
114+
// Get the value of the property (which is a list of attribute objects)
115+
var attributeList = attributeListProperty?.GetValue(collection) as IEnumerable<object>;
116+
117+
// If the list of attributes is not null or empty, create the attributes in the Appwrite database
118+
if (attributeList != null && attributeList.Any())
119+
{
120+
await attributeMap[attributeType]();
121+
}
122122
}
123123
}
124124

AppwriteConsole.App/Helpers/UserSelection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ public static bool GetBooleanAnswer(string message)
7979
}
8080
}
8181

82+
Console.ForegroundColor = ConsoleColor.Red;
8283
Console.WriteLine("Invalid input. Please enter 'y' for yes or 'n' for no.");
84+
Console.ResetColor();
8385
}
8486
}
8587
}

AppwriteConsole.App/Program.cs

Lines changed: 57 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,91 @@
11
using AppwriteClient;
22
using AppwriteClient.DTOs;
3-
using Microsoft.Extensions.Configuration;
4-
// using Newtonsoft.Json;
53
using System.Text.Json;
6-
using System.Text.Json.Serialization;
7-
using static AppwriteClient.DTOs.AttributeDTO;
84
using Helpers;
95

106
internal class Program
117
{
128
private static async Task Main(string[] args)
139
{
14-
bool skipConfirmation = CommandLineArgumentParser.HasArgument(args, "--skip-confirmation");
1510
string environmentName = CommandLineArgumentParser.GetArgumentValue(args, "--environment");
16-
1711
ConfigurationHelper configHelper = new ConfigurationHelper(environmentName);
12+
var databaseId = configHelper.GetSetting("DATABASE_ID");
1813

19-
if (!skipConfirmation)
20-
{
21-
var confirm = UserSelection.GetBooleanAnswer("Would you like to confirm application settings?");
22-
if (confirm)
23-
{
24-
configHelper.PrintSettings();
25-
}
26-
}
27-
else
14+
if (string.IsNullOrEmpty(databaseId))
2815
{
29-
Console.WriteLine("Skipping confirmation of application settings.");
16+
Console.WriteLine("Database ID is not set in the app settings.");
17+
Environment.Exit(1);
3018
}
3119

20+
var shouldConfirmSettings = UserSelection.GetBooleanAnswer("Would you like to confirm application settings?");
21+
22+
if (shouldConfirmSettings) configHelper.PrintSettings();
23+
3224
AppwriteService appwriteService = new AppwriteService(configHelper.GetSettings());
3325

34-
var databaseId = configHelper.GetSetting("DATABASE_ID");
26+
var databaseResponse = await appwriteService.GetDatabase(databaseId);
27+
bool databaseExists = databaseResponse.Result is not null;
3528

36-
var continueResponse = UserSelection.GetBooleanAnswer($"Retrieve database '{databaseId}'?");
29+
Console.WriteLine($"Database '{databaseId}' {(databaseExists ? "does" : "does not ")} exist");
3730

38-
if (!continueResponse)
39-
{
40-
Environment.Exit(0);
41-
}
31+
string databasePrompt = $"Would you like to {(databaseExists ? "reset" : "create")} the database from the local schema definition?";
32+
var shouldResetDatabase = UserSelection.GetBooleanAnswer(databasePrompt);
4233

43-
var database = await ExecuteOrExitOnError(appwriteService.GetDatabase(databaseId), $"Database '{databaseId}' does not exist.");
34+
if (shouldResetDatabase)
35+
{
36+
if (databaseExists)
37+
{
38+
Console.WriteLine("Deleting existing database...");
39+
await appwriteService.DeleteDatabase(databaseId);
40+
}
4441

45-
var collectionList = await ExecuteOrExitOnError(appwriteService.GetCollections(database));
42+
try
43+
{
44+
string json = File.ReadAllText(Path.Combine("DDL", "housingsearch.json"));
45+
SeedDatabaseDTO? seedDatabaseDTO = JsonSerializer.Deserialize<SeedDatabaseDTO>(json);
4646

47-
var collectionListCount = collectionList.Total;
47+
if (seedDatabaseDTO is null)
48+
{
49+
throw new JsonException();
50+
}
4851

49-
if (collectionListCount == 0)
50-
{
51-
Console.WriteLine($"Database '{databaseId}' is empty.");
52-
return;
53-
}
52+
Console.WriteLine("Creating database...");
53+
await appwriteService.CreateDatabase(new DatabaseDTO { DatabaseId = databaseId, Name = seedDatabaseDTO.DatabaseName });
5454

55-
Console.WriteLine($"Database '{databaseId}' exists and has {collectionListCount} collection(s).");
55+
Console.WriteLine("Creating collections...");
56+
await appwriteService.CreateCollections(databaseId, seedDatabaseDTO.Collections);
5657

57-
var operateOnCollectionResponse = UserSelection.GetBooleanAnswer("Would you like to operate on a collection?");
58+
databaseResponse = await appwriteService.GetDatabase(databaseId);
59+
databaseExists = databaseResponse.Result is not null;
60+
}
61+
catch (FileNotFoundException)
62+
{
63+
Console.WriteLine("The housing search JSON file is not valid or present within the 'DDL' directory.");
64+
Environment.Exit(1);
65+
}
66+
catch (JsonException)
67+
{
68+
Console.WriteLine("The housing search JSON file is not valid or present within the 'DDL' directory.");
69+
Environment.Exit(1);
70+
}
71+
}
5872

59-
if (!operateOnCollectionResponse)
73+
if (databaseExists)
6074
{
61-
return;
62-
}
75+
var collectionListResponse = await appwriteService.GetCollections(databaseResponse.Result);
76+
var collectionListCount = collectionListResponse.Result.Total;
6377

64-
var collectionNames = collectionList.Collections.Select(c => c.Name).ToList();
78+
Console.WriteLine($"Database '{databaseId}' has {collectionListCount} collection(s).");
6579

66-
var selectedCollection = UserSelection.GetSelection(collectionNames, "Select a collection:");
80+
var shouldOperateOnCollection = UserSelection.GetBooleanAnswer("Would you like to operate on a collection?");
6781

68-
Console.WriteLine($"You selected: {selectedCollection}");
82+
if (!shouldOperateOnCollection) Environment.Exit(0);
6983

70-
/*
71-
* TODO: Validate that a database doesnt already exist
72-
* TODO: Create Backup
73-
* TODO: Create Database
74-
* TODO: Create Collections and Attributes based on file schema
75-
* TODO: Add logging for process
76-
* TODO: Add required user prompts
77-
* TODO: Write unit test for code
78-
*/
79-
}
84+
var collectionNames = collectionListResponse.Result.Collections.Select(c => c.Name).ToList();
8085

81-
/// <summary>
82-
/// Executes a task and exits the application if the task returns an error.
83-
/// </summary>
84-
/// <typeparam name="T">The type of the result returned by the task.</typeparam>
85-
/// <param name="task">The task to be executed.</param>
86-
/// <param name="exitMessage">The message to be displayed if the task returns an error. If not provided, a default error message will be displayed.</param>
87-
/// <returns>The result of the task if it is executed successfully.</returns>
88-
private static async Task<T> ExecuteOrExitOnError<T>(Task<DatabaseResponse<T>> task, string exitMessage = "")
89-
{
90-
var response = await task;
91-
if (response.Error != null)
92-
{
93-
string message = string.IsNullOrEmpty(exitMessage) ? $"Error: {response.Error}" : exitMessage;
94-
Console.WriteLine(message);
95-
Environment.Exit(1);
86+
var selectedCollection = UserSelection.GetSelection(collectionNames, "Select a collection:");
87+
88+
Console.WriteLine($"You selected: {selectedCollection}");
9689
}
97-
return response.Result;
9890
}
99-
}
91+
}

0 commit comments

Comments
 (0)