(feat): blog server side mock
This commit is contained in:
parent
28d115af0e
commit
49c262d5be
@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "5.1.3",
|
"bootstrap": "5.1.3",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"dayjs": "^1.11.2",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"merge": "^2.1.1",
|
"merge": "^2.1.1",
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
|
|||||||
14
clientapp/src/functions/dateFormat.ts
Normal file
14
clientapp/src/functions/dateFormat.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import dayjs from 'dayjs'
|
||||||
|
import 'dayjs/locale/it'
|
||||||
|
import 'dayjs/locale/en'
|
||||||
|
import 'dayjs/locale/ru'
|
||||||
|
|
||||||
|
const dateFormat = (date: string): string => {
|
||||||
|
return dayjs(date)
|
||||||
|
.locale('en')
|
||||||
|
.format("MMMM YYYY, dddd")
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
dateFormat
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
|
import { dateFormat } from './dateFormat'
|
||||||
import { getKeyValue } from './getKeyValue'
|
import { getKeyValue } from './getKeyValue'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getKeyValue
|
getKeyValue,
|
||||||
|
dateFormat
|
||||||
}
|
}
|
||||||
3
clientapp/src/httpQueries/blog.ts
Normal file
3
clientapp/src/httpQueries/blog.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { IBlogItemModel, ICategoryModel, IFetchResult } from "./models"
|
||||||
|
|
||||||
|
const apiUrl = 'https://localhost:59018/api/Blog'
|
||||||
53
clientapp/src/httpQueries/blogs.ts
Normal file
53
clientapp/src/httpQueries/blogs.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { IBlogItemsPaginationModel, IBlogItemModel, ICategoryModel, IFetchResult } from "./models"
|
||||||
|
|
||||||
|
const apiUrl = 'https://localhost:59018/api/Blogs'
|
||||||
|
|
||||||
|
export interface IGetBlogsRequest {
|
||||||
|
[key: string]: string | undefined
|
||||||
|
category?: string,
|
||||||
|
searchText?: string,
|
||||||
|
currentPage?: string,
|
||||||
|
itemsPerPage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetBlogsResponse {
|
||||||
|
featuredBlog?: IBlogItemModel,
|
||||||
|
blogItemsPagination?: IBlogItemsPaginationModel,
|
||||||
|
categories?: ICategoryModel []
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetBlogs = async (props?: IGetBlogsRequest): Promise<IGetBlogsResponse> => {
|
||||||
|
const url = new URL(apiUrl)
|
||||||
|
|
||||||
|
if(props) {
|
||||||
|
Object.keys(props).forEach(key => {
|
||||||
|
if (typeof(props[key]) !== undefined) {
|
||||||
|
url.searchParams.append(key, props[key] as string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestParams = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`invoke:`, url.toString())
|
||||||
|
|
||||||
|
const fetchData = await fetch(url.toString(), requestParams)
|
||||||
|
.then(async fetchData => {
|
||||||
|
return {
|
||||||
|
status: fetchData.status,
|
||||||
|
text: await fetchData.text()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return JSON.parse((fetchData as IFetchResult).text) as IGetBlogsResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
GetBlogs
|
||||||
|
}
|
||||||
62
clientapp/src/httpQueries/models.ts
Normal file
62
clientapp/src/httpQueries/models.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
export interface IFetchResult {
|
||||||
|
status: number,
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IImageModel {
|
||||||
|
src: string,
|
||||||
|
alt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthorModel {
|
||||||
|
id: string,
|
||||||
|
image?: IImageModel,
|
||||||
|
nickName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IPostItemModel {
|
||||||
|
id: string,
|
||||||
|
slug: string,
|
||||||
|
badge?: string,
|
||||||
|
image?: IImageModel,
|
||||||
|
title: string,
|
||||||
|
shortText: string,
|
||||||
|
text: string,
|
||||||
|
author: IAuthorModel,
|
||||||
|
created: string,
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBlogItemModel extends IPostItemModel {
|
||||||
|
readTime: number,
|
||||||
|
likes: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IShopItemModel extends IPostItemModel {
|
||||||
|
images?: IImageModel [],
|
||||||
|
sku: string,
|
||||||
|
rating?: number,
|
||||||
|
price: number,
|
||||||
|
newPrice?: number,
|
||||||
|
quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IPostPaginationModel {
|
||||||
|
currentPage: number,
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBlogItemsPaginationModel extends IPostPaginationModel {
|
||||||
|
items: IBlogItemModel []
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IShopItemsPaginationModel extends IPostPaginationModel {
|
||||||
|
items: IShopItemModel []
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICategoryModel {
|
||||||
|
id: string,
|
||||||
|
text: string
|
||||||
|
}
|
||||||
13
clientapp/src/httpQueries/shopCatalog.ts
Normal file
13
clientapp/src/httpQueries/shopCatalog.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const apiUrl = 'https://localhost:59018/api/Blogs'
|
||||||
|
|
||||||
|
export interface IGetBlogsRequest {
|
||||||
|
[key: string]: string | undefined
|
||||||
|
category?: string,
|
||||||
|
searchText?: string,
|
||||||
|
currentPage?: string,
|
||||||
|
itemsPerPage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBlogsResponse {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,23 +1,87 @@
|
|||||||
import React from 'react'
|
import React, { FC, useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Card, CardBody, CardHeader, CardImg, Col, Container, Row } from 'reactstrap'
|
import { Card, CardBody, CardFooter, CardHeader, CardImg, Col, Container, Row } from 'reactstrap'
|
||||||
|
import { dateFormat } from '../../../functions'
|
||||||
|
|
||||||
|
|
||||||
|
import { GetBlogs, IGetBlogsResponse } from '../../../httpQueries/blogs'
|
||||||
|
import { IBlogItemModel, IBlogItemsPaginationModel } from '../../../httpQueries/models'
|
||||||
|
|
||||||
|
|
||||||
import { Categories, Empty, Search } from '../SideWidgets'
|
import { Categories, Empty, Search } from '../SideWidgets'
|
||||||
|
|
||||||
|
|
||||||
|
const FeaturedBlog: FC<IBlogItemModel> = (props) => {
|
||||||
|
const { id, slug, badge, image, title, shortText, author, created, readTime, likes, tags } = props
|
||||||
|
|
||||||
|
return <Card className="mb-4 shadow border-0">
|
||||||
|
<CardImg top {...image} />
|
||||||
|
<CardBody className="p-4">
|
||||||
|
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>
|
||||||
|
<Link className="text-decoration-none link-dark stretched-link" to={`blog/item/${slug}`}>
|
||||||
|
<h5 className="card-title mb-3">{title}</h5>
|
||||||
|
</Link>
|
||||||
|
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: shortText }}></p>
|
||||||
|
</CardBody>
|
||||||
|
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
|
||||||
|
<div className="d-flex align-items-end justify-content-between">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<img className="rounded-circle me-3" {...author.image} />
|
||||||
|
<div className="small">
|
||||||
|
<div className="fw-bold">{author.nickName}</div>
|
||||||
|
<div className="text-muted">{dateFormat(created)} · Time to read: {readTime} min</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlogPagination: FC<IBlogItemsPaginationModel> = (props) => {
|
||||||
|
const { items } = props
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{items.map((item, index) => <Col key={index} className="lg-6">
|
||||||
|
<Card className="mb-4">
|
||||||
|
|
||||||
|
<CardImg top {...item.image} />
|
||||||
|
|
||||||
|
<CardBody>
|
||||||
|
<div className="small text-muted">{item.created}</div>
|
||||||
|
<h2 className="card-title h4">{item.title}</h2>
|
||||||
|
<p className="card-text">{item.shortText}</p>
|
||||||
|
<Link to={`${item.slug}`} className="btn btn-primary">Read more →</Link>
|
||||||
|
</CardBody>
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
</Col>)}
|
||||||
|
|
||||||
|
<nav aria-label="Pagination">
|
||||||
|
<hr className="my-0" />
|
||||||
|
<ul className="pagination justify-content-center my-4">
|
||||||
|
<li className="page-item disabled"><a className="page-link" href="#" aria-disabled="true">Newer</a></li>
|
||||||
|
<li className="page-item active" aria-current="page"><a className="page-link" href="#!">1</a></li>
|
||||||
|
<li className="page-item"><a className="page-link" href="#!">2</a></li>
|
||||||
|
<li className="page-item"><a className="page-link" href="#!">3</a></li>
|
||||||
|
<li className="page-item disabled"><a className="page-link" href="#!">...</a></li>
|
||||||
|
<li className="page-item"><a className="page-link" href="#!">15</a></li>
|
||||||
|
<li className="page-item"><a className="page-link" href="#!">Older</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const BlogCatalog = () => {
|
const BlogCatalog = () => {
|
||||||
const items = [
|
const [state, setState] = useState<IGetBlogsResponse>()
|
||||||
{
|
|
||||||
slug: "1"
|
useEffect(() => {
|
||||||
},
|
GetBlogs().then(response => {
|
||||||
{
|
setState(response)
|
||||||
slug: "2"
|
})
|
||||||
},
|
}, [])
|
||||||
{
|
|
||||||
slug: "2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slugd: "2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<header className="py-5 bg-light border-bottom mb-4">
|
<header className="py-5 bg-light border-bottom mb-4">
|
||||||
@ -32,57 +96,16 @@ const BlogCatalog = () => {
|
|||||||
<Container fluid>
|
<Container fluid>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<Card className="mb-4">
|
{state?.featuredBlog ? <FeaturedBlog {...state.featuredBlog} /> : ''}
|
||||||
<Link to={`blog/item/featured`}>
|
|
||||||
<CardImg top src="https://dummyimage.com/850x350/dee2e6/6c757d.jpg" alt="..." />
|
|
||||||
</Link>
|
|
||||||
<CardBody>
|
|
||||||
<div className="small text-muted">January 1, 2022</div>
|
|
||||||
<h2 className="card-title">Featured Post Title</h2>
|
|
||||||
<p className="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla? Quos cum ex quis soluta, a laboriosam. Dicta expedita corporis animi vero voluptate voluptatibus possimus, veniam magni quis!</p>
|
|
||||||
<a className="btn btn-primary" href="#!">Read more →</a>
|
|
||||||
</CardBody>
|
|
||||||
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
{items.map((item, index) => <Col key={index} className="lg-6">
|
{state?.blogItemsPagination ? <BlogPagination {...state.blogItemsPagination} /> : '' }
|
||||||
<Card className="mb-4">
|
|
||||||
|
|
||||||
<Link to={`${item.slug}`}>
|
|
||||||
<CardImg top src="https://dummyimage.com/850x350/dee2e6/6c757d.jpg" alt="..." />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<CardBody>
|
|
||||||
<div className="small text-muted">January 1, 2022</div>
|
|
||||||
<h2 className="card-title h4">Post Title</h2>
|
|
||||||
<p className="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla.</p>
|
|
||||||
<a className="btn btn-primary" href="#!">Read more →</a>
|
|
||||||
</CardBody>
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
</Col>)}
|
|
||||||
|
|
||||||
|
|
||||||
<nav aria-label="Pagination">
|
|
||||||
<hr className="my-0" />
|
|
||||||
<ul className="pagination justify-content-center my-4">
|
|
||||||
<li className="page-item disabled"><a className="page-link" href="#" aria-disabled="true">Newer</a></li>
|
|
||||||
<li className="page-item active" aria-current="page"><a className="page-link" href="#!">1</a></li>
|
|
||||||
<li className="page-item"><a className="page-link" href="#!">2</a></li>
|
|
||||||
<li className="page-item"><a className="page-link" href="#!">3</a></li>
|
|
||||||
<li className="page-item disabled"><a className="page-link" href="#!">...</a></li>
|
|
||||||
<li className="page-item"><a className="page-link" href="#!">15</a></li>
|
|
||||||
<li className="page-item"><a className="page-link" href="#!">Older</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg="4">
|
<Col lg="4">
|
||||||
<Search />
|
<Search />
|
||||||
<Categories />
|
{state?.categories ? <Categories {...{
|
||||||
|
categories: state.categories
|
||||||
|
}} /> : '' }
|
||||||
<Empty/>
|
<Empty/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||||
|
import { ICategoryModel } from '../../../httpQueries/models'
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
return <Card className="mb-4">
|
return <Card className="mb-4">
|
||||||
@ -13,23 +14,35 @@ const Search = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
}
|
}
|
||||||
|
|
||||||
const Categories = () => {
|
export interface ICategories {
|
||||||
|
categories?: ICategoryModel []
|
||||||
|
}
|
||||||
|
|
||||||
|
const Categories = (props: ICategories) => {
|
||||||
|
const { categories } = props
|
||||||
|
|
||||||
|
if(!categories) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const middleIndex = Math.ceil(categories.length / 2)
|
||||||
|
|
||||||
|
const firstHalf = categories.splice(0, middleIndex)
|
||||||
|
const secondHalf = categories.splice(-middleIndex)
|
||||||
|
|
||||||
return <Card className="mb-4">
|
return <Card className="mb-4">
|
||||||
<CardHeader>Categories</CardHeader>
|
<CardHeader>Categories</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Row>
|
<Row>
|
||||||
|
|
||||||
<Col sm="6">
|
<Col sm="6">
|
||||||
<ul className="list-unstyled mb-0">
|
<ul className="list-unstyled mb-0">
|
||||||
<li><a href="#!">Web Design</a></li>
|
{firstHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||||
<li><a href="#!">HTML</a></li>
|
|
||||||
<li><a href="#!">Freebies</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Col>
|
</Col>
|
||||||
<Col sm="6">
|
<Col sm="6">
|
||||||
<ul className="list-unstyled mb-0">
|
<ul className="list-unstyled mb-0">
|
||||||
<li><a href="#!">JavaScript</a></li>
|
{secondHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||||
<li><a href="#!">CSS</a></li>
|
|
||||||
<li><a href="#!">Tutorials</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@ -3265,6 +3265,11 @@ data-urls@^2.0.0:
|
|||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
|
dayjs@^1.11.2:
|
||||||
|
version "1.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
|
||||||
|
integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
|
debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
|||||||
25
webapi/.dockerignore
Normal file
25
webapi/.dockerignore
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
12
webapi/Core/Abstractions/DomainObjects/Category.cs
Normal file
12
webapi/Core/Abstractions/DomainObjects/Category.cs
Normal 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 class Category {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
webapi/Core/Abstractions/DomainObjects/DomainObject.cs
Normal file
10
webapi/Core/Abstractions/DomainObjects/DomainObject.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Abstractions.DomainObjects {
|
||||||
|
public abstract class DomainObject {
|
||||||
|
}
|
||||||
|
}
|
||||||
35
webapi/Core/Abstractions/DomainObjects/PostItem.cs
Normal file
35
webapi/Core/Abstractions/DomainObjects/PostItem.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Core.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Abstractions.DomainObjects {
|
||||||
|
|
||||||
|
public abstract class PostItem : DomainObject {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author / Owner
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public string Badge { get; set; }
|
||||||
|
|
||||||
|
public List<string> Tags { get; set; }
|
||||||
|
|
||||||
|
public List<string> Categories { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Edit dateTime, and Author
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<DateTime, Guid> Edited { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
webapi/Core/Abstractions/Models/RequestModel.cs
Normal file
10
webapi/Core/Abstractions/Models/RequestModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Abstractions.Models {
|
||||||
|
public abstract class RequestModel {
|
||||||
|
}
|
||||||
|
}
|
||||||
10
webapi/Core/Abstractions/Models/ResponseModel.cs
Normal file
10
webapi/Core/Abstractions/Models/ResponseModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Abstractions.Models {
|
||||||
|
public abstract class ResponseModel {
|
||||||
|
}
|
||||||
|
}
|
||||||
9
webapi/Core/Core.csproj
Normal file
9
webapi/Core/Core.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
9
webapi/Core/DomainObjects/BlogItem.cs
Normal file
9
webapi/Core/DomainObjects/BlogItem.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Core.Abstractions.DomainObjects;
|
||||||
|
|
||||||
|
namespace Core.DomainObjects {
|
||||||
|
internal class BlogItem : PostItem {
|
||||||
|
|
||||||
|
public int Likes { get; set; }
|
||||||
|
public int ReadingTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
webapi/Core/DomainObjects/ShopItem.cs
Normal file
17
webapi/Core/DomainObjects/ShopItem.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
29
webapi/Core/DomainObjects/User.cs
Normal file
29
webapi/Core/DomainObjects/User.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Core.Abstractions;
|
||||||
|
using Core.Abstractions.DomainObjects;
|
||||||
|
using Core.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.DomainObjects {
|
||||||
|
|
||||||
|
public class User : DomainObject {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string NickName { get; set; }
|
||||||
|
|
||||||
|
public string Hash { get; set; }
|
||||||
|
public string Salt { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string LastName { get; set; }
|
||||||
|
|
||||||
|
public Contact Email { get; set; }
|
||||||
|
public Contact Mobile { get; set; }
|
||||||
|
|
||||||
|
public Address BillingAddress { get; set; }
|
||||||
|
public Address ShippingAddress { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
webapi/Core/Entities/Address.cs
Normal file
14
webapi/Core/Entities/Address.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Entities {
|
||||||
|
public class Address {
|
||||||
|
public string Street { get; set; }
|
||||||
|
public string City { get; set; }
|
||||||
|
public string PostCode { get; set; }
|
||||||
|
public string Country { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
webapi/Core/Entities/Contact.cs
Normal file
12
webapi/Core/Entities/Contact.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Entities {
|
||||||
|
public class Contact {
|
||||||
|
public string Value { get; set; }
|
||||||
|
public bool IsConfirmed { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
15
webapi/Core/Models/PaginationModel.cs
Normal file
15
webapi/Core/Models/PaginationModel.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Core.Abstractions;
|
||||||
|
using Core.Abstractions.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Models {
|
||||||
|
public class PaginationModel<T> {
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
public int CurrentPage { get; set; }
|
||||||
|
public List<T> Items { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
webapi/DataProviders/BlogDataProvider.cs
Normal file
14
webapi/DataProviders/BlogDataProvider.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace DataProviders;
|
||||||
|
|
||||||
|
public interface IBlogDataProvider { }
|
||||||
|
|
||||||
|
public class BlogDataProvider : IBlogDataProvider {
|
||||||
|
|
||||||
|
private readonly IDataProvidersConfiguration _configuration;
|
||||||
|
|
||||||
|
public BlogDataProvider(IDataProvidersConfiguration configuration) {
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
9
webapi/DataProviders/DataProviders.csproj
Normal file
9
webapi/DataProviders/DataProviders.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
15
webapi/DataProviders/IDataProvidersConfiguration.cs
Normal file
15
webapi/DataProviders/IDataProvidersConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
webapi/DataProviders/ShopDataProvider.cs
Normal file
16
webapi/DataProviders/ShopDataProvider.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DataProviders {
|
||||||
|
|
||||||
|
public interface IShopDataProvider {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShopDataProvider : IShopDataProvider {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
10
webapi/DataProviders/SiteSettingsDataProvider.cs
Normal file
10
webapi/DataProviders/SiteSettingsDataProvider.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DataProviders {
|
||||||
|
public class SiteSettingsDataProvider {
|
||||||
|
}
|
||||||
|
}
|
||||||
40
webapi/Services/HashService/HashService.cs
Normal file
40
webapi/Services/HashService/HashService.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||||
|
|
||||||
|
namespace Services {
|
||||||
|
|
||||||
|
public interface IHashService {
|
||||||
|
(string, string) CreateSaltedHash(string value);
|
||||||
|
bool ValidateHash(string value, string salt, string hash);
|
||||||
|
}
|
||||||
|
public class HashService : IHashService {
|
||||||
|
private string CreateSalt() {
|
||||||
|
byte[] randomBytes = new byte[128 / 8];
|
||||||
|
using (var generator = RandomNumberGenerator.Create()) {
|
||||||
|
generator.GetBytes(randomBytes);
|
||||||
|
return Convert.ToBase64String(randomBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateHash(string value, string salt) {
|
||||||
|
var valueBytes = KeyDerivation.Pbkdf2(
|
||||||
|
password: value,
|
||||||
|
salt: Encoding.UTF8.GetBytes(salt),
|
||||||
|
prf: KeyDerivationPrf.HMACSHA512,
|
||||||
|
iterationCount: 10000,
|
||||||
|
numBytesRequested: 256 / 8);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(valueBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string, string) CreateSaltedHash(string value) {
|
||||||
|
var salt = CreateSalt();
|
||||||
|
var hash = CreateHash(value, salt);
|
||||||
|
|
||||||
|
return (salt, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateHash(string value, string salt, string hash) => CreateHash(value, salt) == hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
webapi/Services/HashService/HashService.csproj
Normal file
13
webapi/Services/HashService/HashService.csproj
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
7
webapi/Services/JWTService/IJWTServiceConfiguration.cs
Normal file
7
webapi/Services/JWTService/IJWTServiceConfiguration.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Services {
|
||||||
|
public interface IJwtServiceSettings {
|
||||||
|
string Secret { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
51
webapi/Services/JWTService/JWTService.cs
Normal file
51
webapi/Services/JWTService/JWTService.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace Services {
|
||||||
|
|
||||||
|
public interface IJwtService {
|
||||||
|
string CreateJwtToken(IEnumerable<string> issuer, DateTime expires, string userId, string userEmail, string userName, IEnumerable<string> userRoles);
|
||||||
|
JwtSecurityToken ReadJwtToken(string token);
|
||||||
|
}
|
||||||
|
public class JwtService : IJwtService {
|
||||||
|
private readonly JwtSecurityTokenHandler _tokenHandler;
|
||||||
|
private readonly IJwtServiceSettings _serviceSettings;
|
||||||
|
|
||||||
|
public JwtService(IJwtServiceSettings serviceSettings) {
|
||||||
|
_serviceSettings = serviceSettings;
|
||||||
|
_tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CreateJwtToken(IEnumerable<string> issuer, DateTime expires, string userId, string userEmail, string userName, IEnumerable<string> userRoles) {
|
||||||
|
var key = Convert.FromBase64String(_serviceSettings.Secret);
|
||||||
|
|
||||||
|
// add roles to claims identity from database
|
||||||
|
var claims = new List<Claim>() {
|
||||||
|
new Claim(ClaimTypes.Actor, userId),
|
||||||
|
new Claim(ClaimTypes.Email, userEmail),
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userName),
|
||||||
|
// new Claim(ClaimTypes.Webpage, issuer)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var role in userRoles)
|
||||||
|
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||||
|
|
||||||
|
foreach (var iss in issuer)
|
||||||
|
claims.Add(new Claim(ClaimTypes.Webpage, iss));
|
||||||
|
|
||||||
|
var token = _tokenHandler.CreateToken(new SecurityTokenDescriptor {
|
||||||
|
IssuedAt = DateTime.UtcNow,
|
||||||
|
Subject = new ClaimsIdentity(claims),
|
||||||
|
Expires = expires,
|
||||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature),
|
||||||
|
});
|
||||||
|
|
||||||
|
return _tokenHandler.WriteToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtSecurityToken ReadJwtToken(string token) => _tokenHandler.ReadJwtToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
webapi/Services/JWTService/JWTService.csproj
Normal file
13
webapi/Services/JWTService/JWTService.csproj
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.18.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -5,6 +5,18 @@ VisualStudioVersion = 17.0.31912.275
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherForecast", "WeatherForecast\WeatherForecast.csproj", "{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherForecast", "WeatherForecast\WeatherForecast.csproj", "{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{113EE574-E047-4727-AA36-841F845504D5}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HashService", "Services\HashService\HashService.csproj", "{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTService", "Services\JWTService\JWTService.csproj", "{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataProviders", "DataProviders\DataProviders.csproj", "{13EDFAD4-5D8B-4879-96F7-D896265FB0DC}"
|
||||||
|
EndProject
|
||||||
|
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1FE09D24-5FC7-4EDD-AC19-C06DB9C035DB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -15,10 +27,34 @@ Global
|
|||||||
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{13EDFAD4-5D8B-4879-96F7-D896265FB0DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{13EDFAD4-5D8B-4879-96F7-D896265FB0DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{13EDFAD4-5D8B-4879-96F7-D896265FB0DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{13EDFAD4-5D8B-4879-96F7-D896265FB0DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1FE09D24-5FC7-4EDD-AC19-C06DB9C035DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1FE09D24-5FC7-4EDD-AC19-C06DB9C035DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1FE09D24-5FC7-4EDD-AC19-C06DB9C035DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1FE09D24-5FC7-4EDD-AC19-C06DB9C035DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B} = {113EE574-E047-4727-AA36-841F845504D5}
|
||||||
|
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7} = {113EE574-E047-4727-AA36-841F845504D5}
|
||||||
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73}
|
SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
5
webapi/WeatherForecast/Configuration.cs
Normal file
5
webapi/WeatherForecast/Configuration.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace WeatherForecast {
|
||||||
|
public class Configuration {
|
||||||
|
public string Secret { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
107
webapi/WeatherForecast/Controllers/BlogsController.cs
Normal file
107
webapi/WeatherForecast/Controllers/BlogsController.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using Core.Models;
|
||||||
|
|
||||||
|
using WeatherForecast.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Core.Abstractions.Models;
|
||||||
|
|
||||||
|
namespace WeatherForecast.Controllers;
|
||||||
|
|
||||||
|
#region Input models
|
||||||
|
public class GetBlogsResponse : ResponseModel {
|
||||||
|
|
||||||
|
public BlogItemModel FeaturedBlog { get; set; }
|
||||||
|
|
||||||
|
public List<CategoryModel> Categories { get; set; }
|
||||||
|
|
||||||
|
public PaginationModel<BlogItemModel> BlogItemsPagination { get; set; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class BlogsController : ControllerBase {
|
||||||
|
|
||||||
|
private readonly ILogger<LoginController> _logger;
|
||||||
|
|
||||||
|
public BlogsController(ILogger<LoginController> logger) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentPage"></param>
|
||||||
|
/// <param name="itemsPerPage"></param>
|
||||||
|
/// <param name="category"></param>
|
||||||
|
/// <param name="searchText"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Get([FromQuery] Guid? category, [FromQuery] string? searchText, [FromQuery] int currentPage = 1, [FromQuery] int itemsPerPage = 4) {
|
||||||
|
var blogItemModel = new BlogItemModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Slug = "blog-post-title",
|
||||||
|
Image = new ImageModel { Src = "https://dummyimage.com/850x350/dee2e6/6c757d.jpg", Alt = "..." },
|
||||||
|
Badge = "news",
|
||||||
|
Title = "Blog post title",
|
||||||
|
ShortText = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||||
|
Author = new AuthorModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Image = new ImageModel { Src = "https://dummyimage.com/40x40/ced4da/6c757d", Alt = "..." },
|
||||||
|
NickName = "Admin"
|
||||||
|
},
|
||||||
|
Created = DateTime.UtcNow,
|
||||||
|
ReadTime = 10,
|
||||||
|
Likes = 200,
|
||||||
|
Tags = new List<string> { "react", "redux", "webapi" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var blogModels = new List<BlogItemModel>();
|
||||||
|
for (int i = 0; i < itemsPerPage; i++) {
|
||||||
|
blogModels.Add(blogItemModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
var blogResponse = new GetBlogsResponse {
|
||||||
|
FeaturedBlog = blogItemModel,
|
||||||
|
BlogItemsPagination = new PaginationModel<BlogItemModel> {
|
||||||
|
CurrentPage = currentPage,
|
||||||
|
TotalPages = 100,
|
||||||
|
Items = blogModels
|
||||||
|
},
|
||||||
|
Categories = new List<CategoryModel> {
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "Web Design"
|
||||||
|
},
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "Html"
|
||||||
|
},
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "Freebies"
|
||||||
|
},
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "Javascript"
|
||||||
|
},
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "CSS"
|
||||||
|
},
|
||||||
|
new CategoryModel {
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Text = "Tutorials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Ok(blogResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
webapi/WeatherForecast/Controllers/LoginController.cs
Normal file
31
webapi/WeatherForecast/Controllers/LoginController.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Core.Abstractions.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using WeatherForecast.Models;
|
||||||
|
|
||||||
|
namespace WeatherForecast.Controllers;
|
||||||
|
|
||||||
|
public class PostLoginRequest : RequestModel {
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class LoginController : ControllerBase {
|
||||||
|
|
||||||
|
private readonly ILogger<LoginController> _logger;
|
||||||
|
|
||||||
|
public LoginController(ILogger<LoginController> logger) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost(Name = "Login")]
|
||||||
|
public IActionResult Post([FromBody] PostLoginRequest requestBody) {
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
47
webapi/WeatherForecast/Controllers/ShopCatalog.cs
Normal file
47
webapi/WeatherForecast/Controllers/ShopCatalog.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Core.Abstractions.Models;
|
||||||
|
using Core.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using WeatherForecast.Models;
|
||||||
|
|
||||||
|
namespace WeatherForecast.Controllers;
|
||||||
|
|
||||||
|
#region Response models
|
||||||
|
public class GetShopCatalogResponse : ResponseModel {
|
||||||
|
|
||||||
|
public PaginationModel<ShopItemModel> ShopItemsPagination { get; set; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class ShopCatalog : ControllerBase {
|
||||||
|
|
||||||
|
private readonly ILogger<LoginController> _logger;
|
||||||
|
|
||||||
|
public ShopCatalog(ILogger<LoginController> logger) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category"></param>
|
||||||
|
/// <param name="searchText"></param>
|
||||||
|
/// <param name="currentPage"></param>
|
||||||
|
/// <param name="itemsPerPage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Get([FromQuery] Guid? category, [FromQuery] string? searchText, [FromQuery] int currentPage = 1, [FromQuery] int itemsPerPage = 4) {
|
||||||
|
var shopItemModel = new ShopItemModel {
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,22 @@
|
|||||||
using Microsoft.AspNetCore.Cors;
|
using Core.Abstractions.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using WeatherForecast.Models;
|
||||||
|
|
||||||
namespace WeatherForecast.Controllers;
|
namespace WeatherForecast.Controllers;
|
||||||
|
|
||||||
|
#region Response models
|
||||||
|
public class GetWeatherForecastResponse : ResponseModel {
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
@ -21,10 +35,9 @@ public class WeatherForecastController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
[HttpGet(Name = "GetWeatherForecast")]
|
||||||
public IEnumerable<WeatherForecast> Get()
|
public IEnumerable<GetWeatherForecastResponse> Get()
|
||||||
{
|
{
|
||||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
return Enumerable.Range(1, 5).Select(index => new GetWeatherForecastResponse {
|
||||||
{
|
|
||||||
Date = DateTime.Now.AddDays(index),
|
Date = DateTime.Now.AddDays(index),
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
TemperatureC = Random.Shared.Next(-20, 55),
|
||||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||||
|
|||||||
23
webapi/WeatherForecast/Dockerfile
Normal file
23
webapi/WeatherForecast/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["WeatherForecast/WeatherForecast.csproj", "WeatherForecast/"]
|
||||||
|
COPY ["Core/Core.csproj", "Core/"]
|
||||||
|
RUN dotnet restore "WeatherForecast/WeatherForecast.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/WeatherForecast"
|
||||||
|
RUN dotnet build "WeatherForecast.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "WeatherForecast.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "WeatherForecast.dll"]
|
||||||
7
webapi/WeatherForecast/Models/AuthorModel.cs
Normal file
7
webapi/WeatherForecast/Models/AuthorModel.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class AuthorModel {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public ImageModel Image { get; set; }
|
||||||
|
public string NickName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
webapi/WeatherForecast/Models/BlogItemModel.cs
Normal file
11
webapi/WeatherForecast/Models/BlogItemModel.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class BlogItemModel : PostItemModel {
|
||||||
|
|
||||||
|
|
||||||
|
public int ReadTime { get; set; }
|
||||||
|
|
||||||
|
public int Likes { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
6
webapi/WeatherForecast/Models/CategoryModel.cs
Normal file
6
webapi/WeatherForecast/Models/CategoryModel.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class CategoryModel {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
6
webapi/WeatherForecast/Models/ImageModel.cs
Normal file
6
webapi/WeatherForecast/Models/ImageModel.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class ImageModel {
|
||||||
|
public string Src { get; set; }
|
||||||
|
public string Alt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
23
webapi/WeatherForecast/Models/PostItemModel.cs
Normal file
23
webapi/WeatherForecast/Models/PostItemModel.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class PostItemModel {
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Slug { get; set; }
|
||||||
|
|
||||||
|
public ImageModel Image { get; set; }
|
||||||
|
|
||||||
|
public string Badge { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public string ShortText { get; set; }
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public AuthorModel Author { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
|
public List<string> Tags { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
webapi/WeatherForecast/Models/ShopItemModel.cs
Normal file
10
webapi/WeatherForecast/Models/ShopItemModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace WeatherForecast.Models {
|
||||||
|
public class ShopItemModel : PostItemModel {
|
||||||
|
public List<ImageModel> Images { get; set; }
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,41 +1,13 @@
|
|||||||
var builder = WebApplication.CreateBuilder(args);
|
namespace WeatherForecast {
|
||||||
|
public class Program {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder => {
|
||||||
builder.Services.AddCors(options =>
|
webBuilder.UseStartup<Startup>();
|
||||||
{
|
});
|
||||||
options.AddPolicy(name: MyAllowSpecificOrigins,
|
}
|
||||||
builder => {
|
|
||||||
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add services to the container.
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
|
||||||
|
|
||||||
app.UseRouting();
|
|
||||||
|
|
||||||
app.UseCors(MyAllowSpecificOrigins);
|
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
"iisSettings": {
|
"iisSettings": {
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
@ -11,13 +11,13 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"WeatherForecast": {
|
"WeatherForecast": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:7151;http://localhost:5133",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
},
|
||||||
|
"applicationUrl": "https://localhost:7151;http://localhost:5133",
|
||||||
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
@ -26,6 +26,13 @@
|
|||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
166
webapi/WeatherForecast/Startup.cs
Normal file
166
webapi/WeatherForecast/Startup.cs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
|
||||||
|
namespace WeatherForecast {
|
||||||
|
public class Startup {
|
||||||
|
|
||||||
|
public IConfiguration _configuration { get; }
|
||||||
|
|
||||||
|
string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration) {
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services) {
|
||||||
|
string serverHostName = Environment.MachineName;
|
||||||
|
|
||||||
|
// configure strongly typed settings objects
|
||||||
|
var appSettingsSection = _configuration.GetSection("Configuration");
|
||||||
|
services.Configure<Configuration>(appSettingsSection);
|
||||||
|
var appSettings = appSettingsSection.Get<Configuration>();
|
||||||
|
|
||||||
|
|
||||||
|
services.AddCors(options => {
|
||||||
|
options.AddPolicy(MyAllowSpecificOrigins,
|
||||||
|
builder => {
|
||||||
|
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
services.AddControllers();
|
||||||
|
|
||||||
|
|
||||||
|
// configure jwt authentication
|
||||||
|
services.AddAuthentication(options => {
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
}).AddJwtBearer(options => {
|
||||||
|
options.RequireHttpsMetadata = false;
|
||||||
|
options.SaveToken = true;
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters {
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(appSettings.Secret)),
|
||||||
|
ValidateIssuer = false,
|
||||||
|
ValidateAudience = false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1#use-httpcontext-from-custom-components
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
#region Swagger
|
||||||
|
services.ConfigureSwaggerGen(options => {
|
||||||
|
// your custom configuration goes here
|
||||||
|
// UseFullTypeNameInSchemaIds replacement for .NET Core
|
||||||
|
options.CustomSchemaIds(x => x.FullName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the Swagger generator, defining 1 or more Swagger documents
|
||||||
|
services.AddSwaggerGen(config => {
|
||||||
|
//c.SerializeAsV2 = true,
|
||||||
|
config.SwaggerDoc("v2", new OpenApiInfo {
|
||||||
|
Title = "MAKS-IT WEB API",
|
||||||
|
Version = "v2",
|
||||||
|
Description = "Site support webapi for blogs or e-commerce",
|
||||||
|
|
||||||
|
// TermsOfService = new Uri(""),
|
||||||
|
/*
|
||||||
|
Contact = new OpenApiContact
|
||||||
|
{
|
||||||
|
Name = "",
|
||||||
|
Email = "",
|
||||||
|
Url = new Uri(""),
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
License = new OpenApiLicense {
|
||||||
|
Name = "Use under ISC",
|
||||||
|
Url = new Uri("https://opensource.org/licenses/ISC")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the comments path for the Swagger JSON and UI.
|
||||||
|
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||||
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||||
|
config.IncludeXmlComments(xmlPath);
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/56234504/bearer-authentication-in-swagger-ui-when-migrating-to-swashbuckle-aspnetcore-ve
|
||||||
|
config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
|
||||||
|
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
|
||||||
|
Name = "Authorization",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Type = SecuritySchemeType.ApiKey,
|
||||||
|
Scheme = "Bearer"
|
||||||
|
});
|
||||||
|
|
||||||
|
config.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer"
|
||||||
|
},
|
||||||
|
Scheme = "oauth2",
|
||||||
|
Name = "Bearer",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
|
||||||
|
},
|
||||||
|
new List<string>()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); //This line
|
||||||
|
});
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) {
|
||||||
|
|
||||||
|
|
||||||
|
if (env.IsDevelopment()) {
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
|
||||||
|
// Enable middleware to serve generated Swagger as a JSON endpoint.
|
||||||
|
app.UseSwagger();
|
||||||
|
|
||||||
|
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
|
||||||
|
// specifying the Swagger JSON endpoint.
|
||||||
|
app.UseSwaggerUI(c => {
|
||||||
|
c.DefaultModelsExpandDepth(-1);
|
||||||
|
c.SwaggerEndpoint("/swagger/v2/swagger.json", "MAKS-IT WEB API v2");
|
||||||
|
// To serve the Swagger UI at the app's root (http://localhost:<port>/), set the RoutePrefix property to an empty string
|
||||||
|
c.RoutePrefix = "swagger";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// app.UseExceptionHandler("/Error");
|
||||||
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
|
// app.UseHsts();
|
||||||
|
}
|
||||||
|
//app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
// UseCors must be called before UseResponseCaching
|
||||||
|
app.UseCors(MyAllowSpecificOrigins);
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints => {
|
||||||
|
endpoints.MapControllers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
namespace WeatherForecast;
|
|
||||||
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
||||||
@ -5,10 +5,20 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>WeatherForecast</RootNamespace>
|
<RootNamespace>WeatherForecast</RootNamespace>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<UserSecretsId>2ea970dd-e71a-4c8e-9ff6-2d1d3123d4df</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Core\Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -5,5 +5,8 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"Configuration": {
|
||||||
|
"Secret": "TUlJQ1d3SUJBQUtCZ0djczU2dnIzTWRwa0VYczYvYjIyemxMWlhSaFdrSWtyN0dqUHB4ZkNpQk9FU2Q3L2VxcA=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
webapi/docker-compose.dcproj
Normal file
18
webapi/docker-compose.dcproj
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectVersion>2.1</ProjectVersion>
|
||||||
|
<DockerTargetOS>Linux</DockerTargetOS>
|
||||||
|
<ProjectGuid>1fe09d24-5fc7-4edd-ac19-c06db9c035db</ProjectGuid>
|
||||||
|
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
|
||||||
|
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
|
||||||
|
<DockerServiceName>weatherforecast</DockerServiceName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="docker-compose.override.yml">
|
||||||
|
<DependentUpon>docker-compose.yml</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="docker-compose.yml" />
|
||||||
|
<None Include=".dockerignore" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
13
webapi/docker-compose.override.yml
Normal file
13
webapi/docker-compose.override.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
weatherforecast:
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=https://+:443;http://+:80
|
||||||
|
ports:
|
||||||
|
- "80"
|
||||||
|
- "443"
|
||||||
|
volumes:
|
||||||
|
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
|
||||||
|
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
8
webapi/docker-compose.yml
Normal file
8
webapi/docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
weatherforecast:
|
||||||
|
image: ${DOCKER_REGISTRY-}weatherforecast
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: WeatherForecast/Dockerfile
|
||||||
Loading…
Reference in New Issue
Block a user