This commit is contained in:
Fergal Moran
2020-07-27 03:22:43 +01:00
commit 82d17d979d
69 changed files with 47721 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
using System;
namespace DSS.Api.Data.Annotations {
[AttributeUsage(AttributeTargets.Property)]
public class SlugFieldAttribute : Attribute {
public string SourceField { get; }
public SlugFieldAttribute(string sourceField) {
SourceField = sourceField;
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DSS.Api.Data.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace DSS.Api.Data {
public class DSSDbContext : IdentityDbContext {
public DSSDbContext(DbContextOptions<DSSDbContext> options)
: base(options) {
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default) {
foreach (var entity in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
.Where(e => e.Entity is ISluggedEntity)
.Select(e => e.Entity as ISluggedEntity)
.Where(e => string.IsNullOrEmpty(e.Slug))) {
entity.Slug = entity.GenerateSlug(this);
}
}
public DbSet<AudioItem> AudioItems { get; set; }
public DbSet<Tag> Tags { get; set; }
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using DSS.Api.Data.Annotations;
using DSS.Api.Data.Models.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace DSS.Api.Data.Extensions {
public static class EntityExtesions {
private class ProxySluggedModel : ISluggedEntity {
public string Slug { get; set; }
}
public class GenerateSlugFailureException : Exception {
public GenerateSlugFailureException(string message) : base(message) { }
}
public static IEnumerable<T> ExecSQL<T>(this DbContext context, string query)
where T : class, ISluggedEntity, new() {
using (var command = context.Database.GetDbConnection().CreateCommand()) {
command.CommandText = query;
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using (var reader = command.ExecuteReader()) {
var result = reader.Select(r => new T {
Slug = r["Slug"] is DBNull ? string.Empty : r["Slug"].ToString()
});
return result.ToList();
}
}
}
public static string GenerateSlug(this IUniqueFieldEntity entity, DbContext context, ILogger logger = null) {
try {
var property = entity.GetType()
.GetProperties()
.FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(SlugFieldAttribute)));
if (property != null) {
var attribute = property
.GetCustomAttributes(typeof(SlugFieldAttribute), false)
.FirstOrDefault();
var t = entity.GetType();
var tableName = context.Model.FindEntityType(t).GetTableName();
if (!string.IsNullOrEmpty(tableName)) {
var sourceField = (attribute as SlugFieldAttribute)?.SourceField;
if (string.IsNullOrEmpty(sourceField)) {
logger?.LogError($"Error slugifying - Entry title is blank, cannot slugify");
// need to throw here, shouldn't save without slug
throw new GenerateSlugFailureException("Entry title is blank, cannot slugify");
}
var slugSource = entity.GetType()
.GetProperty(sourceField)
?.GetValue(entity, null)
?.ToString() ?? string.Empty;
var source = context.ExecSQL<ProxySluggedModel>($"SELECT Slug FROM {tableName}")
.Select(m => m.Slug);
return slugSource.Slugify(source);
}
}
} catch (Exception ex) {
logger?.LogError($"Error slugifying {entity.GetType().Name} - {ex.Message}");
// need to throw here, shouldn't save without slug
throw new GenerateSlugFailureException(ex.Message);
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,23 @@
using DSS.Api.Data.Annotations;
using DSS.Api.Data.Models.Interfaces;
using Microsoft.AspNetCore.Identity;
namespace DSS.Api.Data.Models {
public static class EntityExtesions {
}
public class ApplicationUser : IdentityUser, ISluggedEntity {
// Extended Properties
public string FirstName { get; set; }
public string LastName { get; set; }
public long? FacebookId { get; set; }
public string TwitterHandle { get; set; }
public string PictureUrl { get; set; }
[SlugField(sourceField: "FullName")]
public string Slug { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using DSS.Api.Data.Annotations;
namespace DSS.Api.Data.Models
{
public class AudioItem : BaseEntity
{
public string Title { get; set; }
public string Description { get; set; }
public virtual List<Tag> Tags { get; set; }
public ApplicationUser User { get; set; }
[SlugField(sourceField: "Title")]
public string Slug { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using DSS.Api.Data.Interfaces;
namespace DSS.Api.Data.Models
{
public class BaseEntity : IEntity
{
public Guid Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime UpdateDate { get; set; } = DateTime.UtcNow;
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace DSS.Api.Data.Models.Interfaces
{
public interface IEntity
{
Guid Id { get; set; }
DateTime CreateDate { get; set; }
DateTime UpdateDate { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace DSS.Api.Data.Models.Interfaces
{
public interface ISluggedEntity : IUniqueFieldEntity
{
string Slug { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace DSS.Api.Data.Models.Interfaces
{
public interface IUniqueFieldEntity
{
}
}

View File

@@ -0,0 +1,7 @@
namespace DSS.Api.Data.Models
{
public class Tag : BaseEntity
{
public string Name { get; set; }
}
}