diff --git a/zero.Sqlite/Extensions/SqlExpressionExtensions.cs b/zero.Sqlite/Extensions/SqlExpressionExtensions.cs new file mode 100644 index 00000000..93c24977 --- /dev/null +++ b/zero.Sqlite/Extensions/SqlExpressionExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using ServiceStack.OrmLite; +using zero.Extensions; + +namespace zero.Sqlite; + +public static class SqlExpressionExtensions +{ + public static SqlExpression Paging(this SqlExpression source, int pageNumber, int pageSize) + { + pageNumber = pageNumber.Limit(1, 10_000_000); + pageSize = pageSize.Limit(1, 1_000); + + if (pageNumber <= 0 || pageSize <= 0) + { + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); + } + + return source.Skip((pageNumber - 1) * pageSize).Take(pageSize); + } + + public static SqlExpression WhereIf(this SqlExpression source, Expression> predicate, bool condition, Expression> elsePredicate = null) + { + if (condition) + { + return source.Where(predicate); + } + + return elsePredicate != null ? source.Where(elsePredicate) : source; + } +} diff --git a/zero.Sqlite/Operations/DbOperations.cs b/zero.Sqlite/Operations/DbOperations.cs new file mode 100644 index 00000000..ae30704e --- /dev/null +++ b/zero.Sqlite/Operations/DbOperations.cs @@ -0,0 +1,102 @@ +using System; +using FluentValidation.Results; +using Microsoft.Extensions.DependencyInjection; +using System.Linq.Expressions; +using System.Security.Claims; +using System.Threading.Tasks; +using zero.Context; +using zero.Models; +using zero.Utils; +using zero.Validation; + +namespace zero.Sqlite; + +public partial class DbOperations : IDbOperations +{ + protected IZeroContext Context { get; private set; } + + protected FlavorOptions Flavors { get; private set; } + + protected IServiceProvider Services { get; private set; } + + + public DbOperations(StoreContext context) + { + Context = context.Context; + Services = context.Services; + Flavors = context.Options.For(); + } + + + /// + public Task GenerateId(T model) where T : ZeroIdEntity + { + return Task.FromResult(IdGenerator.Create(12)); + } + + + /// + public T AutoSetIds(T model) + { + return IdGenerator.Autofill(model); + } + + + /// + public T PrepareForSave(T model) where T : ZeroIdEntity + { + // set IDs + AutoSetIds(model); + + if (model is not ZeroEntity zeroModel) + { + return model; + } + + // set default properties + if (zeroModel.CreatedDate == default) + { + zeroModel.CreatedDate = DateTimeOffset.Now; + } + + // update name alias and last modified + zeroModel.Alias = Safenames.Alias(zeroModel.Name); + zeroModel.LastModifiedDate = DateTimeOffset.Now; + zeroModel.Hash ??= IdGenerator.Create(); + + return model; + } + + + /// + public async Task Validate(T model) where T : ZeroIdEntity, new() + { + IZeroMergedValidator validator = Services.GetService>(); + + if (validator == null) + { + return new(); + } + + return await validator.ValidateAsync(model); + } +} + + +public interface IDbOperations +{ + /// + /// Generate model Id by using configured document store conventions + /// + Task GenerateId(T model) where T : ZeroIdEntity; + + /// + /// Generate values for all properties (incl. nested) which contain the [GenerateId] attribute + /// + T AutoSetIds(T model); + + /// + /// Automatically fill base properties of a ZeroEntity + /// + T PrepareForSave(T model) where T : ZeroIdEntity; +} \ No newline at end of file diff --git a/zero.Sqlite/Operations/StoreContext.cs b/zero.Sqlite/Operations/StoreContext.cs new file mode 100644 index 00000000..2b926213 --- /dev/null +++ b/zero.Sqlite/Operations/StoreContext.cs @@ -0,0 +1,26 @@ +using System; +using zero.Communication; +using zero.Configuration; +using zero.Context; + +namespace zero.Sqlite; + +public class StoreContext +{ + public IZeroContext Context { get; private set; } + + public IZeroOptions Options { get; private set; } + + public IServiceProvider Services { get; private set; } + + public IMessageAggregator Messages { get; private set; } + + + public StoreContext(IZeroContext context, IServiceProvider serviceProvider, IMessageAggregator messages) + { + Options = context.Options; + Context = context; + Services = serviceProvider; + Messages = messages; + } +} \ No newline at end of file diff --git a/zero.Sqlite/SqliteOptions.cs b/zero.Sqlite/SqliteOptions.cs new file mode 100644 index 00000000..0c52c95c --- /dev/null +++ b/zero.Sqlite/SqliteOptions.cs @@ -0,0 +1,6 @@ +namespace zero.Sqlite; + +public class SqliteOptions +{ + public string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/zero.Sqlite/ZeroSqliteModule.cs b/zero.Sqlite/ZeroSqliteModule.cs new file mode 100644 index 00000000..410b648d --- /dev/null +++ b/zero.Sqlite/ZeroSqliteModule.cs @@ -0,0 +1,52 @@ +using System; +using System.Data; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using ServiceStack.Data; +using ServiceStack.OrmLite; +using zero.Configuration; +using zero.Models; +using zero.Modules; + +namespace zero.Sqlite; + +public static class ZeroBuilderExtensions +{ + public static ZeroBuilder AddSqlite(this ZeroBuilder builder) + { + builder.AddModule(); + return builder; + } +} + +internal class ZeroSqliteModule : ZeroModule +{ + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(CreateDbConnectionFactory); + services.AddScoped(CreateDbConnection); + services.AddScoped(); + services.AddScoped(); + services.AddOptions(); + services.AddOptions().Bind(configuration.GetSection("Zero:Sqlite")); + services.ConfigureOptions(); + } + + + protected IDbConnectionFactory CreateDbConnectionFactory(IServiceProvider services) + { + IZeroOptions options = services.GetService(); + SqliteOptions ravenOptions = options.For(); + return new OrmLiteConnectionFactory(ravenOptions.ConnectionString, SqliteDialect.Provider); + } + + + protected IDbConnection CreateDbConnection(IServiceProvider services) + { + IDbConnectionFactory factory = services.GetService(); + IDbConnection db = factory.CreateDbConnection(); + db.Open(); + //db.CreateTableIfNotExists(); + return db; + } +} \ No newline at end of file diff --git a/zero.Sqlite/zero.Sqlite.csproj b/zero.Sqlite/zero.Sqlite.csproj new file mode 100644 index 00000000..77d4e6ff --- /dev/null +++ b/zero.Sqlite/zero.Sqlite.csproj @@ -0,0 +1,21 @@ + + + + zero.Sqlite + 1.0.0 + net8.0 + true + zero.Sqlite + embedded + + + + + + + + + + + + \ No newline at end of file diff --git a/zero.sln b/zero.sln index 824d58ac..58c1cce9 100644 --- a/zero.sln +++ b/zero.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero", "zero\zero.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Raven", "zero.Raven\zero.Raven.csproj", "{DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zero.Sqlite", "zero.Sqlite\zero.Sqlite.csproj", "{A57D88BC-952F-4311-B474-26FBF0FA957E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}.Release|Any CPU.Build.0 = Release|Any CPU + {A57D88BC-952F-4311-B474-26FBF0FA957E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A57D88BC-952F-4311-B474-26FBF0FA957E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A57D88BC-952F-4311-B474-26FBF0FA957E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A57D88BC-952F-4311-B474-26FBF0FA957E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE