(feat): data providers init

This commit is contained in:
Maksym Sadovnychyy 2022-08-06 22:48:48 +02:00
parent 8fe4d93000
commit 522a9d4a43
67 changed files with 1293 additions and 130 deletions

View File

@ -0,0 +1,39 @@
import React, { FC } from 'react'
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
import { CategoryModel } from '../../models'
interface ICategories {
items?: CategoryModel []
}
const Categories: FC<ICategories> = ({
items = []
}) => {
const middleIndex = Math.ceil(items.length / 2)
const firstHalf = items.splice(0, middleIndex)
const secondHalf = items.splice(-middleIndex)
return <Card className="mb-4">
<CardHeader>Categories</CardHeader>
<CardBody>
<Row>
<Col sm="6">
<ul className="list-unstyled mb-0">
{firstHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
</ul>
</Col>
<Col sm="6">
<ul className="list-unstyled mb-0">
{secondHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
</ul>
</Col>
</Row>
</CardBody>
</Card>
}
export {
Categories
}

View File

@ -0,0 +1,15 @@
import React, { FC } from 'react'
import { Card, CardBody, CardHeader } from 'reactstrap'
const Empty : FC = () => {
return <Card className="mb-4">
<CardHeader></CardHeader>
<CardBody>
</CardBody>
</Card>
}
export {
Empty
}

View File

@ -0,0 +1,18 @@
import React, { FC } from 'react'
import { Card, CardBody, CardHeader } from 'reactstrap'
const Search : FC = () => {
return <Card className="mb-4">
<CardHeader>Search</CardHeader>
<CardBody>
<div className="input-group">
<input className="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
<button className="btn btn-primary" id="button-search" type="button">Go!</button>
</div>
</CardBody>
</Card>
}
export {
Search
}

View File

@ -1,59 +1,14 @@
import React, { FC } from 'react' import { Search } from './Search'
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' import { Categories } from './Categories'
import { CategoryModel } from '../../models' import { Empty } from './Empty'
const Search = () => {
return <Card className="mb-4">
<CardHeader>Search</CardHeader>
<CardBody>
<div className="input-group">
<input className="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
<button className="btn btn-primary" id="button-search" type="button">Go!</button>
</div>
</CardBody>
</Card>
}
interface ICategories {
items?: CategoryModel []
}
const Categories: FC<ICategories> = ({
items = []
}) => {
const middleIndex = Math.ceil(items.length / 2)
const firstHalf = items.splice(0, middleIndex)
const secondHalf = items.splice(-middleIndex)
return <Card className="mb-4">
<CardHeader>Categories</CardHeader>
<CardBody>
<Row>
<Col sm="6">
<ul className="list-unstyled mb-0">
{firstHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
</ul>
</Col>
<Col sm="6">
<ul className="list-unstyled mb-0">
{secondHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
</ul>
</Col>
</Row>
</CardBody>
</Card>
}
const Empty = () => {
return <Card className="mb-4">
<CardHeader>Side Widget</CardHeader>
<CardBody>
You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component!
</CardBody>
</Card>
}
export { export {
Search, Search,

View File

@ -40,6 +40,19 @@ export interface FormItemModel {
placeHolder?: string, placeHolder?: string,
} }
export interface HeaderLink {
[key: string]: string
}
export interface Meta {
[key: string]: string
}
export interface HeaderModel {
title: string,
link: HeaderLink,
meta: Meta
}
export interface ImageModel { export interface ImageModel {
src: string, src: string,
alt: string alt: string
@ -91,19 +104,3 @@ export interface TestimonialModel {
text: string, text: string,
reviewer: ReviewerModel reviewer: ReviewerModel
} }
export interface HeaderLink {
[key: string]: string
}
export interface Meta {
[key: string]: string
}
export interface HeaderModel {
title: string,
link: HeaderLink,
meta: Meta
}

View File

@ -188,8 +188,8 @@ const BlogCatalog = () => {
</Row> </Row>
</Col> </Col>
<Col lg="4"> <Col lg="4">
<Search /> {/*<Search />
<Categories {...blogCategories} /> <Categories {...blogCategories} />*/}
<Empty/> <Empty/>
</Col> </Col>
</Row> </Row>

View File

@ -84,7 +84,7 @@ const BlogItem = () => {
if(blogItemTitle.postedOnBy && blogItem?.author) if(blogItemTitle.postedOnBy && blogItem?.author)
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{nickName}', dateFormat(blogItem.author.nickName)) blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{nickName}', dateFormat(blogItem.author.nickName))
return <Container fluid mt="5"> return <Container fluid className="mt-5">
<Row> <Row>
<Col lg="8"> <Col lg="8">
@ -99,11 +99,7 @@ const BlogItem = () => {
}} /> }} />
</Col> </Col>
<Col lg="4"> <Col lg="4">
<Search />
<Categories />
<Empty/> <Empty/>
</Col> </Col>
</Row> </Row>

View File

@ -5,9 +5,9 @@ import { Container } from 'reactstrap'
import style from './scss/style.module.scss' import style from './scss/style.module.scss'
const Checkout = () => { const Checkout = () => {
return <Container fluid className={style.container}> return <Container fluid className={`py-5 ${style.container}`}>
<main> <main>
<div className="py-5 text-center"> <div className="text-center">
<img className="d-block mx-auto mb-4" src="../assets/brand/bootstrap-logo.svg" alt="" width="72" height="57" /> <img className="d-block mx-auto mb-4" src="../assets/brand/bootstrap-logo.svg" alt="" width="72" height="57" />
<h2>Checkout form</h2> <h2>Checkout form</h2>
<p className="lead">Below is an example form built entirely with Bootstraps form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.</p> <p className="lead">Below is an example form built entirely with Bootstraps form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.</p>

View File

@ -469,6 +469,7 @@ const unloadedState: ContentState = {
export const reducer: Reducer<ContentState> = (state: ContentState | undefined, incomingAction: Action): ContentState => { export const reducer: Reducer<ContentState> = (state: ContentState | undefined, incomingAction: Action): ContentState => {
if (state === undefined) { if (state === undefined) {
console.log(unloadedState)
return unloadedState return unloadedState
} }

View File

@ -5,6 +5,6 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Core.Abstractions.DomainObjects { namespace Core.Abstractions.DomainObjects {
public abstract class DomainObject { public abstract class DomainObjectBase<T> : EquatableBase<T> {
} }
} }

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.Abstractions.DomainObjects {
public abstract class DomainObjectDocumentBase<T> : DomainObjectBase<T> {
public Guid Id { get; set; }
}
}

View File

@ -0,0 +1,5 @@

namespace Core.Abstractions.DomainObjects {
public abstract class PageBase<T> : DomainObjectBase<T> { }
}

View File

@ -0,0 +1,7 @@

namespace Core.Abstractions.DomainObjects {
public abstract class PageSectionBase<T> : DomainObjectBase<T> {
public string? Title { get; set; }
public string? Text { get; set; }
}
}

View File

@ -0,0 +1,8 @@
using Core.DomainObjects;
namespace Core.Abstractions.DomainObjects {
public abstract class PersonBase<T> : DomainObjectBase<T> {
public Guid Id { get; set; }
public Image? Image { get; set; }
}
}

View File

@ -7,8 +7,7 @@ using System.Threading.Tasks;
namespace Core.Abstractions.DomainObjects { namespace Core.Abstractions.DomainObjects {
public abstract class PostItem : DomainObject { public abstract class PostItemBase<T> : DomainObjectDocumentBase<T> {
public Guid Id { get; set; }
/// <summary> /// <summary>
/// Author / Owner /// Author / Owner

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Core.Abstractions {
public abstract class Enumeration : IComparable {
public string Name { get; private set; }
public int Id { get; private set; }
protected Enumeration(int id, string name) => (Id, Name) = (id, name);
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
typeof(T).GetFields(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null))
.Cast<T>();
public override bool Equals(object? obj) {
if (obj == null)
return false;
if (obj is not Enumeration otherValue) {
return false;
}
var typeMatches = GetType().Equals(obj.GetType());
var valueMatches = Id.Equals(otherValue.Id);
return typeMatches && valueMatches;
}
public override int GetHashCode() => Id.GetHashCode();
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) {
var absoluteDifference = Math.Abs(firstValue.Id - secondValue.Id);
return absoluteDifference;
}
public static T FromValue<T>(int value) where T : Enumeration {
var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
return matchingItem;
}
public static T FromDisplayName<T>(string displayName) where T : Enumeration {
var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
return matchingItem;
}
private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration {
var matchingItem = GetAll<T>().FirstOrDefault(predicate);
if (matchingItem == null)
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
return matchingItem;
}
public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.Abstractions {
public abstract class EquatableBase<T> : IEquatable<T> {
public bool Equals(T? other) {
if (other == null)
return false;
return GetHashCode() == other.GetHashCode();
}
public abstract override int GetHashCode();
}
}

View File

@ -0,0 +1,16 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
public class Author : PersonBase<Author>{
public string NickName { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,14 @@
using Core.Abstractions.DomainObjects; using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects { namespace Core.DomainObjects {
internal class BlogItem : PostItem { public class BlogItem : DomainObjectBase<BlogItem> {
public int Likes { get; set; } public int? ReadTime { get; set; }
public int ReadingTime { get; set; }
public int? Likes { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
} }
} }

View File

@ -0,0 +1,13 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Category : DomainObjectBase<Category> {
public Guid Id { get; set; }
public string Text { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,18 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
public class Comment : DomainObjectBase<Comment> {
public Author Author { get; set; }
public string Text { get; set; }
public List<Comment>? Responses { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,13 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.Documents {
public class BlogItem : PostItemBase<BlogItem> {
public int Likes { get; set; }
public int ReadingTime { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,34 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.Pages;
namespace Core.DomainObjects.Documents {
public class Content : DomainObjectDocumentBase<Content> {
public string SiteName { get; set; }
public string SiteUrl { get; set; }
public Header Header { get; set; }
public Localization Localization { get; set; }
public List<Route> Routes { get; set; }
public List<Route> AdminRoutes { get; set; }
public List<Route> ServiceRoutes { get; set; }
public List<MenuItem> TopMenu { get; set; }
public List<MenuItem> SideMenu { get; set; }
public HomePage HomePage { get; set; }
public ShopCatalogPage ShopCatalog { get; set; }
public ShopItemPage ShopItem { get; set; }
public BlogCatalogPage BlogCatalog { get; set; }
public BlogItemPage Blogitem { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.Documents {
public class ShopItem : PostItemBase<ShopItem> {
public string Sku { get; set; }
public int Rating { get; set; }
public int Price { get; set; }
public int NewPrice { get; set; }
public int Quantity { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -9,9 +9,7 @@ using System.Threading.Tasks;
namespace Core.DomainObjects { namespace Core.DomainObjects {
public class User : DomainObject { public class User : DomainObjectDocumentBase<User> {
public Guid Id { get; set; }
public string NickName { get; set; } public string NickName { get; set; }
public string Hash { get; set; } public string Hash { get; set; }
@ -25,5 +23,9 @@ namespace Core.DomainObjects {
public Address BillingAddress { get; set; } public Address BillingAddress { get; set; }
public Address ShippingAddress { get; set; } public Address ShippingAddress { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
} }
} }

View File

@ -0,0 +1,13 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Feature : DomainObjectBase<Feature> {
public string Icon { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,17 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
public class FormItem : DomainObjectBase<FormItem> {
public string? Title { get; set; }
public string? PlaceHolder { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,16 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Header : DomainObjectBase<Header> {
public string Title { get; set; }
public Dictionary<string, string> HeaderLink { get; set; }
public Dictionary<string, string> Meta { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,12 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Image : DomainObjectBase<Image> {
public string Src { get; set; }
public string Alt { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,12 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Link : DomainObjectBase<Link> {
public string Target { get; set; }
public string AnchorText { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,27 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
public class Localization : DomainObjectBase<Localization> {
public string TimeZone { get; set; }
public string Locale { get; set; }
public string DateFormat { get; set; }
public string TimeFormat { get; set; }
public string Currency { get; set; }
public string CurrencySymobol { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class MenuItem : DomainObjectBase<MenuItem> {
public string? Icon { get; set; }
public string? Title { get; set; }
public string? Target { get; set; }
public List<MenuItem>? ChildItems { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.PageSections {
public class BlogTitleSection : PageSectionBase<BlogTitleSection> {
public string PostedOnBy { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.PageSections {
public class CallToActionSection : PageSectionBase<CallToActionSection> {
public string PrivacyDisclaimer { get; set; }
public FormItem Email { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,16 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects.PageSections {
public class CommentsSection : PageSectionBase<CommentsSection> {
public string LeaveComment { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,16 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects.PageSections {
public class FeaturedBlogSection : PageSectionBase<FeaturedBlogSection> {
public string ReadTime { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects.PageSections {
public class FeaturedBologsSection : PageSectionBase<FeaturedBologsSection> {
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.PageSections {
public class FeaturesSection : PageSectionBase<FeaturesSection> {
public List<Feature> Items { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,17 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects.PageSections {
public class ProductSection : PageSectionBase<ProductSection> {
public string AvailableQuantity { get; set; }
public string AddToCart { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects.PageSections {
public class RelatedProductsSection : PageSectionBase<RelatedProductsSection> {
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,10 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.PageSections {
public class TestimonialsSection : PageSectionBase<TestimonialsSection> {
public List<Testimonial> Items { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,13 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects.PageSections {
public class TitleSection : PageSectionBase<TitleSection> {
public Image? Image { get; set; }
public MenuItem? PrimaryLink { get; set; }
public MenuItem? SecondaryLink { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.PageSections;
namespace Core.DomainObjects.Pages {
public class BlogCatalogPage : PageBase<BlogCatalogPage> {
public TitleSection TitleSection { get; set; }
public FeaturedBlogSection FeaturedBlogSection { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,13 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.PageSections;
namespace Core.DomainObjects.Pages {
public class BlogItemPage : PageBase<BlogItemPage> {
public BlogTitleSection TitleSection { get; set; }
public CommentsSection CommentsSection { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,17 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.PageSections;
namespace Core.DomainObjects.Pages {
public class HomePage : PageBase<HomePage> {
public TitleSection TitleSection { get; set; }
public FeaturesSection FeaturesSection { get; set; }
public TestimonialsSection TestimonialsSection { get; set; }
public FeaturedBologsSection FeaturedBlogsSection { get; set; }
public CallToActionSection CallToActionSection { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,12 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.PageSections;
namespace Core.DomainObjects.Pages {
public class ShopCatalogPage : PageBase<ShopCatalogPage> {
public TitleSection TitleSection { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using Core.Abstractions.DomainObjects;
using Core.DomainObjects.PageSections;
namespace Core.DomainObjects.Pages {
public class ShopItemPage : PageBase<ShopItemPage> {
public ProductSection ProductSection { get; set; }
public RelatedProductsSection RelatedProductsSection { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,12 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Reviewer : PersonBase<Reviewer> {
public string FullName { get; set; }
public string Position { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Abstractions.DomainObjects;
namespace Core.DomainObjects {
public class Route : DomainObjectBase<Route> {
public string Target { get; set; }
public string? Component { get; set; }
public List<Route>? ChildRoutes { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -1,17 +0,0 @@
using Core.Abstractions;
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
internal class ShopItem : PostItem {
public string Sku { get; set; }
public int Rating { get; set; }
public int Price { get; set; }
public int NewPrice { get; set; }
public int Quantity { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using Core.Abstractions.DomainObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.DomainObjects {
public class Testimonial : DomainObjectBase<Testimonial> {
public string Text { get; set; }
public Reviewer Reviewer { get; set; }
public override int GetHashCode() {
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,277 @@
using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using DomainResults.Common;
using Core.Abstractions.DomainObjects;
namespace DataProviders.Abstractions {
public abstract class DataProviderBase<T> where T : DomainObjectDocumentBase<T> {
private protected const string _databaseName = "reactredux";
private protected readonly ILogger<DataProviderBase<T>> _logger;
private protected readonly IMongoClient _client;
private protected readonly IIdGenerator _idGenerator;
private protected readonly ISessionService _sessionService;
private protected List<T>? _collection;
/// <summary>
/// Main constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="client"></param>
/// <param name="idGenerator"></param>
/// <param name="dataProviderUtils"></param>
public DataProviderBase(
ILogger<DataProviderBase<T>> logger,
IMongoClient client,
IIdGenerator idGenerator,
ISessionService sessionService
) {
_logger = logger;
_client = client;
_idGenerator = idGenerator;
_sessionService = sessionService;
}
/// <summary>
/// Testing constructor
/// </summary>
/// <param name="logger"></param>
/// <param name="collection"></param>
public DataProviderBase(
ILogger<DataProviderBase<T>> logger,
ISessionService sessionService,
List<T> collection
) {
_logger = logger;
_sessionService = sessionService;
_collection = collection ?? new List<T>();
}
#region Insert
private protected (Guid?, IDomainResult) Insert(T obj, string collectionName) =>
InsertAsync(obj, collectionName).Result;
private protected (Guid?, IDomainResult) Insert(T obj, string collectionName, Guid sessionId) =>
InsertAsync(obj, collectionName, sessionId).Result;
private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName) =>
InsertAsyncCore(obj, collectionName, null);
private protected Task<(Guid?, IDomainResult)> InsertAsync(T obj, string collectionName, Guid sessionId) =>
InsertAsyncCore(obj, collectionName, sessionId);
#endregion
#region InsertMany
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList, string collectionName) =>
InsertManyAsync(objList, collectionName).Result;
private protected (List<Guid>?, IDomainResult) InsertMany(List<T> objList, string collectionName, Guid sessionId) =>
InsertManyAsync(objList, collectionName, sessionId).Result;
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList, string collectionName) =>
InsertManyAsyncCore(objList, collectionName, null);
private protected Task<(List<Guid>?, IDomainResult)> InsertManyAsync(List<T> objList, string collectionName, Guid sessionId) =>
InsertManyAsyncCore(objList, collectionName, sessionId);
#endregion
#region Get
private protected (List<T>?, IDomainResult) GetWithPredicate(Expression<Func<T, bool>> predicate, string collectionName) =>
GetWithPredicateCore(predicate, collectionName);
#endregion
#region Update
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate, string collectionName) =>
UpdateWithPredicateAsync(obj, predicate, collectionName).Result;
private protected (Guid?, IDomainResult) UpdateWithPredicate(T obj, Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
UpdateWithPredicateAsync(obj, predicate, collectionName, sessionId).Result;
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate, string collectionName) =>
UpdateWithPredicateAsyncCore(obj, predicate, collectionName, null);
private protected Task<(Guid?, IDomainResult)> UpdateWithPredicateAsync(T obj, Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
UpdateWithPredicateAsyncCore(obj, predicate, collectionName, sessionId);
#endregion
#region Exists
private protected (Guid?, IDomainResult) Exists(Guid id, string collectionName) {
var (_resultList, result) = GetWithPredicate(x => x.Id == id, collectionName);
return (result.Status != DomainOperationStatus.Failed && _resultList != null && _resultList.Count > 0
? id
:null,
result);
}
#endregion
#region Delete
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate, string collectionName) =>
DeleteWithPredicateAsync(predicate, collectionName).Result;
private protected IDomainResult DeleteWithPredicate(Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
DeleteWithPredicateAsync(predicate, collectionName, sessionId).Result;
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, string collectionName) =>
DeleteWithPredicateAsyncCore(predicate, collectionName, null);
private protected Task<IDomainResult> DeleteWithPredicateAsync(Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
DeleteWithPredicateAsyncCore(predicate, collectionName, sessionId);
#endregion
#region DeleteMany
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate, string collectionName) =>
DeleteManyWithPredicateAsync(predicate, collectionName).Result;
private protected IDomainResult DeleteManyWithPredicate(Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
DeleteManyWithPredicateAsync(predicate, collectionName, sessionId).Result;
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate, string collectionName) =>
DeleteManyWithPredicateAsyncCore(predicate, collectionName, null);
private protected Task<IDomainResult> DeleteManyWithPredicateAsync(Expression<Func<T, bool>> predicate, string collectionName, Guid sessionId) =>
DeleteManyWithPredicateAsyncCore(predicate, collectionName, sessionId);
#endregion
#region Core methods
private async protected Task<(Guid?, IDomainResult)> InsertAsyncCore(T obj, string collectionName, Guid? sessionId) {
try {
if (_collection != null) {
obj.Id = Guid.NewGuid();
_collection.Add(obj);
return IDomainResult.Success(obj.Id);
}
var collection = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName);
if (sessionId != null)
await collection.InsertOneAsync(_sessionService.GetSession(sessionId.Value), obj);
else
collection.InsertOne(obj);
return IDomainResult.Success(obj.Id);
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<Guid?>();
}
}
private async Task<(List<Guid>?, IDomainResult)> InsertManyAsyncCore(List<T> objList, string collectionName, Guid? sessionId) {
try {
if (_collection != null) {
_collection = _collection.Concat(objList).ToList();
return IDomainResult.Success(objList.Select(x => x.Id).ToList());
}
var collection = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName);
if (sessionId != null)
await collection.InsertManyAsync(_sessionService.GetSession(sessionId.Value), objList);
else
collection.InsertMany(objList);
return IDomainResult.Success(objList.Select(x => x.Id).ToList());
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<List<Guid>?>();
}
}
private (List<T>?, IDomainResult) GetWithPredicateCore(Expression<Func<T, bool>> predicate, string collectionName) {
try {
List<T>? result;
if (_collection != null) {
result = _collection?.AsQueryable()
.Where(predicate).ToList();
}
else {
result = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName)
.Find(predicate).ToList();
}
return result != null
? IDomainResult.Success(result)
: IDomainResult.NotFound<List<T>?>();
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<List<T>?>();
}
}
private async Task<(Guid?, IDomainResult)> UpdateWithPredicateAsyncCore(T obj, Expression<Func<T, bool>> predicate, string collectionName, Guid? sessionId) {
try {
if (_collection != null) {
// remove element(s) from list
foreach (var element in _collection.AsQueryable().Where(predicate))
_collection = _collection.Where(x => x.Id != element.Id).ToList();
// add updated element
_collection.Add(obj);
}
else {
var collection = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName);
if (sessionId != null)
await collection.ReplaceOneAsync(_sessionService.GetSession(sessionId.Value), predicate, obj);
else
await collection.ReplaceOneAsync(predicate, obj);
}
return IDomainResult.Success(obj.Id);
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed<Guid?>();
}
}
private async Task<IDomainResult> DeleteWithPredicateAsyncCore(Expression<Func<T, bool>> predicate, string collectionName, Guid? sessionId) {
try {
var collection = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName);
if (sessionId != null)
await collection.DeleteOneAsync<T>(_sessionService.GetSession(sessionId.Value), predicate);
else
await collection.DeleteOneAsync<T>(predicate);
return IDomainResult.Success();
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed();
}
}
private async Task<IDomainResult> DeleteManyWithPredicateAsyncCore(Expression<Func<T, bool>> predicate, string collectionName, Guid? sessionId) {
try {
var collection = _client.GetDatabase(_databaseName).GetCollection<T>(collectionName);
if (sessionId != null)
await collection.DeleteManyAsync(_sessionService.GetSession(sessionId.Value), predicate);
else
await collection.DeleteManyAsync(predicate);
return IDomainResult.Success();
}
catch (Exception ex) {
_logger.LogError(ex, "Data provider error");
return IDomainResult.Failed();
}
}
#endregion
}
}

View File

@ -4,10 +4,10 @@
public class BlogDataProvider : IBlogDataProvider { public class BlogDataProvider : IBlogDataProvider {
private readonly IDataProvidersConfiguration _configuration;
public BlogDataProvider(IDataProvidersConfiguration configuration) {
_configuration = configuration; public BlogDataProvider() {
} }
} }

View File

@ -0,0 +1,39 @@
using Core.DomainObjects.Documents;
using DataProviders.Abstractions;
using DomainResults.Common;
using Microsoft.Extensions.Logging;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataProviders {
public interface IContentDataProvider {
(Content?, IDomainResult) Get(Guid id);
}
public class ContentDataProvider : DataProviderBase<Content>, IContentDataProvider {
private const string _collectionName = "contents";
public ContentDataProvider(
ILogger<DataProviderBase<Content>> logger,
IMongoClient client,
IIdGenerator idGenerator,
ISessionService sessionService) : base(logger, client, idGenerator, sessionService) {
}
public (Content?, IDomainResult) Get(Guid id) {
var (list, result) = GetWithPredicate(x => x.Id == id, _collectionName);
if (!result.IsSuccess || list == null)
return (null, result);
return (list.First(), result);
}
}
}

View File

@ -0,0 +1,72 @@
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using Core.Abstractions;
namespace PecMgr.DataProviders.Converters {
public class EnumerationSerializer<T> : IBsonSerializer<T> where T : Enumeration {
private T Deserialize(IBsonReader reader) => Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader));
private void Serialize(IBsonWriter writer, T value) => BsonSerializer.Serialize(writer, value.Id);
public Type ValueType { get => typeof(T); }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context.Writer, (T)value);
T IBsonSerializer<T>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value) => Serialize(context.Writer, value);
}
public class EnumerationListSerializer<T> : IBsonSerializer<List<T>>, IBsonArraySerializer where T : Enumeration {
private List<T> Deserialize(IBsonReader reader) {
var type = reader.GetCurrentBsonType();
var response = new List<T>();
switch (type) {
case BsonType.Array:
reader.ReadStartArray();
while (reader.ReadBsonType() != BsonType.EndOfDocument) {
response.Add(Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader)));
}
reader.ReadEndArray();
return response;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
return response;
}
private void Serialize(IBsonWriter writer, List<T> values) {
if (values != null) {
writer.WriteStartArray();
foreach (var value in values) {
writer.WriteInt32(value.Id);
}
writer.WriteEndArray();
}
}
public Type ValueType { get => typeof(List<T>); }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context.Writer, (List<T>)value);
List<T> IBsonSerializer<List<T>>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<T> value) => Serialize(context.Writer, value);
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) {
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
}
}

View File

@ -6,4 +6,15 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.17.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,23 @@
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.IdGenerators;
namespace DataProviders.Extensions
{
public static class ServiceCollectionExtensions {
public static void RegisterDataproviders(this IServiceCollection services, IDataProvidersConfig appSettings) {
var config = appSettings.Database;
services.AddSingleton<IMongoClient>(x => new MongoClient(config.ConnectionString));
services.AddSingleton<IIdGenerator, GuidGenerator>();
services.AddSingleton<ISessionService, SessionService>();
services.AddSingleton<IContentDataProvider, ContentDataProvider>();
Mappings.RegisterClassMap();
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataProviders {
public interface IDataProvidersConfig {
public Database Database { get; set; }
}
public interface IDatabase {
string ConnectionString { get; set; }
}
public class Database : IDatabase {
private string _connectionString;
public string ConnectionString {
get {
var envVar = Environment.GetEnvironmentVariable("DB_CONN");
return envVar ?? _connectionString;
}
set => _connectionString = value;
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataProviders {
public interface IDataProvidersConfiguration {
public Database Database { get; set; }
}
public class Database {
public string ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Serializers;
using DataProviders;
namespace DataProviders {
public class Mappings {
public static void RegisterClassMap() {
ConventionRegistry.Register("MyConventions",
new ConventionPack {
new CamelCaseElementNameConvention(),
new IgnoreIfNullConvention(true)
}, type => true);
// https://kevsoft.net/2020/06/25/storing-guids-as-strings-in-mongodb-with-csharp.html
BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String));
}
}
}

View File

@ -0,0 +1,83 @@
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
namespace DataProviders {
public interface ISessionService {
Task<Guid> StartSession();
IClientSessionHandle? GetSession(Guid id);
void StartTransaction(Guid id);
void CommitTransaction(Guid id);
void RollbackTransaction(Guid id);
void DisposeSession(Guid id);
}
public class SessionService : ISessionService {
private readonly ILogger<SessionService> _logger;
private readonly IMongoClient _client;
private Dictionary<Guid, IClientSessionHandle?> _sessions;
public SessionService(
ILogger<SessionService> logger,
IMongoClient client
) {
_logger = logger;
_client = client;
_sessions = new Dictionary<Guid, IClientSessionHandle?>();
}
public async Task<Guid> StartSession() {
var sessionId = Guid.NewGuid();
var session = await _client.StartSessionAsync();
_sessions.Add(sessionId, session);
return sessionId;
}
public IClientSessionHandle? GetSession(Guid id) {
_sessions.TryGetValue(id, out IClientSessionHandle? session);
return session;
}
public void StartTransaction(Guid id) {
var session = GetSession(id);
if (session != null)
session.StartTransaction();
}
public void CommitTransaction(Guid id) {
var session = GetSession(id);
if (session != null) {
session.CommitTransaction();
session.Dispose();
_sessions.Remove(id);
}
}
public async void RollbackTransaction(Guid id) {
var session = GetSession(id);
if (session != null) {
await session.AbortTransactionAsync();
session.Dispose();
_sessions.Remove(id);
}
}
public void DisposeSession(Guid id) {
var session = GetSession(id);
if (session != null) {
session.Dispose();
_sessions.Remove(id);
}
}
}
}

View File

@ -1,10 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataProviders {
public class SiteSettingsDataProvider {
}
}

View File

@ -1,5 +1,8 @@
namespace WeatherForecast { using DataProviders;
public class Configuration {
namespace WeatherForecast {
public class Configuration : IDataProvidersConfig{
public string Secret { get; set; } public string Secret { get; set; }
public Database Database { get; set; }
} }
} }

View File

@ -0,0 +1,12 @@
namespace WeatherForecast.Models {
public class HeaderModel {
public string Title { get; set; }
public Dictionary<string, string> HeaderLink { get; set; }
public Dictionary<string, string> Meta { get; set; }
}
}

View File

@ -4,6 +4,11 @@ using WeatherForecast.Models.Pages;
namespace WeatherForecast.Models.Responses { namespace WeatherForecast.Models.Responses {
public class GetContentResponseModel : ResponseModel { public class GetContentResponseModel : ResponseModel {
public string SiteName { get; set; } public string SiteName { get; set; }
public string SiteUrl { get; set; }
public HeaderModel Header { get; set; }
public LocalizationModel Localization { get; set; }
public List<RouteModel> Routes { get; set; } public List<RouteModel> Routes { get; set; }
public List<RouteModel> AdminRoutes { get; set; } public List<RouteModel> AdminRoutes { get; set; }

View File

@ -0,0 +1,12 @@
namespace WeatherForecast.Services {
public interface IContentService {
}
public class ContentService : IContentService {
}
}

View File

@ -20,6 +20,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\DataProviders\DataProviders.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>