Add job listing table (#15)

also:
- Add source to success story
- Move all IDs to one source file
- Update DbContext with all of the above
This commit is contained in:
2021-06-19 22:19:12 -04:00
parent b98d28adb4
commit cbd92e6491
13 changed files with 279 additions and 143 deletions

View File

@@ -53,7 +53,8 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
if (form.Id == "new")
{
var story = new Success(await SuccessId.Create(), CurrentCitizenId, _clock.GetCurrentInstant(),
form.FromHere, string.IsNullOrWhiteSpace(form.Story) ? null : new MarkdownString(form.Story));
form.FromHere, "profile",
string.IsNullOrWhiteSpace(form.Story) ? null : new MarkdownString(form.Story));
await _db.AddAsync(story);
}
else

View File

@@ -20,6 +20,12 @@ namespace JobsJobsJobs.Server.Data
public static readonly ValueConverter<ContinentId, string> ContinentIdConverter =
new(v => v.ToString(), v => ContinentId.Parse(v));
/// <summary>
/// Job Listing ID converter
/// </summary>
public static readonly ValueConverter<ListingId, string> ListingIdConverter =
new(v => v.ToString(), v => ListingId.Parse(v));
/// <summary>
/// Markdown converter
/// </summary>

View File

@@ -18,6 +18,11 @@ namespace JobsJobsJobs.Server.Data
/// </summary>
public DbSet<Continent> Continents { get; set; } = default!;
/// <summary>
/// Job listings
/// </summary>
public DbSet<Listing> Listings { get; set; } = default!;
/// <summary>
/// Employment profiles
/// </summary>
@@ -66,6 +71,33 @@ namespace JobsJobsJobs.Server.Data
m.Property(e => e.Name).HasColumnName("name").IsRequired().HasMaxLength(255);
});
modelBuilder.Entity<Listing>(m =>
{
m.ToTable("listing", "jjj").HasKey(e => e.Id);
m.Property(e => e.Id).HasColumnName("id").IsRequired().HasMaxLength(12)
.HasConversion(Converters.ListingIdConverter);
m.Property(e => e.CitizenId).HasColumnName("citizen_id").IsRequired().HasMaxLength(12)
.HasConversion(Converters.CitizenIdConverter);
m.Property(e => e.CreatedOn).HasColumnName("created_on").IsRequired();
m.Property(e => e.Title).HasColumnName("title").IsRequired().HasMaxLength(100);
m.Property(e => e.ContinentId).HasColumnName("continent_id").IsRequired().HasMaxLength(12)
.HasConversion(Converters.ContinentIdConverter);
m.Property(e => e.Region).HasColumnName("region").IsRequired().HasMaxLength(255);
m.Property(e => e.RemoteWork).HasColumnName("remote_work").IsRequired();
m.Property(e => e.IsExpired).HasColumnName("expired").IsRequired();
m.Property(e => e.UpdatedOn).HasColumnName("updated_on").IsRequired();
m.Property(e => e.Text).HasColumnName("listing").IsRequired()
.HasConversion(Converters.MarkdownStringConverter);
m.Property(e => e.NeededBy).HasColumnName("needed_by");
m.Property(e => e.WasFilledHere).HasColumnName("filled_here");
m.HasOne(e => e.Citizen)
.WithMany()
.HasForeignKey(e => e.CitizenId);
m.HasOne(e => e.Continent)
.WithMany()
.HasForeignKey(e => e.ContinentId);
});
modelBuilder.Entity<Profile>(m =>
{
m.ToTable("profile", "jjj").HasKey(e => e.Id);
@@ -111,6 +143,7 @@ namespace JobsJobsJobs.Server.Data
.HasConversion(Converters.CitizenIdConverter);
m.Property(e => e.RecordedOn).HasColumnName("recorded_on").IsRequired();
m.Property(e => e.FromHere).HasColumnName("from_here").IsRequired();
m.Property(e => e.Source).HasColumnName("source").IsRequired().HasMaxLength(7);
m.Property(e => e.Story).HasColumnName("story")
.HasConversion(Converters.OptionalMarkdownStringConverter);
});

View File

@@ -1,26 +0,0 @@
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// The ID of a user (a citizen of Gitmo Nation)
/// </summary>
public record CitizenId(ShortId Id)
{
/// <summary>
/// Create a new citizen ID
/// </summary>
/// <returns>A new citizen ID</returns>
public static async Task<CitizenId> Create() => new CitizenId(await ShortId.Create());
/// <summary>
/// Attempt to create a citizen ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The citizen ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid citizen ID</exception>
public static CitizenId Parse(string id) => new CitizenId(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
}

View File

@@ -1,26 +0,0 @@
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// The ID of a continent
/// </summary>
public record ContinentId(ShortId Id)
{
/// <summary>
/// Create a new continent ID
/// </summary>
/// <returns>A new continent ID</returns>
public static async Task<ContinentId> Create() => new ContinentId(await ShortId.Create());
/// <summary>
/// Attempt to create a continent ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The continent ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid continent ID</exception>
public static ContinentId Parse(string id) => new ContinentId(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// A short ID
/// </summary>
public record ShortId(string Id)
{
/// <summary>
/// Validate the format of the short ID
/// </summary>
private static readonly Regex ValidShortId =
new Regex("^[a-z0-9_-]{12}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Create a new short ID
/// </summary>
/// <returns>A new short ID</returns>
public static async Task<ShortId> Create() => new ShortId(await Nanoid.Nanoid.GenerateAsync(size: 12));
/// <summary>
/// Try to parse a string of text into a short ID
/// </summary>
/// <param name="text">The text of the prospective short ID</param>
/// <returns>The short ID</returns>
/// <exception cref="FormatException">If the format is not valid</exception>
public static ShortId Parse(string text)
{
if (text.Length == 12 && ValidShortId.IsMatch(text)) return new ShortId(text);
throw new FormatException($"The string {text} is not a valid short ID");
}
public override string ToString() => Id;
}
/// <summary>
/// The ID of a user (a citizen of Gitmo Nation)
/// </summary>
public record CitizenId(ShortId Id)
{
/// <summary>
/// Create a new citizen ID
/// </summary>
/// <returns>A new citizen ID</returns>
public static async Task<CitizenId> Create() => new CitizenId(await ShortId.Create());
/// <summary>
/// Attempt to create a citizen ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The citizen ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid citizen ID</exception>
public static CitizenId Parse(string id) => new(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
/// <summary>
/// The ID of a continent
/// </summary>
public record ContinentId(ShortId Id)
{
/// <summary>
/// Create a new continent ID
/// </summary>
/// <returns>A new continent ID</returns>
public static async Task<ContinentId> Create() => new ContinentId(await ShortId.Create());
/// <summary>
/// Attempt to create a continent ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The continent ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid continent ID</exception>
public static ContinentId Parse(string id) => new(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
/// <summary>
/// The ID of a job listing
/// </summary>
public record ListingId(ShortId Id)
{
/// <summary>
/// Create a new job listing ID
/// </summary>
/// <returns>A new job listing ID</returns>
public static async Task<ListingId> Create() => new ListingId(await ShortId.Create());
/// <summary>
/// Attempt to create a job listing ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The job listing ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid job listing ID</exception>
public static ListingId Parse(string id) => new(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
/// <summary>
/// The ID of a skill
/// </summary>
public record SkillId(ShortId Id)
{
/// <summary>
/// Create a new skill ID
/// </summary>
/// <returns>A new skill ID</returns>
public static async Task<SkillId> Create() => new SkillId(await ShortId.Create());
/// <summary>
/// Attempt to create a skill ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The skill ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid skill ID</exception>
public static SkillId Parse(string id) => new(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
/// <summary>
/// The ID of a success report
/// </summary>
public record SuccessId(ShortId Id)
{
/// <summary>
/// Create a new success report ID
/// </summary>
/// <returns>A new success report ID</returns>
public static async Task<SuccessId> Create() => new SuccessId(await ShortId.Create());
/// <summary>
/// Attempt to create a success report ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The success report ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid success report ID</exception>
public static SuccessId Parse(string id) => new(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// A job listing
/// </summary>
public record Listing(
ListingId Id,
CitizenId CitizenId,
DateTime CreatedOn,
string Title,
ContinentId ContinentId,
string Region,
bool RemoteWork,
bool IsExpired,
DateTime UpdatedOn,
MarkdownString Text,
DateTime? NeededBy,
bool? WasFilledHere)
{
/// <summary>
/// Navigation property for the citizen who created the job listing
/// </summary>
public Citizen? Citizen { get; set; }
/// <summary>
/// Navigation property for the continent
/// </summary>
public Continent? Continent { get; set; }
}
}

View File

@@ -1,38 +0,0 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// A short ID
/// </summary>
public record ShortId(string Id)
{
/// <summary>
/// Validate the format of the short ID
/// </summary>
private static readonly Regex ValidShortId =
new Regex("^[a-z0-9_-]{12}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Create a new short ID
/// </summary>
/// <returns>A new short ID</returns>
public static async Task<ShortId> Create() => new ShortId(await Nanoid.Nanoid.GenerateAsync(size: 12));
/// <summary>
/// Try to parse a string of text into a short ID
/// </summary>
/// <param name="text">The text of the prospective short ID</param>
/// <returns>The short ID</returns>
/// <exception cref="FormatException">If the format is not valid</exception>
public static ShortId Parse(string text)
{
if (text.Length == 12 && ValidShortId.IsMatch(text)) return new ShortId(text);
throw new FormatException($"The string {text} is not a valid short ID");
}
public override string ToString() => Id;
}
}

View File

@@ -1,26 +0,0 @@
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// The ID of a skill
/// </summary>
public record SkillId(ShortId Id)
{
/// <summary>
/// Create a new skill ID
/// </summary>
/// <returns>A new skill ID</returns>
public static async Task<SkillId> Create() => new SkillId(await ShortId.Create());
/// <summary>
/// Attempt to create a skill ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The skill ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid skill ID</exception>
public static SkillId Parse(string id) => new SkillId(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
}

View File

@@ -10,5 +10,6 @@ namespace JobsJobsJobs.Shared
CitizenId CitizenId,
Instant RecordedOn,
bool FromHere,
string Source,
MarkdownString? Story);
}

View File

@@ -1,26 +0,0 @@
using System.Threading.Tasks;
namespace JobsJobsJobs.Shared
{
/// <summary>
/// The ID of a success report
/// </summary>
public record SuccessId(ShortId Id)
{
/// <summary>
/// Create a new success report ID
/// </summary>
/// <returns>A new success report ID</returns>
public static async Task<SuccessId> Create() => new SuccessId(await ShortId.Create());
/// <summary>
/// Attempt to create a success report ID from a string
/// </summary>
/// <param name="id">The prospective ID</param>
/// <returns>The success report ID</returns>
/// <exception cref="System.FormatException">If the string is not a valid success report ID</exception>
public static SuccessId Parse(string id) => new SuccessId(ShortId.Parse(id));
public override string ToString() => Id.ToString();
}
}