db and auth refactoring

This commit is contained in:
Michele 2021-02-20 00:18:07 +00:00
parent 0e1c16ab91
commit aa472f9e29
45 changed files with 2182 additions and 697 deletions

View file

@ -4,4 +4,35 @@ to start the container run:
```
docker run --name=<name> -p:5000:80 -v /home/michele/sledgemapper/:/app/db -d privateregistry.michelescandura.com/michele/sledgemapper:<version>
```
Curl
Register user
```
curl -i -X POST -H "Content-Type: application/json" http://localhost:5000/users/register -d '{\"FirstName\":\"Michele\",\"LastName\":\"Scandura\", \"Username\":\"michele.scandura@outlook.com\", \"Password\":\"password1\", \"Initials\":\"MS\"}'
```
Login
```
curl -i -X POST -H "Content-Type: application/json" http://localhost:5000/users/authenticate -d '{\"Username\":\"michele.scandura@outlook.com\", \"Password\":\"password1\"}'
```
New Campaign
```
curl -i -X POST -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjEiLCJuYmYiOjE2MTM3MjgzMjksImV4cCI6MTYxNDMzMzEyOCwiaWF0IjoxNjEzNzI4MzI5fQ.VEsDbYUTnnvpgWnB5buE88JyGx_cg3TJb5ybC3eLvmc" http://localhost:5000/campaign/highfell -d '""'
```
Get Campaigns
```
curl -i -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjEiLCJuYmYiOjE2MTM3MjgzMjksImV4cCI6MTYxNDMzMzEyOCwiaWF0IjoxNjEzNzI4MzI5fQ.VEsDbYUTnnvpgWnB5buE88JyGx_cg3TJb5ybC3eLvmc" http://localhost:5000/campaign -d '""'
```
```
curl -i -X POST -H "Content-Type: application/json" http://localhost:5000/AuthManagement/register -d '{\"FirstName\":\"Michele\",\"LastName\":\"Scandura\", \"UserName\":\"michele\", \"Password\":\"Password1!\", \"Initials\":\"MS\", \"Email\":\"michele.scandura@outlook.com\"}'
```
```
curl -i -X POST -H "Content-Type: application/json" http://localhost:5000/AuthManagement/login -d '{ \"Password\":\"Password1!\", \"Email\":\"michele.scandura@outlook.com\"}'
```

View file

@ -1,9 +1,36 @@
using System;
using System.Collections.Generic;
using MediatR;
namespace Sledgemapper.Api.Commands
{
public class NewSessionCommand : BaseCommand<bool>
{
public NewSessionCommand(string sessionName, int userId):base(sessionName, userId)
public NewSessionCommand(string sessionName, int userId) : base(sessionName, userId)
{
}
}
public class NewCampaignCommand : IRequest<bool>
{
public double Timestamp { get; private set; }
public string CampaignName { get; private set; }
public string UserId { get; private set; }
public NewCampaignCommand(string campaingName, string userId)
{
Timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
CampaignName = campaingName;
UserId = userId;
}
}
public class GetCampaignsCommand : IRequest<List<Core.Entities.Campaign>>
{
public string UserId { get; private set; }
public GetCampaignsCommand(string userId)
{
UserId = userId;
}
}
}

View file

@ -0,0 +1,184 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Sledgemapper.Api.Commands;
using Sledgemapper.Api.Notifications;
using Sledgemapper.Entities;
using Sledgemapper.Models.Users;
using Sledgemapper.Shared.Entities;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Sledgemapper.Api.Controllers
{
[Route("[controller]")] // api/authmanagement
[ApiController]
public class AuthManagementController : ControllerBase
{
private readonly UserManager<User> _userManager;
private readonly JwtConfig _jwtConfig;
public AuthManagementController(UserManager<User> userManager,
IOptionsMonitor<JwtConfig> optionsMonitor)
{
_userManager = userManager;
_jwtConfig = optionsMonitor.CurrentValue;
}
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register([FromBody] Sledgemapper.Models.Users.RegisterModel user)
{
// Check if the incoming request is valid
if (ModelState.IsValid)
{
// check i the user with the same email exist
var existingUser = await _userManager.FindByEmailAsync(user.Email);
if (existingUser != null)
{
return BadRequest(new Sledgemapper.Models.Users.RegistrationResponse()
{
Result = false,
Errors = new List<string>(){
"Email already exist"
}
});
}
var newUser = new User() { Email = user.Email, UserName = user.UserName };
var isCreated = await _userManager.CreateAsync(newUser, user.Password);
if (isCreated.Succeeded)
{
var jwtToken = GenerateJwtToken(newUser);
return Ok(new Sledgemapper.Models.Users.RegistrationResponse()
{
Result = true,
Token = jwtToken
});
}
return new JsonResult(new Sledgemapper.Models.Users.RegistrationResponse()
{
Result = false,
Errors = isCreated.Errors.Select(x => x.Description).ToList()
}
)
{ StatusCode = 500 };
}
return BadRequest(new Sledgemapper.Models.Users.RegistrationResponse()
{
Result = false,
Errors = new List<string>(){
"Invalid payload"
}
});
}
private string GenerateJwtToken(User user)
{
// Now its ime to define the jwt token which will be responsible of creating our tokens
var jwtTokenHandler = new JwtSecurityTokenHandler();
// We get our secret from the appsettings
var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
// we define our token descriptor
// We need to utilise claims which are properties in our token which gives information about the token
// which belong to the specific user who it belongs to
// so it could contain their id, name, email the good part is that these information
// are generated by our server and identity framework which is valid and trusted
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
// the JTI is used for our refresh token which we will be convering in the next video
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
// the life span of the token needs to be shorter and utilise refresh token to keep the user signedin
// but since this is a demo app we can extend it to fit our current need
Expires = DateTime.UtcNow.AddHours(6),
// here we are adding the encryption alogorithim information which will be used to decrypt our token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var token = jwtTokenHandler.CreateToken(tokenDescriptor);
var jwtToken = jwtTokenHandler.WriteToken(token);
return jwtToken;
}
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login([FromBody] UserLoginRequest user)
{
if (ModelState.IsValid)
{
// check if the user with the same email exist
var existingUser = await _userManager.FindByEmailAsync(user.Email);
if (existingUser == null)
{
// We dont want to give to much information on why the request has failed for security reasons
return BadRequest(new RegistrationResponse()
{
Result = false,
Errors = new List<string>(){
"Invalid authentication request"
}
});
}
// Now we need to check if the user has inputed the right password
var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password);
if (isCorrect)
{
var jwtToken = GenerateJwtToken(existingUser);
return Ok(new RegistrationResponse()
{
Result = true,
Token = jwtToken
});
}
else
{
// We dont want to give to much information on why the request has failed for security reasons
return BadRequest(new RegistrationResponse()
{
Result = false,
Errors = new List<string>(){
"Invalid authentication request"
}
});
}
}
return BadRequest(new RegistrationResponse()
{
Result = false,
Errors = new List<string>(){
"Invalid payload"
}
});
}
}
}

View file

@ -0,0 +1,39 @@
using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Sledgemapper.Api.Commands;
using Sledgemapper.Api.Notifications;
using Sledgemapper.Shared.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sledgemapper.Api.Controllers
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("[controller]")]
public class CampaignController : ControllerBase
{
private readonly IMediator _mediator;
private string UserId => HttpContext.User.Claims.FirstOrDefault(m => m.Type == "Id").Value;
public CampaignController(IMediator mediator) => _mediator = mediator;
[HttpPost]
[Route("{campaignName}")]
public async Task<bool> Post(string campaignName)
{
var result = await _mediator.Send(new NewCampaignCommand(campaignName, UserId.ToString()));
return result;
}
[HttpGet]
public async Task<List<Core.Entities.Campaign>> Get()
{
var result = await _mediator.Send(new GetCampaignsCommand(UserId));
return result;
}
}
}

View file

@ -9,98 +9,82 @@ using System.Threading.Tasks;
namespace Sledgemapper.Api.Controllers
{
[Authorize]
[Route("[controller]/{sessionName}")]
public class SessionController : ControllerBase
[Route("[controller]/{mapName}")]
public class MapController : ControllerBase
{
private readonly IMediator _mediator;
private int UserId => int.Parse(HttpContext.User.Identity.Name);
public SessionController(IMediator mediator) => _mediator = mediator;
public MapController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<bool> Post(string sessionName)
public async Task<bool> Post(string mapName)
{
var result = await _mediator.Send(new NewSessionCommand(sessionName, UserId));
var result = await _mediator.Send(new NewSessionCommand(mapName, UserId));
return result;
}
[HttpGet]
public async Task<Session> Get(string sessionName)
public async Task<Session> Get(string mapName)
{
var result = await _mediator.Send(new GetMapSnapshotCommand(sessionName));
var result = await _mediator.Send(new GetMapSnapshotCommand(mapName));
return result;
}
// [HttpPost("ping")]
// public async Task Post(string sessionName, [FromBody] Ping pingLocation)
// {
// await _mediator.Send(new PingCommand(sessionName, pingLocation, UserId));
// }
[HttpPost("snapshot")]
public async Task Post(string sessionName, [FromBody] Session session)
public async Task Post(string mapName, [FromBody] Session session)
{
await _mediator.Send(new NewSnapshotCommand(sessionName, session, UserId));
}
[HttpPost("tile")]
public async Task Post(string sessionName, [FromBody] Tile tile)
{
await _mediator.Send(new NewTileCommand(sessionName, tile, UserId));
await _mediator.Send(new NewSnapshotCommand(mapName, session, UserId));
}
[HttpPost("overlay")]
public async Task Post(string sessionName, [FromBody] Overlay overlay)
public async Task Post(string mapName, [FromBody] Overlay overlay)
{
await _mediator.Send(new NewOverlayCommand(sessionName, overlay, UserId));
await _mediator.Send(new NewOverlayCommand(mapName, overlay, UserId));
}
[HttpPost("wall")]
public async Task Post(string sessionName, [FromBody] Wall wall)
public async Task Post(string mapName, [FromBody] Wall wall)
{
await _mediator.Send(new NewWallCommand(sessionName, wall, UserId));
await _mediator.Send(new NewWallCommand(mapName, wall, UserId));
}
[HttpPost("note")]
public async Task Post(string sessionName, [FromBody] Note note)
public async Task Post(string mapName, [FromBody] Note note)
{
await _mediator.Send(new NewNoteCommand(sessionName, note, UserId));
await _mediator.Send(new NewNoteCommand(mapName, note, UserId));
}
[HttpPost("room")]
public async Task Post(string sessionName, [FromBody] Room room)
public async Task Post(string mapName, [FromBody] Room room)
{
await _mediator.Send(new NewRoomCommand(sessionName, room, UserId));
await _mediator.Send(new NewRoomCommand(mapName, room, UserId));
}
[HttpPost("line")]
public async Task Post(string sessionName, [FromBody] Line line)
public async Task Post(string mapName, [FromBody] Line line)
{
await _mediator.Send(new NewLineCommand(sessionName, line, UserId));
}
[HttpDelete("tile")]
public async Task Delete(string sessionName, [FromBody] Tile tile)
{
await _mediator.Send(new DeleteTileCommand(sessionName, tile, UserId));
await _mediator.Send(new NewLineCommand(mapName, line, UserId));
}
[HttpDelete("overlay")]
public async Task Delete(string sessionName, [FromBody] Overlay overlay)
public async Task Delete(string mapName, [FromBody] Overlay overlay)
{
await _mediator.Send(new DeleteOverlayCommand(sessionName, overlay, UserId));
await _mediator.Send(new DeleteOverlayCommand(mapName, overlay, UserId));
}
[HttpDelete("wall")]
public async Task Delete(string sessionName, [FromBody] Wall wall)
public async Task Delete(string mapName, [FromBody] Wall wall)
{
await _mediator.Send(new DeleteWallCommand(sessionName, wall, UserId));
await _mediator.Send(new DeleteWallCommand(mapName, wall, UserId));
}
[HttpDelete("note")]
public async Task Delete(string sessionName, [FromBody] Note note)
public async Task Delete(string mapName, [FromBody] Note note)
{
await _mediator.Send(new DeleteNoteCommand(sessionName, note, UserId));
await _mediator.Send(new DeleteNoteCommand(mapName, note, UserId));
}
}
}

View file

@ -1,136 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using AutoMapper;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.Options;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Sledgemapper.Entities;
using Sledgemapper.Models.Users;
using Sledgemapper.Helpers;
using Sledgemapper.Api.Infrastructure.Services;
namespace Sledgemapper.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private IUserService _userService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public UsersController(
IUserService userService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_userService = userService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
var user = _userService.Authenticate(model.Username, model.Password);
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
// return basic user info and authentication token
return Ok(new
{
Id = user.Id,
Username = user.Username,
FirstName = user.FirstName,
LastName = user.LastName,
Initials = user.Initials,
Token = tokenString
});
}
[AllowAnonymous]
[HttpPost("register")]
public IActionResult Register([FromBody]RegisterModel model)
{
// map model to entity
var user = _mapper.Map<User>(model);
try
{
// create user
_userService.Create(user, model.Password);
return Ok();
}
catch (AppException ex)
{
// return error message if there was an exception
return BadRequest(new { message = ex.Message });
}
}
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
var model = _mapper.Map<IList<UserModel>>(users);
return Ok(model);
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var user = _userService.GetById(id);
var model = _mapper.Map<UserModel>(user);
return Ok(model);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]UpdateModel model)
{
// map model to entity and set id
var user = _mapper.Map<User>(model);
user.Id = id;
try
{
// update user
_userService.Update(user, model.Password);
return Ok();
}
catch (AppException ex)
{
// return error message if there was an exception
return BadRequest(new { message = ex.Message });
}
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_userService.Delete(id);
return Ok();
}
}
}

View file

@ -0,0 +1,7 @@
using System.ComponentModel.DataAnnotations;
public abstract class BaseEntity
{
[Key]
public int Id { get; set; }
}

View file

@ -0,0 +1,21 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Sledgemapper.Entities;
namespace Sledgemapper.Api.Core.Entities
{
[Index(nameof(CampaignName), nameof(OwnerId), IsUnique = true)]
public class Campaign
{
public int CampaignId { get; set; }
public string CampaignName { get; set; }
public string OwnerId { get; set; }
public User Owner { get; set; }
public ICollection<User> InvitedUsers { get; set; }
public ICollection<Map> Maps { get; set; }
}
}

View file

@ -1,20 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Sledgemapper.Entities;
namespace Sledgemapper.Api.Core.Entities.CampaignAggregate
{
public class Campaign
{
[Key]
public int CampaignId { get; set; }
[Required]
public string CampaignName { get; set; }
[Required]
public int OwnerUserId { get; set; }
public List<User> InvitedUsers { get; set; }
}
}

View file

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace Sledgemapper.Api.Core.Entities.CampaignAggregate
namespace Sledgemapper.Api.Core.Entities
{
public class Map
{

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace Sledgemapper.Entities
{
public class User:IdentityUser
{
// public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// public string Email { get; set; }
// public string Username { get; set; }
public string Initials { get; set; }
// public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public ICollection<Sledgemapper.Api.Core.Entities.Campaign> Campaigns {get;set;}
}
}

View file

@ -0,0 +1,78 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Sledgemapper.Api.Commands;
using Sledgemapper.Api.Infrastructure.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Sledgemapper.Api.Handlers
{
public class NewCampaignCommandHandler : IRequestHandler<NewCampaignCommand, bool>
{
private readonly IMediator _mediator;
private readonly SledgemapperDbContext _dbcontext;
public NewCampaignCommandHandler(IMediator mediator, SledgemapperDbContext dbcontext)
{
_mediator = mediator;
_dbcontext = dbcontext;
}
public async Task<bool> Handle(NewCampaignCommand notification, CancellationToken cancellationToken)
{
try
{
var user = await _dbcontext.Users.FindAsync(notification.UserId);
_dbcontext.Attach(user);
var campaign = new Core.Entities.Campaign
{
CampaignName = notification.CampaignName,
OwnerId = user.Id,
InvitedUsers = new System.Collections.Generic.List<Entities.User> { user }
};
_dbcontext.Campaigns.Add(campaign);
await _dbcontext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
}
return false;
}
}
public class GetCampaignsCommandHandler : IRequestHandler<GetCampaignsCommand, List<Core.Entities.Campaign>>
{
private readonly IMediator _mediator;
private readonly SledgemapperDbContext _dbcontext;
public GetCampaignsCommandHandler(IMediator mediator, SledgemapperDbContext dbcontext)
{
_mediator = mediator;
_dbcontext = dbcontext;
}
public async Task<List<Core.Entities.Campaign>> Handle(GetCampaignsCommand command, CancellationToken cancellationToken)
{
try
{
var user = await _dbcontext.Users.FindAsync(command.UserId);
_dbcontext.Attach(user);
var campaigns = _dbcontext.Campaigns.Where(campaign => campaign.OwnerId == command.UserId || campaign.InvitedUsers.Contains(user));
return campaigns.ToList();
}
catch (Exception ex)
{
}
return new List<Core.Entities.Campaign>();
}
}
}

View file

@ -1,7 +0,0 @@
namespace Sledgemapper.Helpers
{
public class AppSettings
{
public string Secret { get; set; }
}
}

View file

@ -8,9 +8,7 @@ namespace Sledgemapper.Helpers
{
public AutoMapperProfile()
{
CreateMap<User, UserModel>();
CreateMap<RegisterModel, User>();
CreateMap<UpdateModel, User>();
}
}
}

View file

@ -1,24 +0,0 @@
// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;
// using Sledgemapper.Entities;
// namespace Sledgemapper.Helpers
// {
// public class DataContext : DbContext
// {
// protected readonly IConfiguration Configuration;
// public DataContext(IConfiguration configuration)
// {
// Configuration = configuration;
// }
// protected override void OnConfiguring(DbContextOptionsBuilder options)
// {
// // connect to sql server database
// options.UseSqlServer(Configuration.GetConnectionString("WebApiDatabase"));
// }
// public DbSet<User> Users { get; set; }
// }
// }

View file

@ -1,16 +0,0 @@
// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;
// namespace Sledgemapper.Helpers
// {
// public class SqliteDataContext : DataContext
// {
// public SqliteDataContext(IConfiguration configuration) : base(configuration) { }
// protected override void OnConfiguring(DbContextOptionsBuilder options)
// {
// // connect to sqlite database
// options.UseSqlite(Configuration.GetConnectionString("WebApiDatabase"));
// }
// }
// }

View file

@ -89,7 +89,7 @@ namespace Sledgemapper.Api.Hubs
public async Task Ping(string sessionName, Tile location)
{
var userId = int.Parse(Context.User.Identity.Name);
var user = _dbContext.Users.First(u => u.Id == userId);
var user = _dbContext.Users.First(u => u.Id == Context.User.Identity.Name);
var player = new Player { UserId = userId, Initials = user.Initials, Position = new Tile { X = 0, Y = 0 }, Color = UserColors[userId] };
await Clients.Group(sessionName).Ping(new Ping{X=location.X, Y=location.Y, Player=player});
@ -106,7 +106,7 @@ namespace Sledgemapper.Api.Hubs
_dbContext.SessionUsers.Add(userSession);
await _dbContext.SaveChangesAsync();
await Groups.AddToGroupAsync(Context.ConnectionId, session.SessionName);
var user = _dbContext.Users.First(u => u.Id == userId);
var user = _dbContext.Users.First(u => u.Id == Context.User.Identity.Name);
var player = new Player { UserId = userId, Initials = user.Initials, Position = new Tile { X = 0, Y = 0 }, Color = UserColors[userId] };
@ -131,7 +131,7 @@ namespace Sledgemapper.Api.Hubs
{
var userId = int.Parse(Context.User.Identity.Name);
var SessionUsers = _dbContext.SessionUsers.Where(m => m.SessionId == sessionId).OrderBy(m => m.UserId).ToList();
var user = _dbContext.Users.First(u => u.Id == userId);
var user = _dbContext.Users.First(u => u.Id == Context.User.Identity.Name);
var player = new Player { UserId = userId, Initials = user.Initials, Position = tile, Color = UserColors[userId] };
await Clients.Group(sessionName).PlayerUpdate(player);
}

View file

@ -1,88 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Sledgemapper.Api.Core.Entities.CampaignAggregate;
using Sledgemapper.Api.Models;
using Sledgemapper.Entities;
namespace Sledgemapper.Api.Infrastructure.Data
{
// public static class DbInitializer
// {
// public static void Initialize(SledgemapperDbContext context)
// {
// context.Database.EnsureCreated();
// }
// }
public class SledgemapperDbContext : DbContext
{
public DbSet<Sledgemapper.Api.Core.Entities.CampaignAggregate.Campaign> Campaigns { get; set; }
public DbSet<MapLog> MapLogs { get; set; }
public DbSet<Map> Maps { get; set; }
public DbSet<Session> Sessions { get; set; }
public DbSet<SessionUser> SessionUsers { get; set; }
public DbSet<Snapshot> Snapshots { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserConnection> UserConnections { get; set; }
public SledgemapperDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// optionsBuilder.
// options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName);
// optionsBuilder.UseSqlite("Filename=SledgemapperDatabase.db", options =>
// {
// options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName);
// });
optionsBuilder.UseSqlite("Filename=Sledgemapper.db").UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
base.OnConfiguring(optionsBuilder);
}
// protected override void OnModelCreating(ModelBuilder modelBuilder)
// {
// // Map table names
// modelBuilder.Entity<MapLog>().ToTable("MapLog", "dbo");
// modelBuilder.Entity<MapLog>(entity =>
// {
// entity.HasKey(e => e.MapLogId);
// });
// modelBuilder.Entity<Session>().ToTable("Session", "dbo");
// modelBuilder.Entity<Session>(entity =>
// {
// entity.HasKey(e => e.SessionId);
// entity.HasIndex(e => e.SessionName).IsUnique();
// });
// modelBuilder.Entity<UserConnection>().ToTable("UserConnection", "dbo");
// modelBuilder.Entity<UserConnection>(entity =>
// {
// entity.HasKey(e => e.UserConnectionId);
// entity.HasIndex(e => e.UserId);
// });
// modelBuilder.Entity<SessionUser>().ToTable("SessionUser", "dbo");
// modelBuilder.Entity<SessionUser>(entity =>
// {
// entity.HasKey(e => e.SessionUserId);
// entity.HasIndex(e => e.SessionId);
// });
// modelBuilder.Entity<Snapshot>().ToTable("Snapshot", "dbo");
// modelBuilder.Entity<Snapshot>(entity =>
// {
// entity.HasKey(e => e.SnapshotId);
// });
// base.OnModelCreating(modelBuilder);
// }
}
}

View file

@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Sledgemapper.Api.Core.Entities;
using Sledgemapper.Api.Models;
using Sledgemapper.Entities;
namespace Sledgemapper.Api.Infrastructure.Data
{
// public static class DbInitializer
// {
// public static void Initialize(SledgemapperDbContext context)
// {
// context.Database.EnsureCreated();
// }
// }
public class SledgemapperDbContext : IdentityDbContext
{
public DbSet<Sledgemapper.Api.Core.Entities.Campaign> Campaigns { get; set; }
public DbSet<MapLog> MapLogs { get; set; }
public DbSet<Map> Maps { get; set; }
public DbSet<Session> Sessions { get; set; }
public DbSet<SessionUser> SessionUsers { get; set; }
public DbSet<Snapshot> Snapshots { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserConnection> UserConnections { get; set; }
public SledgemapperDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// optionsBuilder.
// options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName);
// optionsBuilder.UseSqlite("Filename=SledgemapperDatabase.db", options =>
// {
// options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName);
// });
optionsBuilder.UseSqlite("Filename=Sledgemapper.db").UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Core.Entities.Campaign>().HasOne<User>(e=>e.Owner);
modelBuilder.Entity<Core.Entities.Campaign>().HasMany<User>(e=>e.InvitedUsers).WithMany(e=>e.Campaigns);
modelBuilder.Entity<User>() //Use your application user class here
.ToTable( "Users" ); //Set the table name here
// // Map table names
// modelBuilder.Entity<MapLog>().ToTable("MapLog", "dbo");
// modelBuilder.Entity<MapLog>(entity =>
// {
// entity.HasKey(e => e.MapLogId);
// });
// modelBuilder.Entity<Session>().ToTable("Session", "dbo");
// modelBuilder.Entity<Session>(entity =>
// {
// entity.HasKey(e => e.SessionId);
// entity.HasIndex(e => e.SessionName).IsUnique();
// });
// modelBuilder.Entity<UserConnection>().ToTable("UserConnection", "dbo");
// modelBuilder.Entity<UserConnection>(entity =>
// {
// entity.HasKey(e => e.UserConnectionId);
// entity.HasIndex(e => e.UserId);
// });
// modelBuilder.Entity<SessionUser>().ToTable("SessionUser", "dbo");
// modelBuilder.Entity<SessionUser>(entity =>
// {
// entity.HasKey(e => e.SessionUserId);
// entity.HasIndex(e => e.SessionId);
// });
// modelBuilder.Entity<Snapshot>().ToTable("Snapshot", "dbo");
// modelBuilder.Entity<Snapshot>(entity =>
// {
// entity.HasKey(e => e.SnapshotId);
// });
base.OnModelCreating(modelBuilder);
}
}
}

View file

@ -1,160 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Sledgemapper.Api.Infrastructure.Data;
using Sledgemapper.Entities;
using Sledgemapper.Helpers;
namespace Sledgemapper.Api.Infrastructure.Services
{
public interface IUserService
{
User Authenticate(string username, string password);
IEnumerable<User> GetAll();
User GetById(int id);
User Create(User user, string password);
void Update(User user, string password = null);
void Delete(int id);
}
public class UserService : IUserService
{
private SledgemapperDbContext _context;
public UserService(SledgemapperDbContext context)
{
_context = context;
}
public User Authenticate(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return null;
var user = _context.Users.SingleOrDefault(x => x.Username == username);
// check if username exists
if (user == null)
return null;
// check if password is correct
if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
return null;
// authentication successful
return user;
}
public IEnumerable<User> GetAll()
{
return _context.Users;
}
public User GetById(int id)
{
return _context.Users.Find(id);
}
public User Create(User user, string password)
{
// validation
if (string.IsNullOrWhiteSpace(password))
throw new AppException("Password is required");
if (_context.Users.Any(x => x.Username == user.Username))
throw new AppException("Username \"" + user.Username + "\" is already taken");
byte[] passwordHash, passwordSalt;
CreatePasswordHash(password, out passwordHash, out passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
_context.Users.Add(user);
_context.SaveChanges();
return user;
}
public void Update(User userParam, string password = null)
{
var user = _context.Users.Find(userParam.Id);
if (user == null)
throw new AppException("User not found");
// update username if it has changed
if (!string.IsNullOrWhiteSpace(userParam.Username) && userParam.Username != user.Username)
{
// throw error if the new username is already taken
if (_context.Users.Any(x => x.Username == userParam.Username))
throw new AppException("Username " + userParam.Username + " is already taken");
user.Username = userParam.Username;
}
// update user properties if provided
if (!string.IsNullOrWhiteSpace(userParam.FirstName))
user.FirstName = userParam.FirstName;
if (!string.IsNullOrWhiteSpace(userParam.LastName))
user.LastName = userParam.LastName;
// update password if provided
if (!string.IsNullOrWhiteSpace(password))
{
byte[] passwordHash, passwordSalt;
CreatePasswordHash(password, out passwordHash, out passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
}
_context.Users.Update(user);
_context.SaveChanges();
}
public void Delete(int id)
{
var user = _context.Users.Find(id);
if (user != null)
{
_context.Users.Remove(user);
_context.SaveChanges();
}
}
// private helper methods
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i]) return false;
}
}
return true;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Sledgemapper.Api
{
public class JwtConfig
{
public string Secret { get; set; }
}
}

View file

@ -9,7 +9,7 @@ using Sledgemapper.Api.Infrastructure.Data;
namespace Sledgemapper.Api.Migrations
{
[DbContext(typeof(SledgemapperDbContext))]
[Migration("20210214232542_InitialCreate")]
[Migration("20210219223114_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -18,25 +18,44 @@ namespace Sledgemapper.Api.Migrations
modelBuilder
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("Sledgemapper.Api.Models.Campaign", b =>
modelBuilder.Entity("CampaignUser", b =>
{
b.Property<int>("CampaignsCampaignId")
.HasColumnType("INTEGER");
b.Property<int>("InvitedUsersId")
.HasColumnType("INTEGER");
b.HasKey("CampaignsCampaignId", "InvitedUsersId");
b.HasIndex("InvitedUsersId");
b.ToTable("CampaignUser");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Property<int>("CampaignId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CampaignName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("OwnerUserId")
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("CampaignId");
b.HasIndex("OwnerId");
b.HasIndex("CampaignName", "OwnerId")
.IsUnique();
b.ToTable("Campaigns");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Map", b =>
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.Property<int>("MapId")
.ValueGeneratedOnAdd()
@ -51,6 +70,8 @@ namespace Sledgemapper.Api.Migrations
b.HasKey("MapId");
b.HasIndex("CampaignId");
b.ToTable("Maps");
});
@ -169,9 +190,6 @@ namespace Sledgemapper.Api.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("CampaignId")
.HasColumnType("INTEGER");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
@ -192,21 +210,47 @@ namespace Sledgemapper.Api.Migrations
b.HasKey("Id");
b.HasIndex("CampaignId");
b.ToTable("Users");
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
modelBuilder.Entity("CampaignUser", b =>
{
b.HasOne("Sledgemapper.Api.Models.Campaign", null)
.WithMany("InvitedUsers")
.HasForeignKey("CampaignId");
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany()
.HasForeignKey("CampaignsCampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Sledgemapper.Entities.User", null)
.WithMany()
.HasForeignKey("InvitedUsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Models.Campaign", b =>
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("InvitedUsers");
b.HasOne("Sledgemapper.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany("Maps")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("Maps");
});
#pragma warning restore 612, 618
}

View file

@ -7,20 +7,6 @@ namespace Sledgemapper.Api.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Campaigns",
columns: table => new
{
CampaignId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CampaignName = table.Column<string>(type: "TEXT", nullable: false),
OwnerUserId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Campaigns", x => x.CampaignId);
});
migrationBuilder.CreateTable(
name: "MapLogs",
columns: table => new
@ -39,20 +25,6 @@ namespace Sledgemapper.Api.Migrations
table.PrimaryKey("PK_MapLogs", x => x.MapLogId);
});
migrationBuilder.CreateTable(
name: "Maps",
columns: table => new
{
MapId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
MapName = table.Column<string>(type: "TEXT", nullable: false),
CampaignId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Maps", x => x.MapId);
});
migrationBuilder.CreateTable(
name: "Sessions",
columns: table => new
@ -121,28 +93,104 @@ namespace Sledgemapper.Api.Migrations
Username = table.Column<string>(type: "TEXT", nullable: true),
Initials = table.Column<string>(type: "TEXT", nullable: true),
PasswordHash = table.Column<byte[]>(type: "BLOB", nullable: true),
PasswordSalt = table.Column<byte[]>(type: "BLOB", nullable: true),
CampaignId = table.Column<int>(type: "INTEGER", nullable: true)
PasswordSalt = table.Column<byte[]>(type: "BLOB", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Campaigns",
columns: table => new
{
CampaignId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CampaignName = table.Column<string>(type: "TEXT", nullable: true),
OwnerId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Campaigns", x => x.CampaignId);
table.ForeignKey(
name: "FK_Users_Campaigns_CampaignId",
name: "FK_Campaigns_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "CampaignUser",
columns: table => new
{
CampaignsCampaignId = table.Column<int>(type: "INTEGER", nullable: false),
InvitedUsersId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CampaignUser", x => new { x.CampaignsCampaignId, x.InvitedUsersId });
table.ForeignKey(
name: "FK_CampaignUser_Campaigns_CampaignsCampaignId",
column: x => x.CampaignsCampaignId,
principalTable: "Campaigns",
principalColumn: "CampaignId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_CampaignUser_Users_InvitedUsersId",
column: x => x.InvitedUsersId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Maps",
columns: table => new
{
MapId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
MapName = table.Column<string>(type: "TEXT", nullable: false),
CampaignId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Maps", x => x.MapId);
table.ForeignKey(
name: "FK_Maps_Campaigns_CampaignId",
column: x => x.CampaignId,
principalTable: "Campaigns",
principalColumn: "CampaignId",
onDelete: ReferentialAction.Restrict);
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Users_CampaignId",
table: "Users",
name: "IX_Campaigns_CampaignName_OwnerId",
table: "Campaigns",
columns: new[] { "CampaignName", "OwnerId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Campaigns_OwnerId",
table: "Campaigns",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_CampaignUser_InvitedUsersId",
table: "CampaignUser",
column: "InvitedUsersId");
migrationBuilder.CreateIndex(
name: "IX_Maps_CampaignId",
table: "Maps",
column: "CampaignId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CampaignUser");
migrationBuilder.DropTable(
name: "MapLogs");
@ -162,10 +210,10 @@ namespace Sledgemapper.Api.Migrations
name: "UserConnections");
migrationBuilder.DropTable(
name: "Users");
name: "Campaigns");
migrationBuilder.DropTable(
name: "Campaigns");
name: "Users");
}
}
}

View file

@ -2,39 +2,60 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Sledgemapper.Api.Infrastructure.Data;
namespace Sledgemapper.Api.Migrations
{
[DbContext(typeof(SledgemapperDbContext))]
partial class SledgemapperDbContextModelSnapshot : ModelSnapshot
[Migration("20210219223251_Adding authentication to our Api")]
partial class AddingauthenticationtoourApi
{
protected override void BuildModel(ModelBuilder modelBuilder)
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("Sledgemapper.Api.Models.Campaign", b =>
modelBuilder.Entity("CampaignUser", b =>
{
b.Property<int>("CampaignsCampaignId")
.HasColumnType("INTEGER");
b.Property<int>("InvitedUsersId")
.HasColumnType("INTEGER");
b.HasKey("CampaignsCampaignId", "InvitedUsersId");
b.HasIndex("InvitedUsersId");
b.ToTable("CampaignUser");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Property<int>("CampaignId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CampaignName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("OwnerUserId")
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("CampaignId");
b.HasIndex("OwnerId");
b.HasIndex("CampaignName", "OwnerId")
.IsUnique();
b.ToTable("Campaigns");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Map", b =>
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.Property<int>("MapId")
.ValueGeneratedOnAdd()
@ -49,6 +70,8 @@ namespace Sledgemapper.Api.Migrations
b.HasKey("MapId");
b.HasIndex("CampaignId");
b.ToTable("Maps");
});
@ -167,9 +190,6 @@ namespace Sledgemapper.Api.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("CampaignId")
.HasColumnType("INTEGER");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
@ -190,21 +210,47 @@ namespace Sledgemapper.Api.Migrations
b.HasKey("Id");
b.HasIndex("CampaignId");
b.ToTable("Users");
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
modelBuilder.Entity("CampaignUser", b =>
{
b.HasOne("Sledgemapper.Api.Models.Campaign", null)
.WithMany("InvitedUsers")
.HasForeignKey("CampaignId");
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany()
.HasForeignKey("CampaignsCampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Sledgemapper.Entities.User", null)
.WithMany()
.HasForeignKey("InvitedUsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Models.Campaign", b =>
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("InvitedUsers");
b.HasOne("Sledgemapper.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany("Maps")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("Maps");
});
#pragma warning restore 612, 618
}

View file

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Sledgemapper.Api.Migrations
{
public partial class AddingauthenticationtoourApi : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View file

@ -0,0 +1,505 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Sledgemapper.Api.Infrastructure.Data;
namespace Sledgemapper.Api.Migrations
{
[DbContext(typeof(SledgemapperDbContext))]
[Migration("20210219230948_rename table")]
partial class renametable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("CampaignUser", b =>
{
b.Property<int>("CampaignsCampaignId")
.HasColumnType("INTEGER");
b.Property<string>("InvitedUsersId")
.HasColumnType("TEXT");
b.HasKey("CampaignsCampaignId", "InvitedUsersId");
b.HasIndex("InvitedUsersId");
b.ToTable("CampaignUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Property<int>("CampaignId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CampaignName")
.HasColumnType("TEXT");
b.Property<string>("OwnerId")
.HasColumnType("TEXT");
b.HasKey("CampaignId");
b.HasIndex("OwnerId");
b.HasIndex("CampaignName", "OwnerId")
.IsUnique();
b.ToTable("Campaigns");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.Property<int>("MapId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CampaignId")
.HasColumnType("INTEGER");
b.Property<string>("MapName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("MapId");
b.HasIndex("CampaignId");
b.ToTable("Maps");
});
modelBuilder.Entity("Sledgemapper.Api.Models.MapLog", b =>
{
b.Property<int>("MapLogId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Object")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("Operation")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("TEXT");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<double>("Timestamp")
.HasColumnType("REAL");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("MapLogId");
b.ToTable("MapLogs");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Session", b =>
{
b.Property<int>("SessionId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("OwnerUserId")
.HasColumnType("INTEGER");
b.Property<string>("SessionName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("SessionId");
b.ToTable("Sessions");
});
modelBuilder.Entity("Sledgemapper.Api.Models.SessionUser", b =>
{
b.Property<int>("SessionUserId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("SessionUserId");
b.ToTable("SessionUsers");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Snapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Object")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<double>("Timestamp")
.HasColumnType("REAL");
b.HasKey("SnapshotId");
b.ToTable("Snapshots");
});
modelBuilder.Entity("Sledgemapper.Api.Models.UserConnection", b =>
{
b.Property<int>("UserConnectionId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConnectionId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("UserConnectionId");
b.ToTable("UserConnections");
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
b.Property<string>("Initials")
.HasColumnType("TEXT");
b.Property<string>("LastName")
.HasColumnType("TEXT");
b.Property<byte[]>("PasswordSalt")
.HasColumnType("BLOB");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.ToTable("Users");
});
modelBuilder.Entity("CampaignUser", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany()
.HasForeignKey("CampaignsCampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Sledgemapper.Entities.User", null)
.WithMany()
.HasForeignKey("InvitedUsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.HasOne("Sledgemapper.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany("Maps")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithOne()
.HasForeignKey("Sledgemapper.Entities.User", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("Maps");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,315 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Sledgemapper.Api.Migrations
{
public partial class renametable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Campaigns_Users_OwnerId",
table: "Campaigns");
migrationBuilder.DropColumn(
name: "PasswordHash",
table: "Users");
migrationBuilder.AlterColumn<string>(
name: "Id",
table: "Users",
type: "TEXT",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER")
.OldAnnotation("Sqlite:Autoincrement", true);
migrationBuilder.AlterColumn<string>(
name: "InvitedUsersId",
table: "CampaignUser",
type: "TEXT",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.AlterColumn<string>(
name: "OwnerId",
table: "Campaigns",
type: "TEXT",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(type: "TEXT", nullable: false),
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(type: "TEXT", nullable: false),
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
UserId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "TEXT", nullable: false),
RoleId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "TEXT", nullable: false),
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_Campaigns_Users_OwnerId",
table: "Campaigns",
column: "OwnerId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Users_AspNetUsers_Id",
table: "Users",
column: "Id",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Campaigns_Users_OwnerId",
table: "Campaigns");
migrationBuilder.DropForeignKey(
name: "FK_Users_AspNetUsers_Id",
table: "Users");
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Users",
type: "INTEGER",
nullable: false,
oldClrType: typeof(string),
oldType: "TEXT")
.Annotation("Sqlite:Autoincrement", true);
migrationBuilder.AddColumn<byte[]>(
name: "PasswordHash",
table: "Users",
type: "BLOB",
nullable: true);
migrationBuilder.AlterColumn<int>(
name: "InvitedUsersId",
table: "CampaignUser",
type: "INTEGER",
nullable: false,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AlterColumn<int>(
name: "OwnerId",
table: "Campaigns",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Campaigns_Users_OwnerId",
table: "Campaigns",
column: "OwnerId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -0,0 +1,503 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Sledgemapper.Api.Infrastructure.Data;
namespace Sledgemapper.Api.Migrations
{
[DbContext(typeof(SledgemapperDbContext))]
partial class SledgemapperDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("CampaignUser", b =>
{
b.Property<int>("CampaignsCampaignId")
.HasColumnType("INTEGER");
b.Property<string>("InvitedUsersId")
.HasColumnType("TEXT");
b.HasKey("CampaignsCampaignId", "InvitedUsersId");
b.HasIndex("InvitedUsersId");
b.ToTable("CampaignUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Property<int>("CampaignId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CampaignName")
.HasColumnType("TEXT");
b.Property<string>("OwnerId")
.HasColumnType("TEXT");
b.HasKey("CampaignId");
b.HasIndex("OwnerId");
b.HasIndex("CampaignName", "OwnerId")
.IsUnique();
b.ToTable("Campaigns");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.Property<int>("MapId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CampaignId")
.HasColumnType("INTEGER");
b.Property<string>("MapName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("MapId");
b.HasIndex("CampaignId");
b.ToTable("Maps");
});
modelBuilder.Entity("Sledgemapper.Api.Models.MapLog", b =>
{
b.Property<int>("MapLogId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Object")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("Operation")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("TEXT");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<double>("Timestamp")
.HasColumnType("REAL");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("MapLogId");
b.ToTable("MapLogs");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Session", b =>
{
b.Property<int>("SessionId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("OwnerUserId")
.HasColumnType("INTEGER");
b.Property<string>("SessionName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("SessionId");
b.ToTable("Sessions");
});
modelBuilder.Entity("Sledgemapper.Api.Models.SessionUser", b =>
{
b.Property<int>("SessionUserId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("SessionUserId");
b.ToTable("SessionUsers");
});
modelBuilder.Entity("Sledgemapper.Api.Models.Snapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Object")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<double>("Timestamp")
.HasColumnType("REAL");
b.HasKey("SnapshotId");
b.ToTable("Snapshots");
});
modelBuilder.Entity("Sledgemapper.Api.Models.UserConnection", b =>
{
b.Property<int>("UserConnectionId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConnectionId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("UserConnectionId");
b.ToTable("UserConnections");
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
b.Property<string>("Initials")
.HasColumnType("TEXT");
b.Property<string>("LastName")
.HasColumnType("TEXT");
b.Property<byte[]>("PasswordSalt")
.HasColumnType("BLOB");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.ToTable("Users");
});
modelBuilder.Entity("CampaignUser", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany()
.HasForeignKey("CampaignsCampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Sledgemapper.Entities.User", null)
.WithMany()
.HasForeignKey("InvitedUsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.HasOne("Sledgemapper.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Map", b =>
{
b.HasOne("Sledgemapper.Api.Core.Entities.Campaign", null)
.WithMany("Maps")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Entities.User", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithOne()
.HasForeignKey("Sledgemapper.Entities.User", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sledgemapper.Api.Core.Entities.Campaign", b =>
{
b.Navigation("Maps");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Sledgemapper.Models.Users
{
public class AuthResult
{
public string Token { get; set; }
public bool Result { get; set; }
public List<string> Errors { get; set; }
}
}

View file

@ -6,16 +6,18 @@ namespace Sledgemapper.Models.Users
{
[Required]
public string FirstName { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Username { get; set; }
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Initials { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace Sledgemapper.Models.Users
{
public class RegistrationResponse : AuthResult
{
}
}

View file

@ -1,10 +0,0 @@
namespace Sledgemapper.Models.Users
{
public class UpdateModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}

View file

@ -1,13 +0,0 @@
namespace Sledgemapper.Entities
{
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Initials { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Sledgemapper.Models.Users
{
public class UserLoginRequest
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
}

View file

@ -1,11 +0,0 @@
namespace Sledgemapper.Models.Users
{
public class UserModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Initials { get; set; }
}
}

View file

@ -10,11 +10,15 @@
<PackageReference Include="mediatr" Version="9.0.0" />
<PackageReference Include="mediatr.extensions.microsoft.dependencyinjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.3" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.7" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.3" />
</ItemGroup>

View file

@ -1,11 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Sledgemapper.Api.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using MediatR;
@ -13,10 +10,7 @@ using Microsoft.IdentityModel.Tokens;
using Sledgemapper.Helpers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
using AutoMapper;
using System.Security.Claims;
using Sledgemapper.Api.Hubs;
using Sledgemapper.Api.Infrastructure.Services;
namespace Sledgemapper.Api
{
@ -31,93 +25,67 @@ namespace Sledgemapper.Api
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// if (_env.IsProduction())
// services.AddDbContext<DataContext>();
// else
// services.AddDbContext<DataContext, SqliteDataContext>();
// services.AddRazorPages();
services.AddCors();
services.AddCors();
services.AddControllers();
services.AddSignalR();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddMediatR(typeof(Startup));
services.AddDbContext<SledgemapperDbContext>(options => {options.UseSqlite("Data Source=db/sledgemapper.db"); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);});
services.AddDbContext<SledgemapperDbContext>(options => { options.UseSqlite("Data Source=db/sledgemapper.db"); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); });
// services.AddEntityFrameworkSqlite().AddDbContext<SledgemapperDbContext>();
// configure strongly typed settings objects
var c = Configuration.GetSection("AppSettings");
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
// configure DI for application services
// within this section we are configuring the authentication and setting the default scheme
services.AddAuthentication(options =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
.AddJwtBearer(jwt =>
{
x.Events = new JwtBearerEvents
var key = Encoding.ASCII.GetBytes(Configuration["JwtConfig:Secret"]);
jwt.SaveToken = true;
jwt.TokenValidationParameters = new TokenValidationParameters
{
OnTokenValidated = context =>
{
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = userService.GetById(userId);
if (user == null)
{
// return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
context.HttpContext.User.Claims.Append(new Claim(ClaimTypes.Name, user.Username));
context.HttpContext.User.Claims.Append(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuerSigningKey = true, // this will validate the 3rd part of the jwt token using the secret that we added in the appsettings and verify we have generated the jwt token
IssuerSigningKey = new SymmetricSecurityKey(key), // Add the secret key to our Jwt encryption
ValidateIssuer = false,
ValidateAudience = false
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = true
};
});
// configure DI for application services
services.AddScoped<IUserService, UserService>();
services.AddDefaultIdentity<Entities.User>(options => options.SignIn.RequireConfirmedAccount = false)
.AddEntityFrameworkStores<SledgemapperDbContext>();
services.AddSwaggerGen();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, SledgemapperDbContext dataContext)
{
dataContext.Database.Migrate();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// if (env.IsDevelopment())
// {
// app.UseDeveloperExceptionPage();
// }
// 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();
// }
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
// //app.UseHttpsRedirection();
// app.UseStaticFiles();
dataContext.Database.Migrate();
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
@ -132,9 +100,7 @@ namespace Sledgemapper.Api
app.UseEndpoints(endpoints =>
{
// endpoints.MapRazorPages();
endpoints.MapHub<SledgemapperHub>("/sledgemapperhub");
});
}
}

View file

@ -1,16 +1,16 @@
{
"AppSettings": {
"Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
},
"ConnectionStrings": {
"WebApiDatabase": "ENTER PRODUCTION SQL SERVER CONNECTION STRING HERE"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
"JwtConfig": {
"Secret": "ALy9zGFVZBza9cju458dVC2rBdwPqfb7"
},
"ConnectionStrings": {
"WebApiDatabase": "ENTER PRODUCTION SQL SERVER CONNECTION STRING HERE"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View file

@ -1,4 +1,4 @@
/* Generated by MyraPad at 11/02/2021 23:16:18 */
/* Generated by MyraPad at 19/02/2021 14:56:38 */
using Myra;
using Myra.Graphics2D;
using Myra.Graphics2D.TextureAtlases;
@ -103,17 +103,17 @@ namespace Sledgemapper.UI
var menuSeparator4 = new MenuSeparator();
MenuCampaignInvite = new MenuItem();
MenuCampaignInvite.Text = "&Invite";
MenuCampaignInvite.ShortcutText = "Ctrl+I";
MenuCampaignInvite.Id = "MenuCampaignInvite";
MenuCampaignPlayers = new MenuItem();
MenuCampaignPlayers.Text = "&Players";
MenuCampaignPlayers.ShortcutText = "CtrI+P";
MenuCampaignPlayers.Id = "MenuCampaignPlayers";
var menuItem2 = new MenuItem();
menuItem2.Text = "C&ampaign";
menuItem2.Items.Add(MenuCampaingNew);
menuItem2.Items.Add(MenuCampaignOpen);
menuItem2.Items.Add(menuSeparator4);
menuItem2.Items.Add(MenuCampaignInvite);
menuItem2.Items.Add(MenuCampaignPlayers);
MenuMapNew = new MenuItem();
MenuMapNew.Text = "&New";
@ -348,7 +348,7 @@ namespace Sledgemapper.UI
public MenuItem MenuConnectUpload;
public MenuItem MenuCampaingNew;
public MenuItem MenuCampaignOpen;
public MenuItem MenuCampaignInvite;
public MenuItem MenuCampaignPlayers;
public MenuItem MenuMapNew;
public MenuItem MenuMapOpen;
public MenuItem MenuViewShowNotes;

View file

@ -27,7 +27,7 @@
<MenuItem Text="&amp;New" ShortcutText="Ctrl+N" Id="MenuCampaingNew" />
<MenuItem Text="&amp;Open" ShortcutText="Ctrl+O" Id="MenuCampaignOpen" />
<MenuSeparator />
<MenuItem Text="&amp;Invite" ShortcutText="Ctrl+I" Id="MenuCampaignInvite" />
<MenuItem Text="&amp;Players" ShortcutText="CtrI+P" Id="MenuCampaignPlayers" />
</MenuItem>
<MenuItem Text="&amp;Map" ShortcutText="Ctrl+M">
<MenuItem Text="&amp;New" ShortcutText="Ctrl+N" Id="MenuMapNew" />