diff --git a/src/JobsJobsJobs.sln b/src/JobsJobsJobs.sln index 74c41b1..461c026 100644 --- a/src/JobsJobsJobs.sln +++ b/src/JobsJobsJobs.sln @@ -17,8 +17,6 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Domain", "JobsJobsJobs\Doma EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Api", "JobsJobsJobs\Api\Api.fsproj", "{8F5A3D1E-562B-4F27-9787-6CB14B35E69E}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DataMigrate", "JobsJobsJobs\DataMigrate\DataMigrate.fsproj", "{C5774E4F-2930-4B64-8407-77BF7EB79F39}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,10 +31,6 @@ Global {8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F5A3D1E-562B-4F27-9787-6CB14B35E69E}.Release|Any CPU.Build.0 = Release|Any CPU - {C5774E4F-2930-4B64-8407-77BF7EB79F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C5774E4F-2930-4B64-8407-77BF7EB79F39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C5774E4F-2930-4B64-8407-77BF7EB79F39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C5774E4F-2930-4B64-8407-77BF7EB79F39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -47,6 +41,5 @@ Global GlobalSection(NestedProjects) = preSolution {C81278DA-DA97-4E55-AB39-4B88565B615D} = {FA833B24-B8F6-4CE6-A044-99257EAC02FF} {8F5A3D1E-562B-4F27-9787-6CB14B35E69E} = {FA833B24-B8F6-4CE6-A044-99257EAC02FF} - {C5774E4F-2930-4B64-8407-77BF7EB79F39} = {FA833B24-B8F6-4CE6-A044-99257EAC02FF} EndGlobalSection EndGlobal diff --git a/src/JobsJobsJobs/DataMigrate/DataContext.fs b/src/JobsJobsJobs/DataMigrate/DataContext.fs deleted file mode 100644 index 7a0b729..0000000 --- a/src/JobsJobsJobs/DataMigrate/DataContext.fs +++ /dev/null @@ -1,121 +0,0 @@ -namespace JobsJobsJobs.DataMigrate - -/// Converters used to translate between database and domain types -module Converters = - open JobsJobsJobs.Shared - open Microsoft.EntityFrameworkCore.Storage.ValueConversion - - /// Citizen ID converter - let CitizenIdConverter = ValueConverter ((fun v -> string v), fun v -> CitizenId.Parse v) - - /// Continent ID converter - let ContinentIdConverter = ValueConverter ((fun v -> string v), fun v -> ContinentId.Parse v) - - /// Markdown converter - let MarkdownStringConverter = ValueConverter ((fun v -> v.Text), fun v -> MarkdownString v) - - /// Markdown converter for possibly-null values - let OptionalMarkdownStringConverter : ValueConverter = - ValueConverter - ((fun v -> match v with null -> null | _ -> v.Text), fun v -> match v with null -> null | _ -> MarkdownString v) - - /// Skill ID converter - let SkillIdConverter = ValueConverter ((fun v -> string v), fun v -> SkillId.Parse v) - - /// Success ID converter - let SuccessIdConverter = ValueConverter ((fun v -> string v), fun v -> SuccessId.Parse v) - - -open JobsJobsJobs.Shared -open Microsoft.EntityFrameworkCore -open System.Collections.Generic - -/// Data context for Jobs, Jobs, Jobs -type JobsDbContext (options : DbContextOptions) = - inherit DbContext (options) - - /// Citizens (users known to us) - member val Citizens : DbSet = Unchecked.defaultof> with get, set - - /// Continents (large land masses - 7 of them!) - member val Continents : DbSet = Unchecked.defaultof> with get, set - - /// Employment profiles - member val Profiles : DbSet = Unchecked.defaultof> with get, set - - /// Skills held by citizens of Gitmo Nation - member val Skills : DbSet = Unchecked.defaultof> with get, set - - /// Success stories from the site - member val Successes : DbSet = Unchecked.defaultof> with get, set - - override _.OnModelCreating (mb : ModelBuilder) = - base.OnModelCreating(mb) - - mb.Entity(fun m -> - m.ToTable("citizen", "jjj").HasKey(fun e -> e.Id :> obj) |> ignore - m.Property(fun e -> e.Id).HasColumnName("id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.CitizenIdConverter) |> ignore - m.Property(fun e -> e.NaUser).HasColumnName("na_user").IsRequired().HasMaxLength(50) |> ignore - m.Property(fun e -> e.DisplayName).HasColumnName("display_name").HasMaxLength(255) |> ignore - m.Property(fun e -> e.ProfileUrl).HasColumnName("profile_url").IsRequired().HasMaxLength(1_024) |> ignore - m.Property(fun e -> e.JoinedOn).HasColumnName("joined_on").IsRequired() |> ignore - m.Property(fun e -> e.LastSeenOn).HasColumnName("last_seen_on").IsRequired() |> ignore - m.Property(fun e -> e.RealName).HasColumnName("real_name").HasMaxLength(255) |> ignore - m.HasIndex(fun e -> e.NaUser :> obj).IsUnique() |> ignore - m.Ignore(fun e -> e.CitizenName :> obj) |> ignore) - |> ignore - - mb.Entity(fun m -> - m.ToTable("continent", "jjj").HasKey(fun e -> e.Id :> obj) |> ignore - m.Property(fun e -> e.Id).HasColumnName("id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.ContinentIdConverter) |> ignore - m.Property(fun e -> e.Name).HasColumnName("name").IsRequired().HasMaxLength(255) |> ignore) - |> ignore - - mb.Entity(fun m -> - m.ToTable("profile", "jjj").HasKey(fun e -> e.Id :> obj) |> ignore - m.Property(fun e -> e.Id).HasColumnName("citizen_id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.CitizenIdConverter) |> ignore - m.Property(fun e -> e.SeekingEmployment).HasColumnName("seeking_employment").IsRequired() |> ignore - m.Property(fun e -> e.IsPublic).HasColumnName("is_public").IsRequired() |> ignore - m.Property(fun e -> e.ContinentId).HasColumnName("continent_id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.ContinentIdConverter) |> ignore - m.Property(fun e -> e.Region).HasColumnName("region").IsRequired().HasMaxLength(255) |> ignore - m.Property(fun e -> e.RemoteWork).HasColumnName("remote_work").IsRequired() |> ignore - m.Property(fun e -> e.FullTime).HasColumnName("full_time").IsRequired() |> ignore - m.Property(fun e -> e.Biography).HasColumnName("biography").IsRequired() - .HasConversion(Converters.MarkdownStringConverter) |> ignore - m.Property(fun e -> e.LastUpdatedOn).HasColumnName("last_updated_on").IsRequired() |> ignore - m.Property(fun e -> e.Experience).HasColumnName("experience") - .HasConversion(Converters.OptionalMarkdownStringConverter) |> ignore - m.HasOne(fun e -> e.Continent :> obj) - .WithMany() - .HasForeignKey(fun e -> e.ContinentId :> obj) |> ignore - m.HasMany(fun e -> e.Skills :> IEnumerable) - .WithOne() - .HasForeignKey(fun e -> e.CitizenId :> obj) |> ignore) - |> ignore - - mb.Entity(fun m -> - m.ToTable("skill", "jjj").HasKey(fun e -> e.Id :> obj) |> ignore - m.Property(fun e -> e.Id).HasColumnName("id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.SkillIdConverter) |> ignore - m.Property(fun e -> e.CitizenId).HasColumnName("citizen_id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.CitizenIdConverter) |> ignore - m.Property(fun e -> e.Description).HasColumnName("skill").IsRequired().HasMaxLength(100) |> ignore - m.Property(fun e -> e.Notes).HasColumnName("notes").HasMaxLength(100) |> ignore) - |> ignore - - mb.Entity(fun m -> - m.ToTable("success", "jjj").HasKey(fun e -> e.Id :> obj) |> ignore - m.Property(fun e -> e.Id).HasColumnName("id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.SuccessIdConverter) |> ignore - m.Property(fun e -> e.CitizenId).HasColumnName("citizen_id").IsRequired().HasMaxLength(12) - .HasConversion(Converters.CitizenIdConverter) |> ignore - m.Property(fun e -> e.RecordedOn).HasColumnName("recorded_on").IsRequired() |> ignore - m.Property(fun e -> e.FromHere).HasColumnName("from_here").IsRequired() |> ignore - // m.Property(fun e -> e.Source).HasColumnName("source").IsRequired().HasMaxLength(7); - m.Property(fun e -> e.Story).HasColumnName("story") - .HasConversion(Converters.OptionalMarkdownStringConverter) |> ignore) - |> ignore diff --git a/src/JobsJobsJobs/DataMigrate/DataMigrate.fsproj b/src/JobsJobsJobs/DataMigrate/DataMigrate.fsproj deleted file mode 100644 index 6eb672e..0000000 --- a/src/JobsJobsJobs/DataMigrate/DataMigrate.fsproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - net5.0 - 3390;$(WarnOn) - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/JobsJobsJobs/DataMigrate/Program.fs b/src/JobsJobsJobs/DataMigrate/Program.fs deleted file mode 100644 index 8bbb0ae..0000000 --- a/src/JobsJobsJobs/DataMigrate/Program.fs +++ /dev/null @@ -1,142 +0,0 @@ -// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp - -open FSharp.Control.Tasks -open System -open System.Linq -open JobsJobsJobs.Api.Data -open JobsJobsJobs.Domain -open JobsJobsJobs.DataMigrate -open Microsoft.EntityFrameworkCore -open Microsoft.Extensions.Hosting -open Microsoft.Extensions.DependencyInjection -open Microsoft.Extensions.Logging -open RethinkDb.Driver.Net - -/// Create the host (reads configuration and initializes both databases) -let createHostBuilder argv = - Host.CreateDefaultBuilder(argv) - .ConfigureServices( - Action ( - fun hostCtx svcs -> - svcs.AddSingleton hostCtx.Configuration |> ignore - // PostgreSQL via EF Core - svcs.AddDbContext(fun options -> - options.UseNpgsql(hostCtx.Configuration.["ConnectionStrings:JobsDb"], - fun o -> o.UseNodaTime() |> ignore) - |> ignore) - |> ignore - // RethinkDB - let cfg = hostCtx.Configuration.GetSection "Rethink" - let log = svcs.BuildServiceProvider().GetRequiredService().CreateLogger "Data" - let conn = Startup.createConnection cfg log - svcs.AddSingleton conn |> ignore - Startup.establishEnvironment cfg log conn |> awaitIgnore - )) - .Build() - -[] -let main argv = - let host = createHostBuilder argv - let r = RethinkDb.Driver.RethinkDB.R - - printfn "0) Connecting to databases..." - - use db = host.Services.GetRequiredService () - let conn = host.Services.GetRequiredService () - - task { - - printfn "1) Migrating continents..." - - let mutable continentXref = Map.empty - let! continents = db.Continents.AsNoTracking().ToListAsync () - let reContinents = - continents - |> Seq.map (fun c -> - let reContinentId = ContinentId.create () - continentXref <- continentXref.Add (string c.Id, reContinentId) - let it : Types.Continent = { - id = reContinentId - name = c.Name - } - it) - |> List.ofSeq - let! _ = r.Table(Table.Continent).Insert(reContinents).RunWriteAsync conn - - printfn "2) Migrating citizens..." - - let mutable citizenXref = Map.empty - let! citizens = db.Citizens.AsNoTracking().ToListAsync () - let reCitizens = - citizens - |> Seq.map (fun c -> - let reCitizenId = CitizenId.create () - citizenXref <- citizenXref.Add (string c.Id, reCitizenId) - let it : Types.Citizen = { - id = reCitizenId - naUser = c.NaUser - displayName = Option.ofObj c.DisplayName - realName = Option.ofObj c.RealName - profileUrl = c.ProfileUrl - joinedOn = c.JoinedOn - lastSeenOn = c.LastSeenOn - } - it) - let! _ = r.Table(Table.Citizen).Insert(reCitizens).RunWriteAsync conn - - printfn "3) Migrating profiles and skills..." - - let! profiles = db.Profiles.AsNoTracking().ToListAsync () - let reProfiles = - profiles - |> Seq.map (fun p -> - let skills = db.Skills.AsNoTracking().Where(fun s -> s.CitizenId = p.Id).ToList () - let reSkills = - skills - |> Seq.map (fun skill -> - let it : Types.Skill = { - id = SkillId.create() - description = skill.Description - notes = Option.ofObj skill.Notes - } - it) - |> List.ofSeq - let it : Types.Profile = { - id = citizenXref.[string p.Id] - seekingEmployment = p.SeekingEmployment - isPublic = p.IsPublic - continentId = continentXref.[string p.ContinentId] - region = p.Region - remoteWork = p.RemoteWork - fullTime = p.FullTime - biography = Types.Text p.Biography.Text - lastUpdatedOn = p.LastUpdatedOn - experience = match p.Experience with null -> None | x -> (Types.Text >> Some) x.Text - skills = reSkills - } - it) - let! _ = r.Table(Table.Profile).Insert(reProfiles).RunWriteAsync conn - - printfn "4) Migrating success stories..." - - let! successes = db.Successes.AsNoTracking().ToListAsync () - let reSuccesses = - successes - |> Seq.map (fun s -> - let it : Types.Success = { - id = SuccessId.create () - citizenId = citizenXref.[string s.CitizenId] - recordedOn = s.RecordedOn - fromHere = s.FromHere - source = "profile" - story = match s.Story with null -> None | x -> (Types.Text >> Some) x.Text - } - it) - let! _ = r.Table(Table.Success).Insert(reSuccesses).RunWriteAsync conn - () - } - |> awaitIgnore - - printfn "Migration complete" - - 0 diff --git a/src/JobsJobsJobs/Shared/Api/Count.cs b/src/JobsJobsJobs/Shared/Api/Count.cs deleted file mode 100644 index 4405eeb..0000000 --- a/src/JobsJobsJobs/Shared/Api/Count.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JobsJobsJobs.Shared.Api -{ - /// - /// A transport mechanism to send counts across the wire via JSON - /// - public record Count(int Value); -} diff --git a/src/JobsJobsJobs/Shared/Api/LogOnSuccess.cs b/src/JobsJobsJobs/Shared/Api/LogOnSuccess.cs deleted file mode 100644 index e5198b7..0000000 --- a/src/JobsJobsJobs/Shared/Api/LogOnSuccess.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace JobsJobsJobs.Shared.Api -{ - /// - /// A successful log on; returns JWT, citizen ID, and display name - /// - public record LogOnSuccess(string Jwt, string Id, string Name) - { - /// - /// The ID return value as a citizen ID - /// - public CitizenId CitizenId => CitizenId.Parse(Id); - } -} diff --git a/src/JobsJobsJobs/Shared/Api/ProfileForm.cs b/src/JobsJobsJobs/Shared/Api/ProfileForm.cs deleted file mode 100644 index 12788a3..0000000 --- a/src/JobsJobsJobs/Shared/Api/ProfileForm.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace JobsJobsJobs.Shared.Api -{ - /// - /// The data required to update a profile - /// - public class ProfileForm - { - /// - /// Whether the citizen to whom this profile belongs is actively seeking employment - /// - public bool IsSeekingEmployment { get; set; } - - /// - /// Whether this profile should appear in the public search - /// - public bool IsPublic { get; set; } - - /// - /// The user's real name - /// - [StringLength(255)] - public string RealName { get; set; } = ""; - - /// - /// The ID of the continent on which the citizen is located - /// - [Required] - [StringLength(12, MinimumLength = 1)] - [Display(Name = "Continent")] - public string ContinentId { get; set; } = ""; - - /// - /// The area within that continent where the citizen is located - /// - [Required] - [StringLength(255)] - public string Region { get; set; } = ""; - - /// - /// If the citizen is available for remote work - /// - public bool RemoteWork { get; set; } - - /// - /// If the citizen is seeking full-time employment - /// - public bool FullTime { get; set; } - - /// - /// The user's professional biography - /// - [Required] - public string Biography { get; set; } = ""; - - /// - /// The user's past experience - /// - public string Experience { get; set; } = ""; - - /// - /// The skills for the user - /// - public ICollection Skills { get; set; } = new List(); - - /// - /// Create an instance of this form from the given profile - /// - /// The profile off which this form will be based - /// The profile form, popluated with values from the given profile - public static ProfileForm FromProfile(Profile profile) => - new ProfileForm - { - IsSeekingEmployment = profile.SeekingEmployment, - IsPublic = profile.IsPublic, - ContinentId = profile.ContinentId.ToString(), - Region = profile.Region, - RemoteWork = profile.RemoteWork, - FullTime = profile.FullTime, - Biography = profile.Biography.Text, - Experience = profile.Experience?.Text ?? "", - Skills = profile.Skills.Select(s => new SkillForm - { - Id = s.Id.ToString(), - Description = s.Description, - Notes = s.Notes - }).ToList() - }; - } -} diff --git a/src/JobsJobsJobs/Shared/Api/ProfileSearch.cs b/src/JobsJobsJobs/Shared/Api/ProfileSearch.cs deleted file mode 100644 index 7d25d55..0000000 --- a/src/JobsJobsJobs/Shared/Api/ProfileSearch.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace JobsJobsJobs.Shared.Api -{ - /// - /// The various ways profiles can be searched - /// - public class ProfileSearch - { - /// - /// Retrieve citizens from this continent - /// - public string? ContinentId { get; set; } - - /// - /// Text for a search within a citizen's skills - /// - public string? Skill { get; set; } - - /// - /// Text for a search with a citizen's professional biography and experience fields - /// - public string? BioExperience { get; set; } - - /// - /// Whether to retrieve citizens who do or do not want remote work - /// - public string RemoteWork { get; set; } = ""; - - /// - /// Is the search empty? - /// - public bool IsEmptySearch => - string.IsNullOrEmpty(ContinentId) - && string.IsNullOrEmpty(Skill) - && string.IsNullOrEmpty(BioExperience) - && string.IsNullOrEmpty(RemoteWork); - } -} diff --git a/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs b/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs deleted file mode 100644 index 390bc96..0000000 --- a/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NodaTime; - -namespace JobsJobsJobs.Shared.Api -{ - /// - /// A user matching the profile search - /// - public record ProfileSearchResult( - CitizenId CitizenId, - string DisplayName, - bool SeekingEmployment, - bool RemoteWork, - bool FullTime, - Instant LastUpdated); -} diff --git a/src/JobsJobsJobs/Shared/Api/PublicSearch.cs b/src/JobsJobsJobs/Shared/Api/PublicSearch.cs deleted file mode 100644 index 91e4e01..0000000 --- a/src/JobsJobsJobs/Shared/Api/PublicSearch.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace JobsJobsJobs.Shared.Api -{ - /// - /// The parameters for a public job search - /// - public class PublicSearch - { - /// - /// Retrieve citizens from this continent - /// - public string? ContinentId { get; set; } - - /// - /// Retrieve citizens from this region - /// - public string? Region { get; set; } - - /// - /// Text for a search within a citizen's skills - /// - public string? Skill { get; set; } - - /// - /// Whether to retrieve citizens who do or do not want remote work - /// - public string RemoteWork { get; set; } = ""; - - /// - /// Is the search empty? - /// - public bool IsEmptySearch => - string.IsNullOrEmpty(ContinentId) - && string.IsNullOrEmpty(Region) - && string.IsNullOrEmpty(Skill) - && string.IsNullOrEmpty(RemoteWork); - } -} diff --git a/src/JobsJobsJobs/Shared/Api/PublicSearchResult.cs b/src/JobsJobsJobs/Shared/Api/PublicSearchResult.cs deleted file mode 100644 index ffdcbfa..0000000 --- a/src/JobsJobsJobs/Shared/Api/PublicSearchResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace JobsJobsJobs.Shared.Api -{ - /// - /// A public profile search result - /// - public record PublicSearchResult( - string Continent, - string Region, - bool RemoteWork, - IEnumerable Skills); -} diff --git a/src/JobsJobsJobs/Shared/Api/SkillForm.cs b/src/JobsJobsJobs/Shared/Api/SkillForm.cs deleted file mode 100644 index 46ec898..0000000 --- a/src/JobsJobsJobs/Shared/Api/SkillForm.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace JobsJobsJobs.Shared.Api -{ - /// - /// The fields required for a skill - /// - public class SkillForm - { - /// - /// The ID of this skill - /// - [Required] - public string Id { get; set; } = ""; - - /// - /// The description of the skill - /// - [StringLength(100)] - public string Description { get; set; } = ""; - - /// - /// Notes regarding the skill - /// - [StringLength(100)] - public string? Notes { get; set; } = null; - } -} diff --git a/src/JobsJobsJobs/Shared/Api/StoryEntry.cs b/src/JobsJobsJobs/Shared/Api/StoryEntry.cs deleted file mode 100644 index f2b535e..0000000 --- a/src/JobsJobsJobs/Shared/Api/StoryEntry.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NodaTime; - -namespace JobsJobsJobs.Shared.Api -{ - /// - /// An entry in the list of success stories - /// - public record StoryEntry( - SuccessId Id, - CitizenId CitizenId, - string CitizenName, - Instant RecordedOn, - bool FromHere, - bool HasStory); -} diff --git a/src/JobsJobsJobs/Shared/Api/StoryForm.cs b/src/JobsJobsJobs/Shared/Api/StoryForm.cs deleted file mode 100644 index 1d85016..0000000 --- a/src/JobsJobsJobs/Shared/Api/StoryForm.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace JobsJobsJobs.Shared.Api -{ - /// - /// The data required to provide a success story - /// - public class StoryForm - { - /// - /// The ID of this story - /// - public string Id { get; set; } = "new"; - - /// - /// Whether the employment was obtained from Jobs, Jobs, Jobs - /// - public bool FromHere { get; set; } = false; - - /// - /// The success story - /// - public string Story { get; set; } = ""; - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/Citizen.cs b/src/JobsJobsJobs/Shared/Domain/Citizen.cs deleted file mode 100644 index d8ced36..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Citizen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NodaTime; - -namespace JobsJobsJobs.Shared -{ - /// - /// A user of Jobs, Jobs, Jobs - /// - public record Citizen( - CitizenId Id, - string NaUser, - string? DisplayName, - string? RealName, - string ProfileUrl, - Instant JoinedOn, - Instant LastSeenOn) - { - /// - /// The user's name by which they should be known - /// - public string CitizenName => RealName ?? DisplayName ?? NaUser; - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/Continent.cs b/src/JobsJobsJobs/Shared/Domain/Continent.cs deleted file mode 100644 index 2be6cf2..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Continent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JobsJobsJobs.Shared -{ - /// - /// A continent - /// - public record Continent(ContinentId Id, string Name); -} diff --git a/src/JobsJobsJobs/Shared/Domain/Ids.cs b/src/JobsJobsJobs/Shared/Domain/Ids.cs deleted file mode 100644 index 4c573b2..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Ids.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace JobsJobsJobs.Shared -{ - /// - /// A short ID - /// - public record ShortId(string Id) - { - /// - /// Validate the format of the short ID - /// - private static readonly Regex ValidShortId = - new Regex("^[a-z0-9_-]{12}", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - /// - /// Create a new short ID - /// - /// A new short ID - public static async Task Create() => new ShortId(await Nanoid.Nanoid.GenerateAsync(size: 12)); - - /// - /// Try to parse a string of text into a short ID - /// - /// The text of the prospective short ID - /// The short ID - /// If the format is not valid - 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; - } - - /// - /// The ID of a user (a citizen of Gitmo Nation) - /// - public record CitizenId(ShortId Id) - { - /// - /// Create a new citizen ID - /// - /// A new citizen ID - public static async Task Create() => new CitizenId(await ShortId.Create()); - - /// - /// Attempt to create a citizen ID from a string - /// - /// The prospective ID - /// The citizen ID - /// If the string is not a valid citizen ID - public static CitizenId Parse(string id) => new(ShortId.Parse(id)); - - public override string ToString() => Id.ToString(); - } - - /// - /// The ID of a continent - /// - public record ContinentId(ShortId Id) - { - /// - /// Create a new continent ID - /// - /// A new continent ID - public static async Task Create() => new ContinentId(await ShortId.Create()); - - /// - /// Attempt to create a continent ID from a string - /// - /// The prospective ID - /// The continent ID - /// If the string is not a valid continent ID - public static ContinentId Parse(string id) => new(ShortId.Parse(id)); - - public override string ToString() => Id.ToString(); - } - - /// - /// The ID of a job listing - /// - public record ListingId(ShortId Id) - { - /// - /// Create a new job listing ID - /// - /// A new job listing ID - public static async Task Create() => new ListingId(await ShortId.Create()); - - /// - /// Attempt to create a job listing ID from a string - /// - /// The prospective ID - /// The job listing ID - /// If the string is not a valid job listing ID - public static ListingId Parse(string id) => new(ShortId.Parse(id)); - - public override string ToString() => Id.ToString(); - } - - /// - /// The ID of a skill - /// - public record SkillId(ShortId Id) - { - /// - /// Create a new skill ID - /// - /// A new skill ID - public static async Task Create() => new SkillId(await ShortId.Create()); - - /// - /// Attempt to create a skill ID from a string - /// - /// The prospective ID - /// The skill ID - /// If the string is not a valid skill ID - public static SkillId Parse(string id) => new(ShortId.Parse(id)); - - public override string ToString() => Id.ToString(); - } - - /// - /// The ID of a success report - /// - public record SuccessId(ShortId Id) - { - /// - /// Create a new success report ID - /// - /// A new success report ID - public static async Task Create() => new SuccessId(await ShortId.Create()); - - /// - /// Attempt to create a success report ID from a string - /// - /// The prospective ID - /// The success report ID - /// If the string is not a valid success report ID - public static SuccessId Parse(string id) => new(ShortId.Parse(id)); - - public override string ToString() => Id.ToString(); - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/Listing.cs b/src/JobsJobsJobs/Shared/Domain/Listing.cs deleted file mode 100644 index dee0278..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Listing.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NodaTime; -using System; - -namespace JobsJobsJobs.Shared -{ - /// - /// A job listing - /// - public record Listing( - ListingId Id, - CitizenId CitizenId, - Instant CreatedOn, - string Title, - ContinentId ContinentId, - string Region, - bool RemoteWork, - bool IsExpired, - Instant UpdatedOn, - MarkdownString Text, - LocalDate? NeededBy, - bool? WasFilledHere) - { - /// - /// Navigation property for the citizen who created the job listing - /// - public Citizen? Citizen { get; set; } - - /// - /// Navigation property for the continent - /// - public Continent? Continent { get; set; } - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/MarkdownString.cs b/src/JobsJobsJobs/Shared/Domain/MarkdownString.cs deleted file mode 100644 index d41b5e5..0000000 --- a/src/JobsJobsJobs/Shared/Domain/MarkdownString.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Markdig; - -namespace JobsJobsJobs.Shared -{ - /// - /// A string of Markdown text - /// - public record MarkdownString(string Text) - { - /// - /// The Markdown conversion pipeline (enables all advanced features) - /// - private readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); - - /// - /// Convert this Markdown string to HTML - /// - /// This Markdown string as HTML - public string ToHtml() => Markdown.ToHtml(Text, Pipeline); - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/Profile.cs b/src/JobsJobsJobs/Shared/Domain/Profile.cs deleted file mode 100644 index 7f03987..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Profile.cs +++ /dev/null @@ -1,31 +0,0 @@ -using NodaTime; -using System.Collections.Generic; - -namespace JobsJobsJobs.Shared -{ - /// - /// A job seeker profile - /// - public record Profile( - CitizenId Id, - bool SeekingEmployment, - bool IsPublic, - ContinentId ContinentId, - string Region, - bool RemoteWork, - bool FullTime, - MarkdownString Biography, - Instant LastUpdatedOn, - MarkdownString? Experience) - { - /// - /// Navigation property for continent - /// - public Continent? Continent { get; set; } - - /// - /// Convenience property for skills associated with a profile - /// - public ICollection Skills { get; set; } = new List(); - } -} diff --git a/src/JobsJobsJobs/Shared/Domain/Skill.cs b/src/JobsJobsJobs/Shared/Domain/Skill.cs deleted file mode 100644 index b5e17f5..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Skill.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JobsJobsJobs.Shared -{ - /// - /// A skill the job seeker possesses - /// - public record Skill(SkillId Id, CitizenId CitizenId, string Description, string? Notes); -} diff --git a/src/JobsJobsJobs/Shared/Domain/Success.cs b/src/JobsJobsJobs/Shared/Domain/Success.cs deleted file mode 100644 index e8eed9c..0000000 --- a/src/JobsJobsJobs/Shared/Domain/Success.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NodaTime; - -namespace JobsJobsJobs.Shared -{ - /// - /// A record of success finding employment - /// - public record Success( - SuccessId Id, - CitizenId CitizenId, - Instant RecordedOn, - bool FromHere, - // string Source, - MarkdownString? Story); -} diff --git a/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj b/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj deleted file mode 100644 index c422640..0000000 --- a/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/JobsJobsJobs/Shared/Result.cs b/src/JobsJobsJobs/Shared/Result.cs deleted file mode 100644 index 7bbb869..0000000 --- a/src/JobsJobsJobs/Shared/Result.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; - -namespace JobsJobsJobs.Shared -{ - /// - /// A result with two different possibilities - /// - /// The type of the Ok result - public struct Result - { - private readonly TOk? _okValue; - - /// - /// Is this an Ok result? - /// - public bool IsOk { get; init; } - - /// - /// Is this an Error result? - /// - public bool IsError - { - get => !IsOk; - } - - /// - /// The Ok value - /// - public TOk Ok - { - get => _okValue!; - } - - /// - /// The error value - /// - public string Error { get; set; } - - /// - /// Constructor (inaccessible - use static creation methods) - /// - /// Whether this is an Ok result - /// The value of the Ok result - /// The error message of the Error result - private Result(bool isOk, TOk? okValue = default, string error = "") - { - IsOk = isOk; - _okValue = okValue; - Error = error; - } - - /// - /// Create an Ok result - /// - /// The value of the Ok result - /// The Ok result - public static Result AsOk(TOk okValue) => new Result(true, okValue); - - /// - /// Create an Error result - /// - /// The error message - /// The Error result - public static Result AsError(string error) => new Result(false) { Error = error }; - - /// - /// Transform a result if it is OK, passing the error along if it is an error - /// - /// The transforming function - /// The existing result - /// The resultant result - public static Result Bind(Func> f, Result result) => - result.IsOk ? f(result.Ok) : result; - - /// - /// Transform a result to a different type if it is OK, passing the error along if it is an error - /// - /// The type to which the result is transformed - /// The transforming function - /// The existing result - /// The resultant result - public static Result Map(Func> f, Result result) => - result.IsOk ? f(result.Ok) : Result.AsError(result.Error); - } -}