Copy blog code to new API micro service repo
This commit is contained in:
commit
b23e96493c
24 changed files with 763 additions and 0 deletions
198
.editorconfig
Normal file
198
.editorconfig
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
max_line_length = 180
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = false
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false:silent
|
||||||
|
dotnet_style_qualification_for_field = false:silent
|
||||||
|
dotnet_style_qualification_for_method = false:silent
|
||||||
|
dotnet_style_qualification_for_property = false:silent
|
||||||
|
|
||||||
|
# Language keywords vs BCL types preferences
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:silent
|
||||||
|
|
||||||
|
# Parentheses preferences
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_style_readonly_field = true:suggestion
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all:suggestion
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_elsewhere = false:silent
|
||||||
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
|
csharp_style_var_when_type_is_apparent = false:silent
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||||
|
csharp_style_prefer_switch_expression = true:suggestion
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_local_function = true:suggestion
|
||||||
|
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = false:silent
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = none
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
[*.{yaml,yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/Website/appsettings.Development.json filter=clean-config
|
2
.gitconfig
Normal file
2
.gitconfig
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[filter "clean-config"]
|
||||||
|
clean = ./clean-config.sh
|
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
*.swp
|
||||||
|
*.*~
|
||||||
|
project.lock.json
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
nupkg/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
#build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
msbuild.log
|
||||||
|
msbuild.err
|
||||||
|
msbuild.wrn
|
||||||
|
|
||||||
|
# Visual Studio 2015
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
|
||||||
|
#lcov files
|
||||||
|
lcov.info
|
||||||
|
|
||||||
|
#compiled and minified assets
|
||||||
|
*.css
|
||||||
|
*.min.css
|
||||||
|
*.min.js
|
||||||
|
|
||||||
|
#nodejs
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
output
|
||||||
|
.tmp
|
||||||
|
*.ncrunchsolution
|
||||||
|
/_NCrunch_Website
|
||||||
|
*.DotSettings
|
7
Readme.md
Normal file
7
Readme.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Blog microservice
|
||||||
|
|
||||||
|
Provides an API to save and load blog posts
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
After clone, please run `git config --local include.path ../.gitconfig`. This will set up the filters required to ignore local dev config.
|
5
clean-config.sh
Normal file
5
clean-config.sh
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
sed \
|
||||||
|
-e 's/"database": "Server=.*;User ID=.*;Password=.*;Database=.*"/"database": "Server=localhost;User ID=user;Password=pass;Database=db"/g' \
|
||||||
|
$1
|
48
src/Robware.Api.Blog.sln
Normal file
48
src/Robware.Api.Blog.sln
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.29613.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robware.Api.Blog", "Robware.Api.Blog\Robware.Api.Blog.csproj", "{C5B2C08C-423C-494D-8C2B-77F2C73F36CC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Blog", "Robware.Blog\Robware.Blog.csproj", "{92F5058F-8F58-4A4D-A661-49A4E1198677}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data", "Robware.Data\Robware.Data.csproj", "{8860B6F7-8740-4CF5-BF24-67D00CA7C1A3}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Blog.Tests", "Robware.Blog.Tests\Robware.Blog.Tests.csproj", "{EEB41C17-B61C-451A-9C72-2B405DC42743}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8448EA1E-66F2-4566-8EBC-FDE8FE9BD733}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
..\.editorconfig = ..\.editorconfig
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{C5B2C08C-423C-494D-8C2B-77F2C73F36CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5B2C08C-423C-494D-8C2B-77F2C73F36CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C5B2C08C-423C-494D-8C2B-77F2C73F36CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C5B2C08C-423C-494D-8C2B-77F2C73F36CC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{92F5058F-8F58-4A4D-A661-49A4E1198677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{92F5058F-8F58-4A4D-A661-49A4E1198677}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{92F5058F-8F58-4A4D-A661-49A4E1198677}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{92F5058F-8F58-4A4D-A661-49A4E1198677}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8860B6F7-8740-4CF5-BF24-67D00CA7C1A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8860B6F7-8740-4CF5-BF24-67D00CA7C1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8860B6F7-8740-4CF5-BF24-67D00CA7C1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8860B6F7-8740-4CF5-BF24-67D00CA7C1A3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EEB41C17-B61C-451A-9C72-2B405DC42743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EEB41C17-B61C-451A-9C72-2B405DC42743}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EEB41C17-B61C-451A-9C72-2B405DC42743}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EEB41C17-B61C-451A-9C72-2B405DC42743}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {6B846E24-8B8F-4472-AAF1-5500B4C6303F}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
53
src/Robware.Api.Blog/Controllers/BlogController.cs
Normal file
53
src/Robware.Api.Blog/Controllers/BlogController.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Robware.Blog;
|
||||||
|
|
||||||
|
namespace Robware.Api.Blog.Controllers {
|
||||||
|
[ApiController]
|
||||||
|
public class BlogController : ControllerBase {
|
||||||
|
private readonly ILogger<BlogController> _logger;
|
||||||
|
private readonly IBlogRepository _blogRepository;
|
||||||
|
|
||||||
|
public BlogController(ILogger<BlogController> logger, IBlogRepository blogRepository) {
|
||||||
|
_logger = logger;
|
||||||
|
_blogRepository = blogRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet(nameof(Get) + "/{url}")]
|
||||||
|
public async Task<BlogPost> Get(string url) {
|
||||||
|
_logger.Log(LogLevel.Information, $"{nameof(Get)}: {nameof(url)}={url}");
|
||||||
|
|
||||||
|
if (int.TryParse(url, out int id)) {
|
||||||
|
return await _blogRepository.GetPostByIdAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _blogRepository.GetPostByUrlAsync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<BlogPost> GetPostByUrl();
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<IEnumerable<BlogPost>> GetLatestPosts(int limit, int offset = 0);
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<BlogPost> GetLatestPost();
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<int> GetCount();
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<BlogPost> GetPostById(int id);
|
||||||
|
|
||||||
|
//[HttpPost]
|
||||||
|
//Task<BlogPost> SavePost(BlogPost post);
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task<IEnumerable<BlogPost>> GetAllPosts();
|
||||||
|
|
||||||
|
//[HttpGet]
|
||||||
|
//Task DeletePost(int id);
|
||||||
|
}
|
||||||
|
}
|
16
src/Robware.Api.Blog/Program.cs
Normal file
16
src/Robware.Api.Blog/Program.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace Robware.Api.Blog {
|
||||||
|
public class Program {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder => {
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
30
src/Robware.Api.Blog/Properties/launchSettings.json
Normal file
30
src/Robware.Api.Blog/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:53729",
|
||||||
|
"sslPort": 44379
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "blog",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Robware.Api.Blog": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "weatherforecast",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Robware.Api.Blog/Robware.Api.Blog.csproj
Normal file
13
src/Robware.Api.Blog/Robware.Api.Blog.csproj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
|
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
43
src/Robware.Api.Blog/Startup.cs
Normal file
43
src/Robware.Api.Blog/Startup.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Robware.Blog;
|
||||||
|
using Robware.Data;
|
||||||
|
|
||||||
|
namespace Robware.Api.Blog {
|
||||||
|
public class Startup {
|
||||||
|
public Startup(IConfiguration configuration) {
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services) {
|
||||||
|
services.AddControllers();
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddSingleton<IDatabaseProvider>(new MySQLDatabaseProvider(Configuration.GetConnectionString("database")))
|
||||||
|
.AddSingleton<IBlogRepository, BlogRepository>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
|
||||||
|
if (env.IsDevelopment()) {
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints => {
|
||||||
|
endpoints.MapControllers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Robware.Api.Blog/appsettings.Development.json
Normal file
12
src/Robware.Api.Blog/appsettings.Development.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"database": "Server=localhost;User ID=user;Password=pass;Database=db"
|
||||||
|
}
|
||||||
|
}
|
10
src/Robware.Api.Blog/appsettings.json
Normal file
10
src/Robware.Api.Blog/appsettings.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
42
src/Robware.Blog.Tests/BlogPostTests.cs
Normal file
42
src/Robware.Blog.Tests/BlogPostTests.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using Robware.Blog;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Website.Tests.Models {
|
||||||
|
public class BlogPostTests {
|
||||||
|
[Fact]
|
||||||
|
public void UpdateTitle_WithNewTitle_UpdatesTitlePropertyAndRegeneratesUrl() {
|
||||||
|
var post = new BlogPost();
|
||||||
|
post.UpdateTitle("new title");
|
||||||
|
|
||||||
|
post.Title.Should().Be("new title");
|
||||||
|
post.Url.Should().Be("new-title");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("new:title", "new-title")]
|
||||||
|
[InlineData("new: title", "new-title")]
|
||||||
|
[InlineData("new$title", "new-title")]
|
||||||
|
[InlineData("new TITle", "new-title")]
|
||||||
|
public void UpdateTitle_WithUnsafeCharsInTitle_RegeneratesSafeUrl(string title, string url) {
|
||||||
|
var post = new BlogPost();
|
||||||
|
post.UpdateTitle(title);
|
||||||
|
post.Url.Should().Be(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpdateDraft_WithContent_UpdatesDraftProperty() {
|
||||||
|
var post = new BlogPost();
|
||||||
|
post.UpdateDraft("content");
|
||||||
|
post.Draft.Should().Be("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Publish_SetsContentToDraft() {
|
||||||
|
var post = new BlogPost();
|
||||||
|
post.UpdateDraft("content");
|
||||||
|
post.Publish();
|
||||||
|
post.Content.Should().Be("content");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Robware.Blog.Tests/Robware.Blog.Tests.csproj
Normal file
21
src/Robware.Blog.Tests/Robware.Blog.Tests.csproj
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.0" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="1.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
28
src/Robware.Blog/BlogPost.cs
Normal file
28
src/Robware.Blog/BlogPost.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Robware.Blog
|
||||||
|
{
|
||||||
|
public class BlogPost
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Content { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public string Draft { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
private void GenerateUrl() {
|
||||||
|
Url = Regex.Replace(Title, @"[^a-zA-Z0-9\.]+", "-").ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTitle(string title) {
|
||||||
|
Title = title;
|
||||||
|
GenerateUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateDraft(string content) => Draft = content;
|
||||||
|
public void Publish() => Content = Draft;
|
||||||
|
}
|
||||||
|
}
|
15
src/Robware.Blog/IBlogRepository.cs
Normal file
15
src/Robware.Blog/IBlogRepository.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Robware.Blog {
|
||||||
|
public interface IBlogRepository {
|
||||||
|
Task<BlogPost> GetPostByUrlAsync(string url);
|
||||||
|
Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int limit, int offset = 0);
|
||||||
|
Task<BlogPost> GetLatestPostAsync();
|
||||||
|
Task<int> GetCountAsync();
|
||||||
|
Task<BlogPost> GetPostByIdAsync(int id);
|
||||||
|
Task<BlogPost> SavePost(BlogPost post);
|
||||||
|
Task<IEnumerable<BlogPost>> GetAllPostsAsync();
|
||||||
|
Task DeletePostAsync(int id);
|
||||||
|
}
|
||||||
|
}
|
7
src/Robware.Blog/Robware.Blog.csproj
Normal file
7
src/Robware.Blog/Robware.Blog.csproj
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
97
src/Robware.Data/BlogRepository.cs
Normal file
97
src/Robware.Data/BlogRepository.cs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using Robware.Blog;
|
||||||
|
using Robware.Data.States;
|
||||||
|
|
||||||
|
namespace Robware.Data
|
||||||
|
{
|
||||||
|
public class BlogRepository : IBlogRepository
|
||||||
|
{
|
||||||
|
private readonly IDatabaseProvider _dbProvider;
|
||||||
|
|
||||||
|
public BlogRepository(IDatabaseProvider dbProvider) => _dbProvider = dbProvider;
|
||||||
|
|
||||||
|
private static BlogPost MapBlogPost(BlogPostState state) => new BlogPost {
|
||||||
|
Id = state.Post_Id,
|
||||||
|
Title = state.Post_Title,
|
||||||
|
Content = state.Post_Content,
|
||||||
|
Timestamp = state.Post_Timestamp,
|
||||||
|
Draft = state.Post_Draft,
|
||||||
|
Url = state.Post_Url,
|
||||||
|
UserId = state.User_Id
|
||||||
|
};
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetPostByUrlAsync(string url) {
|
||||||
|
const string query = "SELECT * FROM blog_posts WHERE post_url=@url AND post_deleted=0";
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var result = await connection.QueryAsync<BlogPostState>(query, new{url});
|
||||||
|
return MapBlogPost(result.First());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int limit, int offset = 0) {
|
||||||
|
const string query = "SELECT * FROM blog_posts WHERE post_content<>'' AND post_deleted=0 ORDER BY post_timestamp DESC LIMIT @offset,@limit";
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var results = await connection.QueryAsync<BlogPostState>(query, new{limit, offset});
|
||||||
|
return results.Select(MapBlogPost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetLatestPostAsync() => (await GetLatestPostsAsync(1)).First();
|
||||||
|
|
||||||
|
public async Task<int> GetCountAsync()
|
||||||
|
{
|
||||||
|
var query="SELECT COUNT(*) FROM blog_posts WHERE post_content<>'' AND post_deleted=0";
|
||||||
|
using(var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var result = await connection.QueryAsync<int>(query);
|
||||||
|
return result.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetPostByIdAsync(int id) {
|
||||||
|
const string query = "SELECT * FROM blog_posts WHERE post_id=@id AND post_deleted=0";
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var result = await connection.QueryAsync<BlogPostState>(query, new {id});
|
||||||
|
return MapBlogPost(result.First());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> SavePost(BlogPost post) {
|
||||||
|
const string newPostQuery = "INSERT INTO blog_posts (post_title, post_content, post_draft, post_url) VALUES (@title, @content, @draft, @url); SELECT CAST(LAST_INSERT_ID() as int)";
|
||||||
|
const string updatePostQuery = "UPDATE blog_posts SET post_id = @id, post_title = @title, post_content = @content, post_draft = @draft, post_url = @url WHERE post_id = @id ";
|
||||||
|
|
||||||
|
var newPost = post.Id == 0;
|
||||||
|
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var result = await connection.QueryAsync<int>(newPost ? newPostQuery : updatePostQuery, post);
|
||||||
|
return newPost ? await GetPostByIdAsync(result.Single()) : post;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() {
|
||||||
|
const string query = "SELECT * FROM blog_posts WHERE post_deleted=0";
|
||||||
|
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
var result = await connection.QueryAsync<BlogPostState>(query);
|
||||||
|
return result.Select(MapBlogPost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeletePostAsync(int id) {
|
||||||
|
const string query = "UPDATE blog_posts SET post_deleted=1 WHERE post_id=@id";
|
||||||
|
|
||||||
|
using (var connection = _dbProvider.NewConnection()) {
|
||||||
|
connection.Open();
|
||||||
|
await connection.ExecuteAsync(query, new {id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/Robware.Data/IDatabaseProvider.cs
Normal file
9
src/Robware.Data/IDatabaseProvider.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace Robware.Data
|
||||||
|
{
|
||||||
|
public interface IDatabaseProvider
|
||||||
|
{
|
||||||
|
IDbConnection NewConnection();
|
||||||
|
}
|
||||||
|
}
|
14
src/Robware.Data/MySQLDatabaseProvider.cs
Normal file
14
src/Robware.Data/MySQLDatabaseProvider.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Data;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
|
||||||
|
namespace Robware.Data
|
||||||
|
{
|
||||||
|
public class MySQLDatabaseProvider:IDatabaseProvider
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public MySQLDatabaseProvider(string connectionString) => _connectionString = connectionString;
|
||||||
|
|
||||||
|
public IDbConnection NewConnection() => new MySqlConnection(_connectionString);
|
||||||
|
}
|
||||||
|
}
|
18
src/Robware.Data/Robware.Data.csproj
Normal file
18
src/Robware.Data/Robware.Data.csproj
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="dapper" Version="2.0.35" />
|
||||||
|
<PackageReference Include="dapper.contrib" Version="2.0.35" />
|
||||||
|
<PackageReference Include="mysqlconnector" Version="0.63.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
19
src/Robware.Data/States/BlogPostState.cs
Normal file
19
src/Robware.Data/States/BlogPostState.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
|
namespace Robware.Data.States
|
||||||
|
{
|
||||||
|
[Table("blog_posts")]
|
||||||
|
public class BlogPostState
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Post_Id { get; set; }
|
||||||
|
public string Post_Title { get; set; }
|
||||||
|
public string Post_Content { get; set; }
|
||||||
|
public DateTime Post_Timestamp { get; set; }
|
||||||
|
public string Post_Draft { get; set; }
|
||||||
|
public string Post_Url { get; set; }
|
||||||
|
public bool Post_Deleted{ get; set; }
|
||||||
|
public int User_Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue