diff --git a/clientapp/package.json b/clientapp/package.json index 1b123df..9bffae3 100644 --- a/clientapp/package.json +++ b/clientapp/package.json @@ -5,6 +5,7 @@ "dependencies": { "bootstrap": "5.1.3", "classnames": "^2.3.1", + "dayjs": "^1.11.2", "history": "5.3.0", "merge": "^2.1.1", "react": "18.0.0", diff --git a/clientapp/src/functions/dateFormat.ts b/clientapp/src/functions/dateFormat.ts new file mode 100644 index 0000000..fff77b3 --- /dev/null +++ b/clientapp/src/functions/dateFormat.ts @@ -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 +} \ No newline at end of file diff --git a/clientapp/src/functions/index.ts b/clientapp/src/functions/index.ts index 8da12c7..fda4aa0 100644 --- a/clientapp/src/functions/index.ts +++ b/clientapp/src/functions/index.ts @@ -1,5 +1,7 @@ +import { dateFormat } from './dateFormat' import { getKeyValue } from './getKeyValue' export { - getKeyValue + getKeyValue, + dateFormat } \ No newline at end of file diff --git a/clientapp/src/httpQueries/blog.ts b/clientapp/src/httpQueries/blog.ts new file mode 100644 index 0000000..5df8e2e --- /dev/null +++ b/clientapp/src/httpQueries/blog.ts @@ -0,0 +1,3 @@ +import { IBlogItemModel, ICategoryModel, IFetchResult } from "./models" + +const apiUrl = 'https://localhost:59018/api/Blog' \ No newline at end of file diff --git a/clientapp/src/httpQueries/blogs.ts b/clientapp/src/httpQueries/blogs.ts new file mode 100644 index 0000000..f4538fc --- /dev/null +++ b/clientapp/src/httpQueries/blogs.ts @@ -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 => { + 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 +} \ No newline at end of file diff --git a/clientapp/src/httpQueries/models.ts b/clientapp/src/httpQueries/models.ts new file mode 100644 index 0000000..83ca426 --- /dev/null +++ b/clientapp/src/httpQueries/models.ts @@ -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 +} \ No newline at end of file diff --git a/clientapp/src/httpQueries/shopCatalog.ts b/clientapp/src/httpQueries/shopCatalog.ts new file mode 100644 index 0000000..b15e16d --- /dev/null +++ b/clientapp/src/httpQueries/shopCatalog.ts @@ -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 { + +} \ No newline at end of file diff --git a/clientapp/src/pages/Blog/Catalog/index.tsx b/clientapp/src/pages/Blog/Catalog/index.tsx index c12ebb7..b56f9a3 100644 --- a/clientapp/src/pages/Blog/Catalog/index.tsx +++ b/clientapp/src/pages/Blog/Catalog/index.tsx @@ -1,23 +1,87 @@ -import React from 'react' +import React, { FC, useEffect, useState } from 'react' 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' + +const FeaturedBlog: FC = (props) => { + const { id, slug, badge, image, title, shortText, author, created, readTime, likes, tags } = props + + return + + +
{badge}
+ +
{title}
+ +

+
+ +
+
+ +
+
{author.nickName}
+
{dateFormat(created)} · Time to read: {readTime} min
+
+
+
+
+
+ +} + +const BlogPagination: FC = (props) => { + const { items } = props + + return <> + {items.map((item, index) => + + + + + +
{item.created}
+

{item.title}

+

{item.shortText}

+ Read more → +
+ +
+ )} + + + +} + + + const BlogCatalog = () => { - const items = [ - { - slug: "1" - }, - { - slug: "2" - }, - { - slug: "2" - }, - { - slugd: "2" - } - ] + const [state, setState] = useState() + + useEffect(() => { + GetBlogs().then(response => { + setState(response) + }) + }, []) return <>
@@ -32,57 +96,16 @@ const BlogCatalog = () => { - - - - - -
January 1, 2022
-

Featured Post Title

-

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!

- Read more → -
- - -
- + {state?.featuredBlog ? : ''} - {items.map((item, index) => - - - - - - - -
January 1, 2022
-

Post Title

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis aliquid atque, nulla.

- Read more → -
- -
- )} - - - - + {state?.blogItemsPagination ? : '' }
- + {state?.categories ? : '' }
diff --git a/clientapp/src/pages/Blog/SideWidgets/index.tsx b/clientapp/src/pages/Blog/SideWidgets/index.tsx index 74d72ab..1988137 100644 --- a/clientapp/src/pages/Blog/SideWidgets/index.tsx +++ b/clientapp/src/pages/Blog/SideWidgets/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' +import { ICategoryModel } from '../../../httpQueries/models' const Search = () => { return @@ -13,23 +14,35 @@ const Search = () => { } -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 Categories + diff --git a/clientapp/yarn.lock b/clientapp/yarn.lock index 68f72c3..8b135ac 100644 --- a/clientapp/yarn.lock +++ b/clientapp/yarn.lock @@ -3265,6 +3265,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.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: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" diff --git a/webapi/.dockerignore b/webapi/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/webapi/.dockerignore @@ -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 \ No newline at end of file diff --git a/webapi/Core/Abstractions/DomainObjects/Category.cs b/webapi/Core/Abstractions/DomainObjects/Category.cs new file mode 100644 index 0000000..2db22a0 --- /dev/null +++ b/webapi/Core/Abstractions/DomainObjects/Category.cs @@ -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; } + } +} diff --git a/webapi/Core/Abstractions/DomainObjects/DomainObject.cs b/webapi/Core/Abstractions/DomainObjects/DomainObject.cs new file mode 100644 index 0000000..f2e5d2a --- /dev/null +++ b/webapi/Core/Abstractions/DomainObjects/DomainObject.cs @@ -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 { + } +} diff --git a/webapi/Core/Abstractions/DomainObjects/PostItem.cs b/webapi/Core/Abstractions/DomainObjects/PostItem.cs new file mode 100644 index 0000000..7198403 --- /dev/null +++ b/webapi/Core/Abstractions/DomainObjects/PostItem.cs @@ -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; } + + /// + /// Author / Owner + /// + public Guid UserId { get; set; } + + public string Title { get; set; } + + public string Text { get; set; } + + public string Badge { get; set; } + + public List Tags { get; set; } + + public List Categories { get; set; } + + public DateTime Created { get; set; } + + /// + /// Edit dateTime, and Author + /// + public Dictionary Edited { get; set; } + } +} diff --git a/webapi/Core/Abstractions/Models/RequestModel.cs b/webapi/Core/Abstractions/Models/RequestModel.cs new file mode 100644 index 0000000..fc52704 --- /dev/null +++ b/webapi/Core/Abstractions/Models/RequestModel.cs @@ -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 { + } +} diff --git a/webapi/Core/Abstractions/Models/ResponseModel.cs b/webapi/Core/Abstractions/Models/ResponseModel.cs new file mode 100644 index 0000000..d681f65 --- /dev/null +++ b/webapi/Core/Abstractions/Models/ResponseModel.cs @@ -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 { + } +} diff --git a/webapi/Core/Core.csproj b/webapi/Core/Core.csproj new file mode 100644 index 0000000..27ac386 --- /dev/null +++ b/webapi/Core/Core.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/webapi/Core/DomainObjects/BlogItem.cs b/webapi/Core/DomainObjects/BlogItem.cs new file mode 100644 index 0000000..f550290 --- /dev/null +++ b/webapi/Core/DomainObjects/BlogItem.cs @@ -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; } + } +} diff --git a/webapi/Core/DomainObjects/ShopItem.cs b/webapi/Core/DomainObjects/ShopItem.cs new file mode 100644 index 0000000..a5f9518 --- /dev/null +++ b/webapi/Core/DomainObjects/ShopItem.cs @@ -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; } + } +} diff --git a/webapi/Core/DomainObjects/User.cs b/webapi/Core/DomainObjects/User.cs new file mode 100644 index 0000000..527a2be --- /dev/null +++ b/webapi/Core/DomainObjects/User.cs @@ -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; } + } +} diff --git a/webapi/Core/Entities/Address.cs b/webapi/Core/Entities/Address.cs new file mode 100644 index 0000000..ec00d24 --- /dev/null +++ b/webapi/Core/Entities/Address.cs @@ -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; } + } +} diff --git a/webapi/Core/Entities/Contact.cs b/webapi/Core/Entities/Contact.cs new file mode 100644 index 0000000..50676d7 --- /dev/null +++ b/webapi/Core/Entities/Contact.cs @@ -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; } + } +} diff --git a/webapi/Core/Models/PaginationModel.cs b/webapi/Core/Models/PaginationModel.cs new file mode 100644 index 0000000..6668ae1 --- /dev/null +++ b/webapi/Core/Models/PaginationModel.cs @@ -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 { + public int TotalPages { get; set; } + public int CurrentPage { get; set; } + public List Items { get; set; } + } +} diff --git a/webapi/DataProviders/BlogDataProvider.cs b/webapi/DataProviders/BlogDataProvider.cs new file mode 100644 index 0000000..413bbc1 --- /dev/null +++ b/webapi/DataProviders/BlogDataProvider.cs @@ -0,0 +1,14 @@ +namespace DataProviders; + + public interface IBlogDataProvider { } + +public class BlogDataProvider : IBlogDataProvider { + + private readonly IDataProvidersConfiguration _configuration; + + public BlogDataProvider(IDataProvidersConfiguration configuration) { + _configuration = configuration; + } + +} + diff --git a/webapi/DataProviders/DataProviders.csproj b/webapi/DataProviders/DataProviders.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/webapi/DataProviders/DataProviders.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/webapi/DataProviders/IDataProvidersConfiguration.cs b/webapi/DataProviders/IDataProvidersConfiguration.cs new file mode 100644 index 0000000..01db840 --- /dev/null +++ b/webapi/DataProviders/IDataProvidersConfiguration.cs @@ -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; } + } +} diff --git a/webapi/DataProviders/ShopDataProvider.cs b/webapi/DataProviders/ShopDataProvider.cs new file mode 100644 index 0000000..861ca34 --- /dev/null +++ b/webapi/DataProviders/ShopDataProvider.cs @@ -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 { + + } +} diff --git a/webapi/DataProviders/SiteSettingsDataProvider.cs b/webapi/DataProviders/SiteSettingsDataProvider.cs new file mode 100644 index 0000000..bf595ff --- /dev/null +++ b/webapi/DataProviders/SiteSettingsDataProvider.cs @@ -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 { + } +} diff --git a/webapi/Services/HashService/HashService.cs b/webapi/Services/HashService/HashService.cs new file mode 100644 index 0000000..9f21b2a --- /dev/null +++ b/webapi/Services/HashService/HashService.cs @@ -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; + } +} diff --git a/webapi/Services/HashService/HashService.csproj b/webapi/Services/HashService/HashService.csproj new file mode 100644 index 0000000..0a6de5f --- /dev/null +++ b/webapi/Services/HashService/HashService.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/webapi/Services/JWTService/IJWTServiceConfiguration.cs b/webapi/Services/JWTService/IJWTServiceConfiguration.cs new file mode 100644 index 0000000..1d4376c --- /dev/null +++ b/webapi/Services/JWTService/IJWTServiceConfiguration.cs @@ -0,0 +1,7 @@ +using System.IO; + +namespace Services { + public interface IJwtServiceSettings { + string Secret { get; set; } + } +} diff --git a/webapi/Services/JWTService/JWTService.cs b/webapi/Services/JWTService/JWTService.cs new file mode 100644 index 0000000..8e0ccb7 --- /dev/null +++ b/webapi/Services/JWTService/JWTService.cs @@ -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 issuer, DateTime expires, string userId, string userEmail, string userName, IEnumerable 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 issuer, DateTime expires, string userId, string userEmail, string userName, IEnumerable userRoles) { + var key = Convert.FromBase64String(_serviceSettings.Secret); + + // add roles to claims identity from database + var claims = new List() { + 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); + } +} diff --git a/webapi/Services/JWTService/JWTService.csproj b/webapi/Services/JWTService/JWTService.csproj new file mode 100644 index 0000000..9b54a3d --- /dev/null +++ b/webapi/Services/JWTService/JWTService.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/webapi/WeatherForecast.sln b/webapi/WeatherForecast.sln index e95e8c4..890930b 100644 --- a/webapi/WeatherForecast.sln +++ b/webapi/WeatherForecast.sln @@ -5,6 +5,18 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherForecast", "WeatherForecast\WeatherForecast.csproj", "{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}" 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 GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.Release|Any CPU.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE 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 SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73} EndGlobalSection diff --git a/webapi/WeatherForecast/Configuration.cs b/webapi/WeatherForecast/Configuration.cs new file mode 100644 index 0000000..dd026c8 --- /dev/null +++ b/webapi/WeatherForecast/Configuration.cs @@ -0,0 +1,5 @@ +namespace WeatherForecast { + public class Configuration { + public string Secret { get; set; } + } +} diff --git a/webapi/WeatherForecast/Controllers/BlogsController.cs b/webapi/WeatherForecast/Controllers/BlogsController.cs new file mode 100644 index 0000000..42e5682 --- /dev/null +++ b/webapi/WeatherForecast/Controllers/BlogsController.cs @@ -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 Categories { get; set; } + + public PaginationModel BlogItemsPagination { get; set; } +} +#endregion + + +[AllowAnonymous] +[ApiController] +[Route("api/[controller]")] +public class BlogsController : ControllerBase { + + private readonly ILogger _logger; + + public BlogsController(ILogger logger) { + _logger = logger; + } + + /// + /// + /// + /// + /// + /// + /// + /// + [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 { "react", "redux", "webapi" } + }; + + var blogModels = new List(); + for (int i = 0; i < itemsPerPage; i++) { + blogModels.Add(blogItemModel); + } + + var blogResponse = new GetBlogsResponse { + FeaturedBlog = blogItemModel, + BlogItemsPagination = new PaginationModel { + CurrentPage = currentPage, + TotalPages = 100, + Items = blogModels + }, + Categories = new List { + 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); + } +} + diff --git a/webapi/WeatherForecast/Controllers/LoginController.cs b/webapi/WeatherForecast/Controllers/LoginController.cs new file mode 100644 index 0000000..12d5d46 --- /dev/null +++ b/webapi/WeatherForecast/Controllers/LoginController.cs @@ -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 _logger; + + public LoginController(ILogger logger) { + _logger = logger; + } + + [HttpPost(Name = "Login")] + public IActionResult Post([FromBody] PostLoginRequest requestBody) { + return BadRequest(); + } + + +} diff --git a/webapi/WeatherForecast/Controllers/ShopCatalog.cs b/webapi/WeatherForecast/Controllers/ShopCatalog.cs new file mode 100644 index 0000000..fe4c350 --- /dev/null +++ b/webapi/WeatherForecast/Controllers/ShopCatalog.cs @@ -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 ShopItemsPagination { get; set; } +} +#endregion + +[AllowAnonymous] +[ApiController] +[Route("api/[controller]")] +public class ShopCatalog : ControllerBase { + + private readonly ILogger _logger; + + public ShopCatalog(ILogger logger) { + _logger = logger; + } + + /// + /// + /// + /// + /// + /// + /// + /// + [HttpGet] + public IActionResult Get([FromQuery] Guid? category, [FromQuery] string? searchText, [FromQuery] int currentPage = 1, [FromQuery] int itemsPerPage = 4) { + var shopItemModel = new ShopItemModel { + + + }; + + + + + return Ok(); + } +} diff --git a/webapi/WeatherForecast/Controllers/WeatherForecastController.cs b/webapi/WeatherForecast/Controllers/WeatherForecastController.cs index b30af95..cd34fa9 100644 --- a/webapi/WeatherForecast/Controllers/WeatherForecastController.cs +++ b/webapi/WeatherForecast/Controllers/WeatherForecastController.cs @@ -1,8 +1,22 @@ -using Microsoft.AspNetCore.Cors; +using Core.Abstractions.Models; using Microsoft.AspNetCore.Mvc; +using WeatherForecast.Models; + 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] [Route("[controller]")] @@ -21,10 +35,9 @@ public class WeatherForecastController : ControllerBase } [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() + public IEnumerable Get() { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { + return Enumerable.Range(1, 5).Select(index => new GetWeatherForecastResponse { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] diff --git a/webapi/WeatherForecast/Dockerfile b/webapi/WeatherForecast/Dockerfile new file mode 100644 index 0000000..15f0270 --- /dev/null +++ b/webapi/WeatherForecast/Dockerfile @@ -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"] \ No newline at end of file diff --git a/webapi/WeatherForecast/Models/AuthorModel.cs b/webapi/WeatherForecast/Models/AuthorModel.cs new file mode 100644 index 0000000..a174e2e --- /dev/null +++ b/webapi/WeatherForecast/Models/AuthorModel.cs @@ -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; } + } +} diff --git a/webapi/WeatherForecast/Models/BlogItemModel.cs b/webapi/WeatherForecast/Models/BlogItemModel.cs new file mode 100644 index 0000000..19569fa --- /dev/null +++ b/webapi/WeatherForecast/Models/BlogItemModel.cs @@ -0,0 +1,11 @@ +namespace WeatherForecast.Models { + public class BlogItemModel : PostItemModel { + + + public int ReadTime { get; set; } + + public int Likes { get; set; } + + + } +} diff --git a/webapi/WeatherForecast/Models/CategoryModel.cs b/webapi/WeatherForecast/Models/CategoryModel.cs new file mode 100644 index 0000000..fdfa869 --- /dev/null +++ b/webapi/WeatherForecast/Models/CategoryModel.cs @@ -0,0 +1,6 @@ +namespace WeatherForecast.Models { + public class CategoryModel { + public Guid Id { get; set; } + public string Text { get; set; } + } +} diff --git a/webapi/WeatherForecast/Models/ImageModel.cs b/webapi/WeatherForecast/Models/ImageModel.cs new file mode 100644 index 0000000..1c6c6b4 --- /dev/null +++ b/webapi/WeatherForecast/Models/ImageModel.cs @@ -0,0 +1,6 @@ +namespace WeatherForecast.Models { + public class ImageModel { + public string Src { get; set; } + public string Alt { get; set; } + } +} diff --git a/webapi/WeatherForecast/Models/PostItemModel.cs b/webapi/WeatherForecast/Models/PostItemModel.cs new file mode 100644 index 0000000..7c84f96 --- /dev/null +++ b/webapi/WeatherForecast/Models/PostItemModel.cs @@ -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 Tags { get; set; } + } +} diff --git a/webapi/WeatherForecast/Models/ShopItemModel.cs b/webapi/WeatherForecast/Models/ShopItemModel.cs new file mode 100644 index 0000000..674ce8f --- /dev/null +++ b/webapi/WeatherForecast/Models/ShopItemModel.cs @@ -0,0 +1,10 @@ +namespace WeatherForecast.Models { + public class ShopItemModel : PostItemModel { + public List 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; } + } +} diff --git a/webapi/WeatherForecast/Program.cs b/webapi/WeatherForecast/Program.cs index 350006a..73de067 100644 --- a/webapi/WeatherForecast/Program.cs +++ b/webapi/WeatherForecast/Program.cs @@ -1,41 +1,13 @@ -var builder = WebApplication.CreateBuilder(args); +namespace WeatherForecast { + public class Program { + public static void Main(string[] args) { + CreateHostBuilder(args).Build().Run(); + } - -var MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; - -builder.Services.AddCors(options => -{ - 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(); + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { + webBuilder.UseStartup(); + }); + } } - -app.UseHttpsRedirection(); - -app.UseRouting(); - -app.UseCors(MyAllowSpecificOrigins); - -app.UseAuthorization(); - - -app.MapControllers(); - -app.Run(); diff --git a/webapi/WeatherForecast/Properties/launchSettings.json b/webapi/WeatherForecast/Properties/launchSettings.json index e4ca471..8f56e8e 100644 --- a/webapi/WeatherForecast/Properties/launchSettings.json +++ b/webapi/WeatherForecast/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, @@ -11,13 +11,13 @@ "profiles": { "WeatherForecast": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "https://localhost:7151;http://localhost:5133", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "applicationUrl": "https://localhost:7151;http://localhost:5133", + "dotnetRunMessages": true }, "IIS Express": { "commandName": "IISExpress", @@ -26,6 +26,13 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "publishAllPorts": true, + "useSSL": true } } -} +} \ No newline at end of file diff --git a/webapi/WeatherForecast/Startup.cs b/webapi/WeatherForecast/Startup.cs new file mode 100644 index 0000000..313e783 --- /dev/null +++ b/webapi/WeatherForecast/Startup.cs @@ -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(appSettingsSection); + var appSettings = appSettingsSection.Get(); + + + 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() + } + }); + + // 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:/), 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(); + }); + } + } +} + diff --git a/webapi/WeatherForecast/WeatherForecast.cs b/webapi/WeatherForecast/WeatherForecast.cs deleted file mode 100644 index c4fdae4..0000000 --- a/webapi/WeatherForecast/WeatherForecast.cs +++ /dev/null @@ -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; } -} diff --git a/webapi/WeatherForecast/WeatherForecast.csproj b/webapi/WeatherForecast/WeatherForecast.csproj index a242435..77ee597 100644 --- a/webapi/WeatherForecast/WeatherForecast.csproj +++ b/webapi/WeatherForecast/WeatherForecast.csproj @@ -5,10 +5,20 @@ enable enable WeatherForecast + true + 2ea970dd-e71a-4c8e-9ff6-2d1d3123d4df + Linux + ..\docker-compose.dcproj + + + + + + diff --git a/webapi/WeatherForecast/appsettings.json b/webapi/WeatherForecast/appsettings.json index 10f68b8..0439d21 100644 --- a/webapi/WeatherForecast/appsettings.json +++ b/webapi/WeatherForecast/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Configuration": { + "Secret": "TUlJQ1d3SUJBQUtCZ0djczU2dnIzTWRwa0VYczYvYjIyemxMWlhSaFdrSWtyN0dqUHB4ZkNpQk9FU2Q3L2VxcA==" + } } diff --git a/webapi/docker-compose.dcproj b/webapi/docker-compose.dcproj new file mode 100644 index 0000000..6f24878 --- /dev/null +++ b/webapi/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.1 + Linux + 1fe09d24-5fc7-4edd-ac19-c06db9c035db + LaunchBrowser + {Scheme}://localhost:{ServicePort}/swagger + weatherforecast + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/webapi/docker-compose.override.yml b/webapi/docker-compose.override.yml new file mode 100644 index 0000000..9af71fa --- /dev/null +++ b/webapi/docker-compose.override.yml @@ -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 \ No newline at end of file diff --git a/webapi/docker-compose.yml b/webapi/docker-compose.yml new file mode 100644 index 0000000..d2da61f --- /dev/null +++ b/webapi/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.4' + +services: + weatherforecast: + image: ${DOCKER_REGISTRY-}weatherforecast + build: + context: . + dockerfile: WeatherForecast/Dockerfile