Compare commits

...

32 Commits

Author SHA1 Message Date
02bb73e0a6 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 21s
Create and publish a Docker image / Deploy image (push) Successful in 4s
2023-12-24 15:08:12 +03:00
620884f587 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 21s
Create and publish a Docker image / Deploy image (push) Successful in 4s
2023-12-24 15:06:19 +03:00
a59a1c27c7 авторизация
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 56s
Create and publish a Docker image / Deploy image (push) Successful in 4s
2023-12-24 15:01:37 +03:00
fc43da83eb сделал отправку на почту и присваивание ачивки за ранее
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m12s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 14:54:35 +03:00
d8b7d6b7d5 Очередной фикс авторизации
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 19s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 13:06:44 +03:00
3ab097df30 Обновил README
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 9s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 12:51:50 +03:00
d0ff5effd6 Фикс авторизации
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 20s
Create and publish a Docker image / Deploy image (push) Successful in 4s
2023-12-24 12:50:32 +03:00
15911d2c69 фиксы
Some checks failed
Create and publish a Docker image / Publish image (push) Failing after 14s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 12:44:42 +03:00
5bef2b0dbc сделал мерж
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 20s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 12:35:30 +03:00
0a840a32dc фиксы 2023-12-24 12:33:51 +03:00
651bd5b822 Добавил схему бд
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 10s
Create and publish a Docker image / Deploy image (push) Successful in 2s
2023-12-24 12:30:38 +03:00
5167076340 Фикс
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 56s
Create and publish a Docker image / Deploy image (push) Successful in 3s
2023-12-24 12:01:51 +03:00
ac987781fa Добавил Google auth
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 55s
Create and publish a Docker image / Deploy image (push) Successful in 39s
2023-12-24 04:58:01 +03:00
b5477d529f авторизация
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m18s
Create and publish a Docker image / Deploy image (push) Successful in 38s
2023-12-24 04:16:18 +03:00
d2d2685672 обновил gitignore
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m19s
Create and publish a Docker image / Deploy image (push) Successful in 39s
2023-12-24 01:48:29 +03:00
b4b5bfc1ed сделал авторизация доабвил оптимизацию
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m26s
Create and publish a Docker image / Deploy image (push) Successful in 38s
2023-12-23 23:34:14 +03:00
6a32bc4fa9 сделал пользователей
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m26s
Create and publish a Docker image / Deploy image (push) Successful in 37s
2023-12-23 18:34:51 +03:00
a1019db433 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m23s
Create and publish a Docker image / Deploy image (push) Successful in 36s
2023-12-23 17:39:46 +03:00
f2d083c877 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 53s
Create and publish a Docker image / Deploy image (push) Successful in 36s
2023-12-23 15:16:17 +03:00
63db345729 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m24s
Create and publish a Docker image / Deploy image (push) Successful in 36s
2023-12-23 15:08:00 +03:00
f3ec89dbe9 добавил логику
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m23s
Create and publish a Docker image / Deploy image (push) Successful in 36s
2023-12-23 14:05:03 +03:00
4ca6c0da9b добавил логики
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m48s
Create and publish a Docker image / Deploy image (push) Successful in 35s
2023-12-23 12:06:50 +03:00
dba01cdcc6 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 50s
Create and publish a Docker image / Deploy image (push) Successful in 33s
2023-12-23 02:30:45 +03:00
414f66dbd0 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 49s
Create and publish a Docker image / Deploy image (push) Successful in 34s
2023-12-23 02:29:05 +03:00
cda564870b Исправление бд
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 50s
Create and publish a Docker image / Deploy image (push) Successful in 34s
2023-12-23 02:25:04 +03:00
b66e0edda7 фиксы
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 1m16s
Create and publish a Docker image / Deploy image (push) Successful in 34s
2023-12-23 02:00:59 +03:00
acfda9f35a delete https redirections
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 49s
Create and publish a Docker image / Deploy image (push) Successful in 33s
2023-12-23 01:58:00 +03:00
0eeebb753a change swagger
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 50s
Create and publish a Docker image / Deploy image (push) Successful in 33s
2023-12-23 01:49:53 +03:00
a599fc1650 add init file folder
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 49s
Create and publish a Docker image / Deploy image (push) Successful in 33s
2023-12-23 01:38:14 +03:00
147124e00e make api
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 50s
Create and publish a Docker image / Deploy image (push) Successful in 33s
2023-12-23 01:18:04 +03:00
368648248c merge
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 50s
Create and publish a Docker image / Deploy image (push) Successful in 32s
2023-12-23 01:14:17 +03:00
620fd310a9 add fields 2023-12-23 01:11:00 +03:00
37 changed files with 1260 additions and 301 deletions

2
.gitignore vendored
View File

@@ -475,3 +475,5 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
CyberBoom/appsettings.Development.json

13
CyberBoom/AuthOptions.cs Normal file
View File

@@ -0,0 +1,13 @@
using System.Text;
using Microsoft.IdentityModel.Tokens;
public class AuthOptions
{
public const string ISSUER = "MyAuthServer"; // издатель токена
public const string AUDIENCE = "MyAuthClient"; // потребитель токена
const string KEY = "mysupersecret_secretkey!123"; // ключ для шифрации
public const int LIFETIME = 1;
public static SymmetricSecurityKey GetSymmetricSecurityKey() =>
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(KEY));
}

View File

@@ -0,0 +1,52 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public class BearerAccessTokenOptions
{
public string GetBearerToken(List<Claim> claims) => new JwtSecurityTokenHandler()
.WriteToken(
JwtSecurityToken(claims)
);
public JwtSecurityToken JwtSecurityToken(List<Claim> claims) => new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
notBefore: DateTime.UtcNow,
claims: claims,
expires: BearerTokenLifeTime,
signingCredentials: SigningCredentials
);
public string SecurityAlgorithm { get; set; } = SecurityAlgorithms.HmacSha256;
public SigningCredentials SigningCredentials => new SigningCredentials(GetSymmetricSecurityKey, SecurityAlgorithm);
public DateTime BearerTokenLifeTime => DateTime.UtcNow.Add(TimeSpan.FromHours(Lifetime));
public bool RequiredHttpsMetadata { get; set; } = false;
public string Issuer { get; set; } = "BackendHackathon";
public string Audience { get; set; } = "BackendHackathon";
public string Key { get; set; } = "5XGgEtGK9jsNxIUQvxef7wtAE6LwbLWd6LFwpryYoF9w==";
public int Lifetime { get; set; } = 100;
public SymmetricSecurityKey GetSymmetricSecurityKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Key));
public TokenValidationParameters TokenValidationParameters => new()
{
// укзывает, будет ли валидироваться издатель при валидации токена
ValidateIssuer = true,
// строка, представляющая издателя
ValidIssuer = Issuer,
// будет ли валидироваться потребитель токена
ValidateAudience = true,
// установка потребителя токена
ValidAudience = Audience,
// будет ли валидироваться время существования
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
// установка ключа безопасности
IssuerSigningKey = GetSymmetricSecurityKey,
// валидация ключа безопасности
ValidateIssuerSigningKey = true,
};
}

4
CyberBoom/Consts.cs Normal file
View File

@@ -0,0 +1,4 @@
public static class Consts
{
public const char TOKENS_SEPORATOR = ';';
}

View File

@@ -0,0 +1,76 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace CyberBoom.Controllers;
[Authorize]
[ApiController]
[Route("/api/[controller]")]
public class MeetingsController : ControllerBase
{
private readonly ApplicationContext _applicationContext;
public MeetingsController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
[HttpPost]
public async Task<IActionResult> Post([FromForm] PostMeetingDto meeting)
{
await meeting.SpeackerImage.WriteFilesToDirectory();
await meeting.PlaceImages.WriteFilesToDirectory();
var meetingWrite = meeting.Adapt<Meeting>();
meetingWrite.SpeackerImage = meeting.SpeackerImage.JoinFileNames();
meetingWrite.PlaceImages = meeting.PlaceImages.JoinFileNames();
await _applicationContext.Meetings.AddAsync(meetingWrite);
await _applicationContext.SaveChangesAsync();
return Ok(new { meetingWrite.Id });
}
[HttpPut]
public async Task<IActionResult> Put([FromForm] PutMeetingDto meeting)
{
await meeting.SpeackerImage.WriteFilesToDirectory();
await meeting.PlaceImages.WriteFilesToDirectory();
var meetingWrite = meeting.Adapt<Meeting>();
meetingWrite.SpeackerImage = meeting.SpeackerImage.JoinFileNames();
meetingWrite.PlaceImages = meeting.PlaceImages.JoinFileNames();
_applicationContext.Entry(meetingWrite).State = EntityState.Modified;
_applicationContext.Update(meetingWrite);
//findedMeeting = meetingWrite;
await _applicationContext.SaveChangesAsync();
return Ok();
}
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var meeting = await _applicationContext.Meetings.FirstOrDefaultAsync(s => s.Id == id);
return Ok(meeting);
}
[AllowAnonymous]
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
{
var meetings = _applicationContext.Meetings.AsNoTracking().Skip(offset).Take(limit);
return Ok(meetings);
}
}

View File

@@ -0,0 +1,63 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace CyberBoom.Controllers;
[ApiController]
[Route("/api/[controller]")]
public class QuestionsController : ControllerBase
{
private readonly ApplicationContext _applicationContext;
public QuestionsController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
[Authorize]
[HttpPost]
public async Task<IActionResult> Post([FromBody] PostQuestionDto question)
{
var dbWr = question.Adapt<Question>();
await _applicationContext.Questions.AddAsync(dbWr);
await _applicationContext.SaveChangesAsync();
return Ok(new { dbWr.Id });
}
[Authorize]
[HttpPut]
public async Task<IActionResult> Put([FromBody] PutQuestionDto question)
{
var fReview = await _applicationContext.Questions.FirstAsync(r => r.Id == question.Id);
fReview.Text = question.Text;
await _applicationContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var question = await _applicationContext.Questions.FirstAsync(s => s.Id == id);
return Ok(question);
}
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
{
var questions = _applicationContext.Questions
.AsNoTracking()
.Include(c => c.User)
.Skip(offset)
.Take(limit);
return Ok(questions);
}
}

View File

@@ -0,0 +1,60 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace CyberBoom.Controllers;
[ApiController]
[Route("/api/[controller]")]
public class ReactionsController : ControllerBase
{
private readonly ApplicationContext _applicationContext;
public ReactionsController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
[Authorize]
[HttpPost]
public async Task<IActionResult> Post([FromBody] PostReactionDto reaction)
{
var dbWr = reaction.Adapt<Reaction>();
await _applicationContext.Reactions.AddAsync(dbWr);
await _applicationContext.SaveChangesAsync();
return Ok(new { dbWr.Id });
}
[Authorize]
[HttpDelete]
public async Task<IActionResult> Delete(string id)
{
var fReview = await _applicationContext.Reactions.FirstAsync(r => r.Id == id);
_applicationContext.Reactions.Remove(fReview);
await _applicationContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var reaction = await _applicationContext.Reactions.FirstAsync(s => s.Id == id);
return Ok(reaction);
}
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
{
var reactions = _applicationContext.Reactions.AsNoTracking().Skip(offset).Take(limit);
return Ok(reactions);
}
}

View File

@@ -0,0 +1,73 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace CyberBoom.Controllers;
[ApiController]
[Route("/api/[controller]")]
public class ReviewsController : ControllerBase
{
private readonly ApplicationContext _applicationContext;
public ReviewsController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
[Authorize]
[HttpPost]
public async Task<IActionResult> Post([FromBody] PostReviewDto review)
{
var dbWr = review.Adapt<Review>();
await _applicationContext.Reviews.AddAsync(dbWr);
await _applicationContext.SaveChangesAsync();
return Ok(new { dbWr.Id });
}
[Authorize]
[HttpPut]
public async Task<IActionResult> Put([FromBody] PutReviewDto review)
{
var fReview = await _applicationContext.Reviews.FirstAsync(r => r.Id == review.Id);
fReview.Text = review.Text;
fReview.Score = review.Score;
fReview.Date = review.Date;
await _applicationContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var review = await _applicationContext.Reviews.FirstAsync(s => s.Id == id);
var user = await _applicationContext.Users.FirstAsync(s => s.Id == review.UserId);
return Ok(new { review, user });
}
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
{
var reviews = _applicationContext.Reviews
.AsNoTracking()
.Include(c => c.User)
.Skip(offset)
.Take(limit);
// var userIds = reviews.Select(u => u.UserId).ToArray();
// var users = _applicationContext.Users.Where(u => userIds.Contains(u.Id));
return Ok(reviews);
}
}

View File

@@ -1,132 +1,253 @@
using System.IdentityModel.Tokens.Jwt;
using Mapster;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
namespace CyberBoom.Controllers;
[ApiController]
[Route("/api/[controller]")]
public class UserController : ControllerBase
public class UsersController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<UserController> _logger;
private readonly ApplicationContext _applicationContext;
public UserController(ILogger<UserController> logger, ApplicationContext applicationContext)
private readonly UserManager<User> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public UsersController(
ApplicationContext applicationContext,
UserManager<User> userManager,
RoleManager<IdentityRole> roleManager
)
{
_logger = logger;
_applicationContext = applicationContext;
_userManager = userManager;
_roleManager = roleManager;
}
// [HttpGet("google-auth")]
// public IActionResult Regiester()
// {
// var properties = new AuthenticationProperties{
// RedirectUri = Url.Action("GoogleResponse")
// };
// return Challenge(properties, GoogleDefaults.AuthenticationScheme);
// }
// [Route("google-response")]
// public async Task<IActionResult> GoogleResponse()
// {
// var result = await HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
// var claims = result?.Principal?.Identities.First().Claims;
// var jwt = new JwtSecurityToken(
// issuer: AuthOptions.ISSUER,
// audience: AuthOptions.AUDIENCE,
// claims: claims,
// expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(2)),
// signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256));
// var strJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
// return Ok(new {
// Token = strJwt
// });
// }
}
[ApiController]
[Route("/api/[controller]")]
public class MeetingsController : ControllerBase
{
private static readonly string[] Summaries = new[]
async Task AddUerToRole(User user, string role)
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
var isExists = await _roleManager.RoleExistsAsync(role);
private readonly ILogger<UserController> _logger;
if (!isExists)
{
var roleResult = await _roleManager.CreateAsync(new IdentityRole(role));
if (!roleResult.Succeeded)
throw new Exception("cannot create role");
}
private readonly ApplicationContext _applicationContext;
var addingRole = await _userManager.AddToRoleAsync(user, role);
public MeetingsController(ILogger<UserController> logger, ApplicationContext applicationContext)
{
_logger = logger;
_applicationContext = applicationContext;
if (!addingRole.Succeeded)
throw new Exception("cannot create role");
}
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Post([FromForm]PostMeetingDto meeting)
public async Task<IActionResult> Post([FromForm] UserPost user)
{
await meeting.SpeackerImage.WriteFileToDirectory();
var meetingWrite = meeting.Adapt<Meeting>();
meetingWrite.SpeackerImage = meeting.SpeackerImage.JoinFileNames();
await _applicationContext.Meetings.AddAsync(meetingWrite);
await user.Avatar.WriteFileToDirectory();
var userWr = new User
{
AvatarUrl = user.Avatar.FileName,
Fio = user.Fio,
Specialities = user.Specialities,
TelegramBotUrl = user.TelegramBotUrl,
UserName = user.Username,
Email = user.Email
};
var result = await _userManager.CreateAsync(userWr);
await _applicationContext.SaveChangesAsync();
if (!result.Succeeded)
return BadRequest(result.Errors);
var role = user.Username == "moderator" ? "модератор" : "спикер";
await AddUerToRole(userWr, role);
var token = GetToken(userWr, role);
return Ok(new { userWr.Id, Token = token });
}
string GetToken(User user, string role)
{
var claims = new List<Claim>
{
new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName!),
new Claim(ClaimsIdentity.DefaultRoleClaimType, role)
};
var bOpt = new BearerAccessTokenOptions();
return bOpt.GetBearerToken(claims);
}
[Authorize(Roles = "модератор")]
[HttpPut]
public async Task<IActionResult> Put([FromForm] UserPut user)
{
await user.Avatar.WriteFileToDirectory();
var fuser = await _userManager.FindByIdAsync(user.Id);
if (fuser is null)
throw new Exception("user not found");
fuser.AvatarUrl = user.Avatar.FileName;
fuser.Fio = user.Fio;
fuser.Specialities = user.Specialities;
fuser.TelegramBotUrl = user.TelegramBotUrl;
fuser.UserName = user.Username;
fuser.Email = user.Email;
var result = await _userManager.UpdateAsync(fuser);
if (result.Succeeded)
return Ok();
return BadRequest(result.Errors);
}
[Authorize(Roles = "модератор")]
[HttpPost("moderator")]
public async Task<IActionResult> PostModerator([FromForm] UserPost user)
{
await user.Avatar.WriteFileToDirectory();
var userWr = new User
{
AvatarUrl = user.Avatar.FileName,
Fio = user.Fio,
Specialities = user.Specialities,
TelegramBotUrl = user.TelegramBotUrl,
UserName = user.Username
};
var result = await _userManager.CreateAsync(userWr);
if (!result.Succeeded)
return BadRequest(result.Errors);
var role = "модератор";
await AddUerToRole(userWr, role);
var token = GetToken(userWr, role);
return Ok(new { userWr.Id, Token = token });
}
[AllowAnonymous]
[HttpGet("signin-google")]
public IActionResult SignInWithGoogle()
{
var properties = new AuthenticationProperties { RedirectUri = Url.Action(nameof(SignInWithGoogleCallback)) };
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
[AllowAnonymous]
[HttpGet("signin-google-callback")]
public async Task<IActionResult> SignInWithGoogleCallback()
{
var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (result?.Succeeded != true)
{
return BadRequest("Ошибка аутентификации Google");
}
// Извлеките информацию о пользователе из результата аутентификации
var claims = result.Principal!.Identities
.FirstOrDefault(y => y.AuthenticationType == GoogleDefaults.AuthenticationScheme)?
.Claims;
var email = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)!.Value;
var name = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Name)!.Value;
var user = await _userManager.FindByEmailAsync(email!);
var role = "спикер";
if(user is null)
{
user = new User
{
Fio = name!,
Specialities = string.Empty,
TelegramBotUrl = string.Empty,
AvatarUrl = $"https://www.gravatar.com/avatar/{BitConverter.ToString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(email!))).Replace("-", "").ToLowerInvariant()}?d=identicon",
UserName = name,
Email = email
};
var createResult = await _userManager.CreateAsync(user);
if (!createResult.Succeeded)
return BadRequest(createResult.Errors);
await AddUerToRole(user, role);
}
var token = GetToken(user, role);
// Здесь вы можете создать JWT или другой токен для аутентификации в вашем приложении
// и отправить его пользователю.
return Ok(new {
meetingWrite.Id
Token = token,
User = user
});
}
[HttpPut]
public async Task<IActionResult> Put([FromForm]PutMeetingDto meeting)
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(string email)
{
await meeting.SpeackerImage.WriteFileToDirectory();
var meetingWrite = meeting.Adapt<Meeting>();
meetingWrite.SpeackerImage = meeting.SpeackerImage.JoinFileNames();
var findedMeeting = await _applicationContext.Meetings.FirstAsync(s => s.Id == meeting.Id);
findedMeeting = meetingWrite;
var user = await _userManager.FindByEmailAsync(email);
await _applicationContext.SaveChangesAsync();
return Ok();
if(user is null)
return BadRequest();
var role = await _userManager.GetRolesAsync(user);
var token = GetToken(user, role.First());
return Ok(new {
token,
user
});
}
[Authorize]
[HttpGet]
public async Task<IActionResult> Get(int id)
public async Task<IActionResult> GetUserData(string id)
{
var meeting = await _applicationContext.Meetings.FirstOrDefaultAsync(s => s.Id == id);
var user = await _userManager.FindByIdAsync(id);
return Ok(meeting);
if (user is null)
return BadRequest();
var role = await _userManager.GetRolesAsync(user);
return Ok(new { user, role });
}
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
[Authorize]
[HttpGet("stats")]
public async Task<IActionResult> GetUserStats(string id)
{
var meetings = _applicationContext.Meetings.Skip(offset).Take(limit);
var user = await _userManager.FindByIdAsync(id);
return Ok(meetings);
if (user is null)
return BadRequest();
var stats = await _applicationContext.GetStatistic(id);
var achievmnets = _applicationContext.Achievments.Where(c => c.UserId == id);
return Ok(new { Stats = stats, Achievments = achievmnets });
}
}

View File

@@ -0,0 +1,173 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Hangfire;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
namespace CyberBoom.Controllers;
[ApiController]
[Route("/api/users/meetings")]
public class UserWriteToMetingController : ControllerBase
{
private readonly ApplicationContext _applicationContext;
public UserWriteToMetingController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
[Authorize]
[HttpPost]
public async Task<IActionResult> Post([FromBody] PostUserWriteToMetingDto write)
{
var dbWr = write.Adapt<UserWriteToMeting>();
var meeting = await _applicationContext.Meetings.FirstAsync(m => m.Id == write.MeetingId);
if (DateTime.UtcNow > meeting.Time)
return BadRequest();
await _applicationContext.UserWriteToMetings.AddAsync(dbWr);
var user = await _applicationContext.Users.FirstAsync(u => u.Id == write.UserId);
var delay = meeting.Time - DateTime.UtcNow - new TimeSpan(2, 0, 0);
BackgroundJob.Schedule<IEmailService>((_emailService) => _emailService.SendEmailAsync(user.Email!, "Вход в Муза", $"Здравcтвуйте. У вас мероприятие через 2 часа под названием {meeting.Title}"), delay); // за начало до встречи
BackgroundJob.Schedule<ApplicationContext>((context) => AfterEnd(write.UserId, context), meeting.Time - DateTime.UtcNow); // за начало до встречи
await _applicationContext.SaveChangesAsync();
return Ok(new { dbWr.Id});
}
async Task<List<Achievment>> WriteAchievment(StatsData stats, string userId, ApplicationContext context)
{
List<Achievment> achievments = new List<Achievment>();
if (stats.Count > 0 && stats.Count % 5 == 0)
{
var achievment = new Achievment
{
Name = $"Редстоун Наблюдатель Level {stats.Count / 5}",
Text =
"Вы cамый настоящий Редстоун Наблюдатель из игры Майнкрафт, который не пропускате ни единой всречи!",
UserId = userId
};
achievments.Add(achievment);
await context.Achievments.AddAsync(achievment);
}
var achievedTags = stats.StatsByTag.Where(st => st.Count > 0 && st.Count % 5 == 0);
if (achievedTags.Count() > 0 && stats.Count % 5 == 0)
{
foreach (var tag in achievedTags)
{
var achievment = new Achievment
{
Name = $"Вкачаю все в {tag.Tag} Level {tag.Count / 5}",
Text =
$"Вы нежалеете очки времени на ветку {tag.Tag}, продолжайте в том же духе!",
UserId = userId
};
achievments.Add(achievment);
await context.Achievments.AddAsync(achievment);
}
}
return achievments;
}
[Authorize]
[HttpDelete]
public async Task<IActionResult> Delete(string id)
{
var fReview = await _applicationContext.UserWriteToMetings.FirstAsync(r => r.Id == id);
var meeting = await _applicationContext.Meetings.FirstAsync(m => m.Id == fReview.MeetingId);
if (DateTime.UtcNow > meeting.Time)
return BadRequest();
_applicationContext.UserWriteToMetings.Remove(fReview);
await _applicationContext.SaveChangesAsync();
return Ok();
}
async Task AfterEnd(string userId, ApplicationContext context)
{
var newStats = await context.GetStatistic(userId);
await WriteAchievment(newStats, userId, context);
await context.SaveChangesAsync();
}
[HttpGet]
public async Task<IActionResult> Get(string id)
{
var review = await _applicationContext.UserWriteToMetings.FirstAsync(s => s.Id == id);
return Ok(review);
}
[HttpGet("list")]
public IActionResult GetList(int offset, int limit)
{
var reviews = _applicationContext.UserWriteToMetings
.AsNoTracking()
.Skip(offset)
.Take(limit);
return Ok(reviews);
}
}
public interface IEmailService
{
public Task SendEmailAsync(string email, string subject, string message);
}
public class EmailService : IEmailService
{
private readonly IConfiguration _configuration;
private ILogger<EmailService> _logger;
public EmailService(IConfiguration configuration, ILogger<EmailService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task SendEmailAsync(string email, string subject, string message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress(_configuration["Email:Name"], _configuration["Email:Address"]));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Html)
{
Text = message
};
using var client = new SmtpClient();
client.Timeout = 15000;
await client.ConnectAsync(_configuration["Email:Host"], int.Parse(_configuration["Email:Port"]!), SecureSocketOptions.None);
await client.AuthenticateAsync(_configuration["Email:Address"], _configuration["Email:Password"]);
await client.SendAsync(emailMessage);
_logger.LogInformation("Письмо отправлено на почту {Email}", email);
await client.DisconnectAsync(true);
}
}

View File

@@ -9,6 +9,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.Core" Version="1.8.6" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.20.4" />
<PackageReference Include="Hangfire.PostgreSql.Npgsql5" Version="1.19.11" />
<PackageReference Include="MailKit" Version="4.3.0" />
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="7.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.14" />

View File

@@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CyberBoom", "CyberBoom.csproj", "{A8F9D7D8-DDA5-479B-8735-00E706A5827F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A8F9D7D8-DDA5-479B-8735-00E706A5827F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8F9D7D8-DDA5-479B-8735-00E706A5827F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8F9D7D8-DDA5-479B-8735-00E706A5827F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8F9D7D8-DDA5-479B-8735-00E706A5827F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {674E1E42-AF3F-4DDA-A4F1-EDD84025DB24}
EndGlobalSection
EndGlobal

86
CyberBoom/DataHelpers.cs Normal file
View File

@@ -0,0 +1,86 @@
using Microsoft.EntityFrameworkCore;
using static Consts;
public static class DataHelpers
{
public static string JoinFileNames(this IEnumerable<IFormFile> files) => files.Select(s => s.FileName).JoinStrings();
public static string JoinStrings(this IEnumerable<string> files) => String.Join(TOKENS_SEPORATOR, files.Select(s => s));
public static TimeSpan ParseDuration(this string duration)
{
var durArr = duration.Split(TOKENS_SEPORATOR, StringSplitOptions.RemoveEmptyEntries);
var hours = int.Parse(durArr.First());
var minutes = int.Parse(durArr[1]);
return new TimeSpan(hours, minutes, 0);
}
public static async Task WriteFileToDirectory(this IFormFile file)
{
var readStream = file.OpenReadStream();
var memstream = new MemoryStream();
await readStream.CopyToAsync(memstream);
await File.WriteAllBytesAsync(Path.Combine("cyber-boom-files", file.FileName), memstream.ToArray());
}
public static async Task WriteFilesToDirectory(this IEnumerable<IFormFile> files)
{
foreach(var file in files)
{
await file.WriteFileToDirectory();
}
}
public static async Task<StatsData> GetStatistic(this ApplicationContext applicationContext, string id)
{
var specialities = await applicationContext.UserWriteToMetings.Where(c => c.UserId == id)
.Join(applicationContext.Meetings,
m => m.MeetingId,
m => m.Id,
(c,m) => new {
m.Tags,
m.Id,
m.Duration,
m.Time
}
).Where(t => DateTime.UtcNow > t.Time).ToArrayAsync();
var selectedSpecialities = specialities.Select(s => new {
s.Id,
Tags = s.Tags.Split(TOKENS_SEPORATOR, StringSplitOptions.RemoveEmptyEntries),
Duration = s.Duration.ParseDuration().TotalHours
});
var allTags = selectedSpecialities.SelectMany(s => s.Tags).Distinct();
var count = selectedSpecialities.Count();
StatsData stats = new StatsData{
Count = count,
Hours = selectedSpecialities.Sum(m => m.Duration) * count
};
foreach(var tag in allTags)
{
//StatsData.TagStats
var specByTag = selectedSpecialities.Where(f => f.Tags.Contains(tag));
var countByTag = specByTag.Count();
var hours = selectedSpecialities.Sum(s => s.Duration) * countByTag;
var stat = new StatsData.TagStats
{
Count = countByTag,
Tag = tag,
Hours = hours
};
stats.StatsByTag.Add(stat);
}
return stats;
}
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
public class Achievment
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public string UserId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Text { get; set; } = null!;
}

View File

@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class ApplicationContext : IdentityDbContext<User>
{
public DbSet<Meeting> Meetings { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<Reaction> Reactions { get; set; }
public DbSet<UserWriteToMeting> UserWriteToMetings { get; set; }
public DbSet<Question> Questions { get; set; }
public DbSet<Achievment> Achievments { get; set; }
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Meeting>().HasMany<Review>().WithOne().HasForeignKey(c => c.MeetingId);
builder.Entity<Meeting>().HasMany<Question>().WithOne().HasForeignKey(c => c.MeetingId);
builder.Entity<Reaction>().HasOne<User>().WithMany().HasForeignKey(c => c.UserId);
builder.Entity<Review>().HasOne(c => c.User).WithMany().HasForeignKey(c => c.UserId);
builder.Entity<Question>().HasOne<Meeting>().WithMany().HasForeignKey(c => c.MeetingId);
builder.Entity<Question>().HasOne(c => c.User).WithMany().HasForeignKey(c => c.UserId);
builder.Entity<Reaction>().HasOne<Question>().WithMany().HasForeignKey(c => c.QuestionId);
builder.Entity<Meeting>().HasMany<UserWriteToMeting>().WithOne().HasForeignKey(c => c.MeetingId);
builder.Entity<UserWriteToMeting>().HasOne<User>().WithMany().HasForeignKey(c => c.UserId);
builder.Entity<Achievment>().HasOne<User>().WithMany().HasForeignKey(c => c.UserId);
}
}

View File

@@ -0,0 +1,31 @@
public class PostMeetingDto
{
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public IEnumerable<IFormFile> SpeackerImage { get; set; } = null!;
public IEnumerable<IFormFile> PlaceImages { get; set; } = null!;
public string Splecializations { get; set; } = null!;
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string Urls { get; set; } = null!;
public string PlaceAdress { get; set; } = null!;
public string Duration { get; set; } = null!;
}

View File

@@ -0,0 +1,33 @@
public class PutMeetingDto
{
public String Id { get; set; } = null!;
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public IEnumerable<IFormFile> SpeackerImage { get; set; } = null!;
public IEnumerable<IFormFile> PlaceImages { get; set; } = null!;
public string Splecializations { get; set; } = null!;
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string Urls { get; set; } = null!;
public string PlaceAdress { get; set; } = null!;
public string Duration { get; set; } = null!;
}

View File

@@ -0,0 +1,8 @@
public class PostQuestionDto
{
public string Text { get; set; } = null!;
public string MeetingId { get; set; } = null!;
public string UserId { get; set; } = null!;
}

View File

@@ -0,0 +1,6 @@
public class PutQuestionDto
{
public string Id { get; set; } = null!;
public string Text { get; set; } = null!;
}

View File

@@ -0,0 +1,9 @@
public class PostReactionDto
{
public string QuestionId { get; set; } = null!;
public string UserId { get; set; } = null!;
public bool IsLike { get; set; } = true;
}

View File

@@ -0,0 +1,14 @@
public class PostReviewDto
{
public String MeetingId { get; set; } = null!;
public String UserId { get; set; } = null!;
public string Text { get; set; } = null!;
public int Score { get; set; } = 0;
DateTime _date;
public DateTime Date { get => _date; set => _date = value.ToUniversalTime(); }
}

View File

@@ -0,0 +1,12 @@
public class PutReviewDto
{
public string Id { get; set; } = null!;
public string Text { get; set; } = null!;
public int Score { get; set; } = 0;
DateTime _date;
public DateTime Date { get => _date; set => _date = value.ToUniversalTime(); }
}

View File

@@ -0,0 +1,6 @@
public class PostUserWriteToMetingDto
{
public String UserId { get; set; } = null!;
public String MeetingId { get; set; } = null!;
}

View File

@@ -0,0 +1,17 @@
public class StatsData
{
public double Hours{ get; set; }
public int Count { get; set; }
public List<TagStats> StatsByTag { get; set; } = new List<TagStats>();
public class TagStats
{
public string Tag { get; set; } = null!;
public int Count { get; set; }
public double Hours { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
public class UserPost
{
public IFormFile Avatar { get; set; } = null!;
public string Fio { get; set; } = null!;
public string Username { get; set; } = null!;
public string Specialities { get; set; } = null!;
public string TelegramBotUrl { get; set; } = null!;
public string Email { get; set; } = null!;
}

View File

@@ -0,0 +1,16 @@
public class UserPut
{
public string Id { get; set; } = null!;
public IFormFile Avatar { get; set; } = null!;
public string Fio { get; set; } = null!;
public string Username { get; set; } = null!;
public string Specialities { get; set; } = null!;
public string TelegramBotUrl { get; set; } = null!;
public string Email { get; set; } = null!;
}

View File

@@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations.Schema;
public class Meeting
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public String Id { get; set; } = null!;
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public string SpeackerImage { get; set; } = null!;
public string Splecializations { get; set; } = null!; //speacker specilization
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string PlaceImages { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string Urls { get; set; } = null!;
public string PlaceAdress { get; set; } = null!;
public string Duration { get; set; } = null!;
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;
public class Question
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public string Text { get; set; } = null!;
public string MeetingId { get; set; } = null!;
public string UserId { get; set; } = null!;
public User User { get; set; } = null!;
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
public class Reaction
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public string QuestionId { get; set; } = null!;
public string UserId { get; set; } = null!;
public bool IsLike { get; set; } = true;
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations.Schema;
public class Review
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public string MeetingId { get; set; } = null!;
public string UserId { get; set; } = null!;
public string Text { get; set; } = null!;
public int Score { get; set; } = 0;
public User User { get; set; } = null!;
DateTime _date;
public DateTime Date { get => _date; set => _date = value.ToUniversalTime(); }
}

View File

@@ -1,107 +1,17 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class User : IdentityUser
{
public string AvatarUrl { get; set; } = null!;
public string Fio { get; set; } = null!;
public string Specialities { get; set; } = null!;
public string TelegramBotUrl { get; set; } = null!;
public int Level { get; set; }
}
public class PostMeetingDto
{
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public IEnumerable<IFormFile> SpeackerImage { get; set; } = null!;
public string Splecializations { get; set; } = null!;
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string VideoUrl { get; set; } = null!;
}
public class PutMeetingDto
{
public long Id { get; set; }
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public IEnumerable<IFormFile> SpeackerImage { get; set; } = null!;
public string Splecializations { get; set; } = null!;
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string VideoUrl { get; set; } = null!;
}
public class Meeting
{
public long Id { get; set; }
public DateTime Time { get; set; }
public string Title { get; set; } = null!;
public string Theme { get; set; } = null!;
public string SpeakerName { get; set; } = null!;
public string SpeackerImage { get; set; } = null!;
public string Splecializations { get; set; } = null!;
public string Type { get; set; } = "онлайн/офлайн";
public string SpeakerTelephone { get; set; } = null!;
public string SpeakerEmail { get; set; } = null!;
public string Tags { get; set; } = null!;
public string VideoUrl { get; set; } = null!;
}
public class ApplicationContext : IdentityDbContext<User>
{
public DbSet<Meeting> Meetings { get; set; }
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Meeting>();
}
}

View File

@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations.Schema;
public class UserWriteToMeting
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public String Id { get; set; } = null!;
public String UserId { get; set; } = null!;
public String MeetingId { get; set; } = null!;
}

View File

@@ -1,91 +1,127 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Mapster;
using static Consts;
using Microsoft.Extensions.FileProviders;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.HttpOverrides;
using Hangfire;
using Hangfire.PostgreSql;
using Hangfire.Dashboard;
TypeAdapterConfig<PutMeetingDto, Meeting>.NewConfig().Map(d => d.SpeackerImage, s => s.SpeackerImage.JoinFileNames());
TypeAdapterConfig<PostMeetingDto, Meeting>.NewConfig().Map(d => d.SpeackerImage, s => s.SpeackerImage.JoinFileNames());
TypeAdapterConfig<PostMeetingDto, Meeting>.NewConfig().Map(d => d.Splecializations, s => String.Join(FILES_SEPORATOR_IN_STORE, s.Splecializations));
TypeAdapterConfig<PostMeetingDto, Meeting>.NewConfig().Map(d => d.Splecializations, s => String.Join(TOKENS_SEPORATOR, s.Splecializations));
TypeAdapterConfig<PutMeetingDto, Meeting>.NewConfig().Map(d => d.Splecializations, s => String.Join(FILES_SEPORATOR_IN_STORE, s.Splecializations));
TypeAdapterConfig<PutMeetingDto, Meeting>.NewConfig().Map(d => d.Splecializations, s => String.Join(TOKENS_SEPORATOR, s.Splecializations));
TypeAdapterConfig<PostMeetingDto, Meeting>.NewConfig().Map(d => d.Time, s => s.Time.ToUniversalTime());
TypeAdapterConfig<PutMeetingDto, Meeting>.NewConfig().Map(d => d.Time, s => s.Time.ToUniversalTime());
var dir = Directory.CreateDirectory("cyber-boom-files");
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddDbContext<ApplicationContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
options.UseNpgsql(builder.Configuration["CONNECTION_STRING"]));
builder.Services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<ApplicationContext>();
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// указывает, будет ли валидироваться издатель при валидации токена
ValidateIssuer = true,
// строка, представляющая издателя
ValidIssuer = AuthOptions.ISSUER,
// будет ли валидироваться потребитель токена
ValidateAudience = true,
// установка потребителя токена
ValidAudience = AuthOptions.AUDIENCE,
// будет ли валидироваться время существования
ValidateLifetime = true,
// установка ключа безопасности
IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
// валидация ключа безопасности
ValidateIssuerSigningKey = true,
};
})
.AddGoogle(googleOptions =>
builder.Services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"] ?? throw new NullReferenceException("");
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"] ?? throw new NullReferenceException("");
var bearerOptions = new BearerAccessTokenOptions();
options.RequireHttpsMetadata = bearerOptions.RequiredHttpsMetadata;
options.TokenValidationParameters = bearerOptions.TokenValidationParameters;
})
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Google:ClientId"]!;
options.ClientSecret = builder.Configuration["Google:ClientSecret"]!;
options.CallbackPath = "/api/signin-google";
});
// builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
// .AddEntityFrameworkStores<ApplicationContext>();
// builder.Services.AddRazorPages();
builder.Services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UsePostgreSqlStorage(c =>
c.UseNpgsqlConnection(builder.Configuration["CONNECTION_STRING"])));
//.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection")));
// Add the processing server as IHostedService
builder.Services.AddHangfireServer();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
builder.Services.AddCors();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors(builder => builder.AllowAnyMethod());
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "cyber-boom-files")),
RequestPath = "/cyber-boom-files"
RequestPath = "/api/cyber-boom-files"
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseSwagger();
app.UseSwaggerUI();
@@ -93,43 +129,31 @@ app.UseAuthentication(); // подключение аутентификаци
app.UseAuthorization();
app.UseHangfireDashboard("/workers", new DashboardOptions
{
Authorization = new [] { new AdminAuthorizationFilter() }
});
app.MapControllers();
app.MapHangfireDashboard();
//app.MapRazorPages();
app.Run();
public class AuthOptions
public class AdminAuthorizationFilter : IDashboardAuthorizationFilter
{
public const string ISSUER = "MyAuthServer"; // издатель токена
public const string AUDIENCE = "MyAuthClient"; // потребитель токена
const string KEY = "mysupersecret_secretkey!123"; // ключ для шифрации
public static SymmetricSecurityKey GetSymmetricSecurityKey() =>
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(KEY));
}
public static class Consts
{
public const char FILES_SEPORATOR_IN_STORE = ';';
}
public static class PhileDataHelpers
{
public static string JoinFileNames(this IEnumerable<IFormFile> files) => files.Select(s => s.FileName).JoinStrings();
public static string JoinStrings(this IEnumerable<string> files) => String.Join(FILES_SEPORATOR_IN_STORE, files.Select(s => s));
public static async Task WriteFileToDirectory(this IEnumerable<IFormFile> files)
public bool Authorize(DashboardContext context)
{
var dir = Directory.CreateDirectory("cyber-boom-files");
foreach(var file in files)
{
var readStream = file.OpenReadStream();
var memstream = new MemoryStream();
await readStream.CopyToAsync(memstream);
await File.WriteAllBytesAsync(Path.Combine(dir.FullName, file.FileName), memstream.ToArray());
}
var user = context.GetHttpContext().User;
if (user.IsInRole("модератор"))
return true;
return false;
}
}

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -6,8 +6,6 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Host=localhost; Database=CyberBoomWellBeing; Username=postgres; Password=supper_password_123"
}
"CONNECTION_STRING": "Host=localhost; Database=CyberBoomWellBeing; Username=postgres; Password=supper_password_123"
}

View File

@@ -10,6 +10,9 @@
# Схема
![Схема](img/scheme.png "scheme")
# Схема БД
![Схема БД](img/db.jpg "scheme")
# Docker
```bash
@@ -34,5 +37,11 @@ services:
environment:
- CONNECTION_STRING=Host=192.168.0.94;Port=5432;Database=backend;Username=prod;Password=
- TZ=Europe/Moscow
- Google:ClientId=1074677274720-anq59r07nlu1nh7r444pg7llsts>
- Google:ClientSecret=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
volumes:
- filesvolume:/app/cyber-boom-files
image: 'git.zetcraft.ru/cybebloom/cybebloombackend:main'
volumes:
filesvolume:
```

BIN
img/db.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB