Merge pull request 'dotnet' (#1) from dotnet into master
Reviewed-on: phoenix/textsender-api#1
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
appsettings.Development.json
|
||||
*.json
|
||||
bin
|
||||
obj
|
||||
@@ -0,0 +1,67 @@
|
||||
using TextSender_API.Models;
|
||||
|
||||
namespace TextSender_API.Controllers;
|
||||
|
||||
public class ContactManagement
|
||||
{
|
||||
#region Fields
|
||||
private IConfiguration _config;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public ContactManagement(IConfiguration config)
|
||||
{
|
||||
this._config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
// Queues the contact file to be processed latter
|
||||
public ContactCreationQueue QueueContact(IFormFile file)
|
||||
{
|
||||
var queue = new ContactCreationQueue();
|
||||
queue.Processed = false;
|
||||
var directory = this._config["Paths:ContactQueueDirectory"];
|
||||
|
||||
if (directory!.ElementAt(directory!.Length - 1) != '/')
|
||||
{
|
||||
directory.Append('/');
|
||||
}
|
||||
|
||||
if (!System.IO.Directory.Exists(directory))
|
||||
{
|
||||
queue.Status = "Missing directory";
|
||||
// Directory does not exist
|
||||
return queue;
|
||||
}
|
||||
|
||||
var length = 16;
|
||||
var chars = "ABCDEF0123456789";
|
||||
var random = new Random();
|
||||
var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
|
||||
s[random.Next(s.Length)]).ToArray());
|
||||
var extension = ".txt";
|
||||
|
||||
var contactPath = $"{directory}/{filename}{extension}";
|
||||
queue.Filepath = contactPath;
|
||||
|
||||
if (System.IO.File.Exists(contactPath))
|
||||
{
|
||||
// Already exists
|
||||
queue.Status = "File exists";
|
||||
return queue;
|
||||
}
|
||||
|
||||
queue.Status = "Created";
|
||||
|
||||
using (var filestream = new FileStream(contactPath, FileMode.Create))
|
||||
{
|
||||
file.CopyTo(filestream);
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TextSender_API.Models;
|
||||
using TextSender_API.Repositories;
|
||||
|
||||
namespace TextSender_API.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/v1/contact/queue")]
|
||||
public class ContactQueueCreationController : ControllerBase
|
||||
{
|
||||
#region Fields
|
||||
private IConfiguration _config;
|
||||
private readonly ILogger<ContactQueueCreationController> _logger;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ContactQueueCreationController(ILogger<ContactQueueCreationController> logger, IConfiguration config)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
[HttpPost("upload"), DisableRequestSizeLimit]
|
||||
public IActionResult Upload([FromForm(Name = "file")] List<IFormFile> contactFile,
|
||||
[FromForm(Name = "user_id")] string userID)
|
||||
{
|
||||
// Save the file on the filesystem to be processed by a separate
|
||||
// system
|
||||
var response = new CreateQueueResponse();
|
||||
var connString = this._config.GetConnectionString("MongoDBURI");
|
||||
var userRepo = new UsersRepository(connString!);
|
||||
var queueRepo = new ContactCreationQueueRepository(connString!);
|
||||
|
||||
try
|
||||
{
|
||||
var ctMgr = new ContactManagement(this._config);
|
||||
var user = userRepo.Exists(userID);
|
||||
|
||||
if (!user.Item1)
|
||||
{
|
||||
return NotFound(response);
|
||||
}
|
||||
|
||||
foreach (var file in contactFile)
|
||||
{
|
||||
var queue = ctMgr.QueueContact(file);
|
||||
queue.UserId = user.Item2.Id;
|
||||
queue.DateCreated = DateTime.Now;
|
||||
queueRepo.Create(queue);
|
||||
|
||||
response.Data.Add(queue);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError($"An error occurred: {ex.Message}");
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Responses
|
||||
public class CreateQueueResponse
|
||||
{
|
||||
#region Properties
|
||||
[JsonProperty("data")]
|
||||
public List<ContactCreationQueue> Data { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public CreateQueueResponse()
|
||||
{
|
||||
this.Data = new List<ContactCreationQueue>();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TextSender_API.Models;
|
||||
using TextSender_API.Repositories;
|
||||
|
||||
namespace TextSender_API.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/v1/contact")]
|
||||
public class ContactsController : ControllerBase
|
||||
{
|
||||
#region Fields
|
||||
private IConfiguration _config;
|
||||
private readonly ILogger<ContactsController> _logger;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ContactsController(ILogger<ContactsController> logger, IConfiguration config)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
[HttpPost("new"), DisableRequestSizeLimit]
|
||||
public IActionResult CreateContact([FromBody] Contact contact)
|
||||
{
|
||||
var response = new CreateContactResponse();
|
||||
var connString = this._config.GetConnectionString("MongoDBURI");
|
||||
var userRepo = new UsersRepository(connString!);
|
||||
var contactsRepo = new ContactsRepository(connString!);
|
||||
|
||||
try
|
||||
{
|
||||
var contacts = contactsRepo.RetrieveAll();
|
||||
var users = userRepo.RetrieveAllUsers();
|
||||
|
||||
if (users.Count() == 0)
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
|
||||
if (contacts.Count() > 0)
|
||||
{
|
||||
if (contacts.Exists(c => c.PhoneNumber!.Equals(contact.PhoneNumber)) ||
|
||||
string.IsNullOrEmpty(contact.UserID))
|
||||
{
|
||||
// The Contact already exists
|
||||
return BadRequest(response);
|
||||
}
|
||||
}
|
||||
|
||||
if (!userRepo.Exists(contact.UserID!).Item1)
|
||||
{
|
||||
return BadRequest(response);
|
||||
}
|
||||
|
||||
contact.DateCreated = DateTime.Now;
|
||||
contactsRepo.Create(contact);
|
||||
response.Data.Add(contact);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError($"An error occurred: {ex.Message}");
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Responses
|
||||
public class CreateContactResponse
|
||||
{
|
||||
#region Properties
|
||||
[JsonProperty("data")]
|
||||
public List<Contact> Data { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public CreateContactResponse()
|
||||
{
|
||||
this.Data = new List<Contact>();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using TextSender_API.Models;
|
||||
using TextSender_API.Repositories;
|
||||
|
||||
namespace TextSender_API.Controllers;
|
||||
|
||||
public class PasswordVerification
|
||||
{
|
||||
#region Fiends
|
||||
private UsersRepository _userRepo;
|
||||
private SaltRepository _saltRepo;
|
||||
private int _keySize = 64;
|
||||
private int _iterations = 350000;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public PasswordVerification(UsersRepository userRepo, SaltRepository saltRepo)
|
||||
{
|
||||
this._userRepo = userRepo;
|
||||
this._saltRepo = saltRepo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
public bool VerifyPassword(User user, string password)
|
||||
{
|
||||
var salt = this._saltRepo.Retrieve(user.Id!);
|
||||
var hashedPassword = this.HashPassword(user, salt, password);
|
||||
|
||||
return hashedPassword.Equals(user.Password);
|
||||
}
|
||||
public Salt CreateSalt(User user)
|
||||
{
|
||||
var salt = new Salt();
|
||||
salt.Key = RandomNumberGenerator.GetBytes(this._keySize);
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
public string HashPassword(User user, Salt salt, string password = "")
|
||||
{
|
||||
var hashAlgorithm = HashAlgorithmName.SHA512;
|
||||
var dbHashedPassword = user.Password;
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
user.Password = password;
|
||||
}
|
||||
|
||||
var hash = Rfc2898DeriveBytes.Pbkdf2(
|
||||
Encoding.UTF8.GetBytes(user.Password!),
|
||||
salt.Key!,
|
||||
this._iterations,
|
||||
hashAlgorithm,
|
||||
this._keySize);
|
||||
|
||||
user.Password = dbHashedPassword;
|
||||
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TextSender_API.Models;
|
||||
using TextSender_API.Repositories;
|
||||
|
||||
namespace TextSender_API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/user")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<UsersController> _logger;
|
||||
private IConfiguration _config;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
public UsersController(ILogger<UsersController> logger, IConfiguration config)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
private string AllScopes()
|
||||
{
|
||||
var allScopes = new List<String>()
|
||||
{
|
||||
"create:contact"
|
||||
};
|
||||
|
||||
var scopes = string.Empty;
|
||||
|
||||
for (var i = 0; i < allScopes.Count; i++)
|
||||
{
|
||||
if (i == allScopes.Count - 1)
|
||||
{
|
||||
scopes += allScopes[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
scopes += allScopes[i] + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return scopes;
|
||||
}
|
||||
|
||||
private List<Claim> Payload()
|
||||
{
|
||||
var expLimit = 30;
|
||||
var currentDate = DateTime.Now;
|
||||
var expiredDate = currentDate.AddMinutes(expLimit);
|
||||
var issued = Math.Floor((currentDate - DateTime.UnixEpoch).TotalSeconds);
|
||||
var expires = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds);
|
||||
var issuer = this._config["JWT:Issuer"];
|
||||
var audience = this._config["JWT:Audience"];
|
||||
var subject = this._config["JWT:Subject"];
|
||||
|
||||
var claim = new List<System.Security.Claims.Claim>()
|
||||
{
|
||||
new System.Security.Claims.Claim("scope", AllScopes(), "string"),
|
||||
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString(), "string"),
|
||||
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Aud, audience!),
|
||||
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iss, issuer!),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, subject!)
|
||||
};
|
||||
|
||||
return claim;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public IActionResult Login([FromBody] User userRequest)
|
||||
{
|
||||
var response = new LoginResponse();
|
||||
|
||||
try
|
||||
{
|
||||
var connString = this._config.GetConnectionString("MongoDBURI");
|
||||
var userRepo = new UsersRepository(connString!);
|
||||
var saltRepo = new SaltRepository(connString!);
|
||||
var pwdVerify = new PasswordVerification(userRepo, saltRepo);
|
||||
var result = userRepo.Exists(userRequest);
|
||||
|
||||
if (result.Item1 && pwdVerify.VerifyPassword(result.Item2, userRequest.Password!))
|
||||
{
|
||||
var user = result.Item2;
|
||||
var token = new Token();
|
||||
token.Issued = DateTime.Now;
|
||||
token.UserId = user.Id;
|
||||
token.TokenType = "JWT";
|
||||
|
||||
var payload = Payload();
|
||||
payload.Add(new System.Security.Claims.Claim("user_id", user.Id!, ClaimValueTypes.String));
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._config["JWT:Secret"]!));
|
||||
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var tok = new JwtSecurityToken(
|
||||
this._config["JWT:Issuer"],
|
||||
this._config["JWT:Audience"],
|
||||
payload,
|
||||
expires: DateTime.UtcNow.AddMinutes(30),
|
||||
signingCredentials: signIn);
|
||||
|
||||
token.AccessToken = new JwtSecurityTokenHandler().WriteToken(tok);
|
||||
|
||||
var expClaim = payload.FirstOrDefault(cl =>
|
||||
{
|
||||
return cl.Type.Equals("exp");
|
||||
});
|
||||
|
||||
if (expClaim != null)
|
||||
{
|
||||
var expiredDate = DateTime.Parse(expClaim!.Value);
|
||||
var exp = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds);
|
||||
}
|
||||
|
||||
response.Data.Add(token);
|
||||
Ok(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound(response);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError($"An error occurred: {ex.Message}");
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("register"), DisableRequestSizeLimit]
|
||||
public IActionResult Upload([FromBody] User userRequest)
|
||||
{
|
||||
var response = new RegisterUserResponse();
|
||||
|
||||
try
|
||||
{
|
||||
var connString = this._config.GetConnectionString("MongoDBURI");
|
||||
var userRepo = new UsersRepository(connString!);
|
||||
var saltRepo = new SaltRepository(connString!);
|
||||
var allUsers = userRepo.RetrieveAllUsers();
|
||||
var result = allUsers.Exists(ea => ea.Username!.Equals(userRequest.Username));
|
||||
|
||||
if (!result)
|
||||
{
|
||||
var pwdVerify = new PasswordVerification(userRepo, saltRepo);
|
||||
var salt = pwdVerify.CreateSalt(userRequest);
|
||||
var hashedPassword = pwdVerify.HashPassword(userRequest, salt);
|
||||
|
||||
this._logger.LogInformation("Creating user");
|
||||
userRequest.Password = hashedPassword;
|
||||
userRequest.DateCreated = DateTime.Now;
|
||||
userRepo.CreateUser(userRequest);
|
||||
|
||||
response.Data.Add(userRequest);
|
||||
|
||||
salt.UserId = userRequest.Id;
|
||||
saltRepo.Create(salt);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogInformation("User already exists");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError($"An error occurred: {ex.Message}");
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Responses
|
||||
public class LoginResponse
|
||||
{
|
||||
#region Properties
|
||||
[JsonProperty("data")]
|
||||
public List<Token> Data { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public LoginResponse()
|
||||
{
|
||||
this.Data = new List<Token>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class RegisterUserResponse
|
||||
{
|
||||
#region Properties
|
||||
[JsonProperty("data")]
|
||||
public List<User> Data { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public RegisterUserResponse()
|
||||
{
|
||||
this.Data = new List<User>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TextSender_API.Models;
|
||||
|
||||
public class Contact
|
||||
{
|
||||
#region Properties
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.ObjectId)]
|
||||
[JsonProperty("id")]
|
||||
public string? Id { get; set; }
|
||||
[BsonElement("firstname")]
|
||||
[JsonProperty("firstname")]
|
||||
public string? Firstname { get; set; }
|
||||
[BsonElement("lastname")]
|
||||
[JsonProperty("lastname")]
|
||||
public string? Lastname { get; set; }
|
||||
[BsonElement("phonenumber")]
|
||||
[JsonProperty("phonenumber")]
|
||||
public string? PhoneNumber { get; set; }
|
||||
[BsonElement("date_created")]
|
||||
[JsonProperty("date_created")]
|
||||
public DateTime DateCreated { get; set; }
|
||||
// [BsonRepresentation(BsonType.ObjectId)]
|
||||
[BsonElement("user_id")]
|
||||
[JsonProperty("user_id")]
|
||||
public string? UserID { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class ContactCreationQueue
|
||||
{
|
||||
#region Properties
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.ObjectId)]
|
||||
public string? Id { get; set; }
|
||||
[BsonElement("filepath")]
|
||||
public string? Filepath { get; set; }
|
||||
[BsonElement("status")]
|
||||
public string? Status { get; set; }
|
||||
[BsonElement("user_id")]
|
||||
public string? UserId { get; set; }
|
||||
[BsonElement("date_created")]
|
||||
public DateTime? DateCreated { get; set; }
|
||||
[BsonElement("processed")]
|
||||
public bool? Processed { get; set; }
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace TextSender_API.Models;
|
||||
|
||||
public class Text
|
||||
{
|
||||
#region Properties
|
||||
public int TextID { get; set; }
|
||||
public string? Subject { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public int UserID { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace TextSender_API.Models;
|
||||
|
||||
public class TextQueue
|
||||
{
|
||||
#region Properties
|
||||
public int TextQueueID { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? ContactPhoneNumber { get; set; }
|
||||
public int TextID { get; set; }
|
||||
public int UserID { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
public DateTime ExecutionDate { get; set; }
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TextSender_API.Models;
|
||||
|
||||
public class Token
|
||||
{
|
||||
#region Properties
|
||||
[JsonProperty("access_token")]
|
||||
public string? AccessToken { get; set; }
|
||||
[JsonProperty("token_type")]
|
||||
public string? TokenType { get; set; }
|
||||
[JsonProperty("issued")]
|
||||
public DateTime? Issued { get; set; }
|
||||
[JsonProperty("id")]
|
||||
public string? UserId { get; set; }
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TextSender_API.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
#region Properties
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.ObjectId)]
|
||||
[JsonProperty("id")]
|
||||
public string? Id { get; set; }
|
||||
[BsonElement("firstname")]
|
||||
[JsonProperty("firstname")]
|
||||
public string? Firstname { get; set; }
|
||||
[BsonElement("lastname")]
|
||||
[JsonProperty("lastname")]
|
||||
public string? Lastname { get; set; }
|
||||
[BsonElement("phonenumber")]
|
||||
[JsonProperty("phone_number")]
|
||||
public string? PhoneNumber { get; set; }
|
||||
[BsonElement("username")]
|
||||
[JsonProperty("username")]
|
||||
public string? Username { get; set; }
|
||||
[BsonElement("password")]
|
||||
[JsonProperty("password")]
|
||||
public string? Password { get; set; }
|
||||
[BsonElement("datecreated")]
|
||||
[JsonProperty("date_created")]
|
||||
public DateTime? DateCreated { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class Salt
|
||||
{
|
||||
#region Properties
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.ObjectId)]
|
||||
public string? Id { get; set; }
|
||||
[BsonElement("key")]
|
||||
public byte[]? Key { get; set; }
|
||||
[BsonElement("user_id")]
|
||||
public string? UserId { get; set; }
|
||||
#endregion
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using TextSender_API.Repositories;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.SaveToken = true;
|
||||
options.TokenValidationParameters = new TokenValidationParameters()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = builder.Configuration["JWT:Audience"],
|
||||
ValidIssuer = builder.Configuration["JWT:Issuer"],
|
||||
RequireExpirationTime = false,
|
||||
ValidateIssuerSigningKey = false,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"]!))
|
||||
};
|
||||
});
|
||||
|
||||
// var connectionString = builder.Configuration.GetConnectionString("MongoDBURI");
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
// app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:22514",
|
||||
"sslPort": 44361
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5168",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7154;http://localhost:5168",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,11 @@ An API used to send texts messages to multiple contacts.
|
||||
## Getting started
|
||||
|
||||
Installing dependencies
|
||||
`npm install`
|
||||
```BASH
|
||||
dotnet restore
|
||||
```
|
||||
|
||||
Running server
|
||||
`node app.js`
|
||||
```BASH
|
||||
dotnet run
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
namespace TextSender_API.Repositories;
|
||||
|
||||
public class BaseRepository
|
||||
{
|
||||
#region Fields
|
||||
protected string? _connectionString;
|
||||
protected string _databaseName = "textSender";
|
||||
protected string? _tableName;
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
using TextSender_API.Models;
|
||||
|
||||
namespace TextSender_API.Repositories;
|
||||
|
||||
|
||||
public class ContactCreationQueueRepository : BaseRepository
|
||||
{
|
||||
#region Fields
|
||||
private readonly IMongoCollection<ContactCreationQueue> _bookCollection;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public ContactCreationQueueRepository(string connectionString)
|
||||
{
|
||||
this._connectionString = connectionString;
|
||||
this._tableName = "contactCreationQueue";
|
||||
var client = this.InitializeClient();
|
||||
this._bookCollection = client.GetDatabase(this._databaseName).GetCollection<ContactCreationQueue>(this._tableName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Metods
|
||||
public Tuple<bool, ContactCreationQueue> Exists(string id)
|
||||
{
|
||||
var allQueue = this.RetrieveAll();
|
||||
var result = allQueue.FirstOrDefault(q => q.Id!.Equals(id));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return new Tuple<bool, ContactCreationQueue>(true, result!);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Tuple<bool, ContactCreationQueue>(false, new ContactCreationQueue());
|
||||
}
|
||||
}
|
||||
|
||||
public void Create(ContactCreationQueue queue)
|
||||
{
|
||||
this._bookCollection.InsertOne(queue);
|
||||
}
|
||||
|
||||
public List<ContactCreationQueue> RetrieveAll()
|
||||
{
|
||||
return this._bookCollection.Find(_ => true).ToList();
|
||||
}
|
||||
|
||||
public void Update(ContactCreationQueue queue)
|
||||
{
|
||||
var filter = Builders<ContactCreationQueue>.Filter.Eq(q => q.Id, queue.Id);
|
||||
this._bookCollection.ReplaceOne(filter, queue);
|
||||
}
|
||||
|
||||
public void Delete(ContactCreationQueue queue)
|
||||
{
|
||||
var filter = Builders<ContactCreationQueue>.Filter.Eq(q => q.Id, queue.Id);
|
||||
this._bookCollection.DeleteOne(filter);
|
||||
}
|
||||
|
||||
private MongoClient InitializeClient()
|
||||
{
|
||||
return new MongoClient(this._connectionString);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class ContactsRepository : BaseRepository
|
||||
{
|
||||
#region Fields
|
||||
private readonly IMongoCollection<Contact> _bookCollection;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public ContactsRepository(string connectionString)
|
||||
{
|
||||
this._connectionString = connectionString;
|
||||
this._tableName = "contacts";
|
||||
var client = this.InitializeClient();
|
||||
this._bookCollection = client.GetDatabase(this._databaseName).GetCollection<Contact>(this._tableName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Metods
|
||||
public Tuple<bool, Contact> Exists(string id)
|
||||
{
|
||||
var allContacts= this.RetrieveAll();
|
||||
var result = allContacts.FirstOrDefault(c => c.Id!.Equals(id));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return new Tuple<bool, Contact>(true, result!);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Tuple<bool, Contact>(false, new Contact());
|
||||
}
|
||||
}
|
||||
|
||||
public void Create(Contact contact)
|
||||
{
|
||||
this._bookCollection.InsertOne(contact);
|
||||
}
|
||||
|
||||
public List<Contact> RetrieveAll()
|
||||
{
|
||||
return this._bookCollection.Find(_ => true).ToList();
|
||||
}
|
||||
|
||||
public void Update(Contact contact)
|
||||
{
|
||||
var filter = Builders<Contact>.Filter.Eq(c => c.Id, contact.Id);
|
||||
this._bookCollection.ReplaceOne(filter, contact);
|
||||
}
|
||||
|
||||
public void Delete(Contact contact)
|
||||
{
|
||||
var filter = Builders<Contact>.Filter.Eq(c => c.Id, contact.Id);
|
||||
this._bookCollection.DeleteOne(filter);
|
||||
}
|
||||
|
||||
private MongoClient InitializeClient()
|
||||
{
|
||||
return new MongoClient(this._connectionString);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
using TextSender_API.Models;
|
||||
|
||||
namespace TextSender_API.Repositories;
|
||||
|
||||
|
||||
public class UsersRepository : BaseRepository
|
||||
{
|
||||
#region Fields
|
||||
private readonly IMongoCollection<User> _bookCollection;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public UsersRepository(string connectionString)
|
||||
{
|
||||
this._connectionString = connectionString;
|
||||
this._tableName = "users";
|
||||
var client = this.InitializeClient();
|
||||
this._bookCollection = client.GetDatabase(this._databaseName).GetCollection<User>(this._tableName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Metods
|
||||
public Tuple<bool, User> Exists(User user)
|
||||
{
|
||||
var allUsers = this.RetrieveAllUsers();
|
||||
var result = allUsers.FirstOrDefault(ea => ea.Username!.Equals(user.Username));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return new Tuple<bool, User>(true, result!);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Tuple<bool, User>(false, new User());
|
||||
}
|
||||
}
|
||||
|
||||
public Tuple<bool, User> Exists(string userId)
|
||||
{
|
||||
var allUsers = this.RetrieveAllUsers();
|
||||
var result = allUsers.FirstOrDefault(ea => ea.Id!.Equals(userId));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return new Tuple<bool, User>(true, result!);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Tuple<bool, User>(false, new User());
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateUser(User user)
|
||||
{
|
||||
this._bookCollection.InsertOne(user);
|
||||
}
|
||||
|
||||
public List<User> RetrieveAllUsers()
|
||||
{
|
||||
return this._bookCollection.Find(_ => true).ToList();
|
||||
}
|
||||
|
||||
public void Update(User user)
|
||||
{
|
||||
var filter = Builders<User>.Filter.Eq(u => u.Id, user.Id);
|
||||
this._bookCollection.ReplaceOne(filter, user);
|
||||
}
|
||||
|
||||
public void Delete(User user)
|
||||
{
|
||||
var filter = Builders<User>.Filter.Eq(u => u.Id, user.Id);
|
||||
this._bookCollection.DeleteOne(filter);
|
||||
}
|
||||
|
||||
private MongoClient InitializeClient()
|
||||
{
|
||||
return new MongoClient(this._connectionString);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class SaltRepository : BaseRepository
|
||||
{
|
||||
#region Fields
|
||||
private readonly IMongoCollection<Salt> _bookCollection;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public SaltRepository(string connectionString)
|
||||
{
|
||||
this._connectionString = connectionString;
|
||||
this._tableName = "salt";
|
||||
var client = this.InitializeClient();
|
||||
this._bookCollection = client.GetDatabase(this._databaseName).GetCollection<Salt>(this._tableName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Metods
|
||||
public void Create(Salt salt)
|
||||
{
|
||||
this._bookCollection.InsertOne(salt);
|
||||
}
|
||||
|
||||
public List<Salt> RetrieveAll()
|
||||
{
|
||||
return this._bookCollection.Find(_ => true).ToList();
|
||||
}
|
||||
|
||||
public Salt Retrieve(string userId)
|
||||
{
|
||||
return this._bookCollection.Find(s => s.UserId!.Equals(userId)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void Update(Salt salt)
|
||||
{
|
||||
var filter = Builders<Salt>.Filter.Eq(u => u.Id, salt.Id);
|
||||
this._bookCollection.ReplaceOne(filter, salt);
|
||||
}
|
||||
|
||||
public void Delete(Salt salt)
|
||||
{
|
||||
var filter = Builders<Salt>.Filter.Eq(u => u.Id, salt.Id);
|
||||
this._bookCollection.DeleteOne(filter);
|
||||
}
|
||||
|
||||
private MongoClient InitializeClient()
|
||||
{
|
||||
return new MongoClient(this._connectionString);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>TextSender_API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.11" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.21.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,74 +0,0 @@
|
||||
require('./models.js')
|
||||
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
const port = 3000
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
// User Routes
|
||||
app.post('/api/v1/user/new', (req, res) => {
|
||||
res.send('Posting')
|
||||
console.log(req.body)
|
||||
|
||||
// const usr = new User()
|
||||
|
||||
let username = req.body['username']
|
||||
|
||||
// usr.username = username
|
||||
|
||||
console.log(`Username: ${username}`)
|
||||
})
|
||||
|
||||
// User routes
|
||||
// TODO: Not implemented
|
||||
app.post('/api/v1/login', (req, res) => {
|
||||
res.send('Implement');
|
||||
})
|
||||
|
||||
// Text Routes
|
||||
// Add a new text
|
||||
// TODO: Not implemented
|
||||
app.post('/api/v1/text/new', (req, res) => {
|
||||
res.send('Create new text')
|
||||
})
|
||||
|
||||
// Retrieve texts
|
||||
// TODO: Not implemented
|
||||
app.get('/api/v1/text', (req, res) => {
|
||||
res.send('Create new text')
|
||||
})
|
||||
|
||||
// Queue a text for sending
|
||||
// TODO: Not implemented
|
||||
app.post('/api/v1/text/queue', (req, res) => {
|
||||
res.send('Queue text')
|
||||
})
|
||||
|
||||
// Create contact
|
||||
// TODO: Not implemented
|
||||
app.post('/api/v1/contacts/new', (req, res) => {
|
||||
res.send('Creating contact')
|
||||
})
|
||||
|
||||
// Retrieving contacts
|
||||
// TODO: Not implemented
|
||||
app.get('/api/v1/contacts', (req, res) => {
|
||||
res.send('Retrieving contact')
|
||||
})
|
||||
|
||||
// Editing a contact
|
||||
// TODO: Not implemented
|
||||
app.patch('/api/v1/contacts', (req, res) => {
|
||||
res.send('Editing contact')
|
||||
})
|
||||
|
||||
// Deleting a contact
|
||||
// TODO: Not implemented
|
||||
app.delete('/api/v1/contacts', (req, res) => {
|
||||
res.send('Delete contact')
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`textsender-api listening on port ${port}`)
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"JWT": {
|
||||
"Audience": "",
|
||||
"Issuer": "",
|
||||
"Secret": "",
|
||||
"Subject": ""
|
||||
},
|
||||
"Paths": {
|
||||
"ContactQueueDirectory": "/home/user/path/"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"MongoDBURI": "mongodb+srv://<username>:<password>@cluster0.abc.mongodb.net/?retryWrites=true&w=majority"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"JWT": {
|
||||
"Audience": "",
|
||||
"Issuer": "",
|
||||
"Secret": "",
|
||||
"Subject": ""
|
||||
},
|
||||
"Paths": {
|
||||
"ContactQueueDirectory": "/home/user/path/"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"MongoDBURI": "mongodb+srv://<username>:<password>@cluster0.abc.mongodb.net/?retryWrites=true&w=majority"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
const ObjectId = Schema.ObjectId;
|
||||
|
||||
const User = new Schema({
|
||||
userID: { type: String },
|
||||
firstName: { type: String },
|
||||
lastName: { type: String },
|
||||
phoneNumber: { type: String },
|
||||
username: { type: String },
|
||||
password: { type: String },
|
||||
dateCreated: { type: Date, default: Date.now }
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "textsender-api",
|
||||
"version": "0.1.0",
|
||||
"description": "Web API for queueing text message notifications",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "kdeng00",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mongodb": "^5.3.0",
|
||||
"mongoose": "^7.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
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}") = "TextSender-API", "TextSender-API.csproj", "{AD184BBA-C715-4112-95A9-530F05323BEE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AD184BBA-C715-4112-95A9-530F05323BEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AD184BBA-C715-4112-95A9-530F05323BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AD184BBA-C715-4112-95A9-530F05323BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AD184BBA-C715-4112-95A9-530F05323BEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {70126048-DECC-43EE-9956-2CEF937B7AA1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Reference in New Issue
Block a user