From 48d3cf0ba23b8658f142e2581bf3b0e477918d9d Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 17 Sep 2023 20:44:31 -0400 Subject: [PATCH 01/34] Removed javascript project Choosing to use .NET instead --- app.js | 74 ---------------------------------------------------- models.js | 14 ---------- package.json | 16 ------------ 3 files changed, 104 deletions(-) delete mode 100644 app.js delete mode 100644 models.js delete mode 100644 package.json diff --git a/app.js b/app.js deleted file mode 100644 index c9441eb..0000000 --- a/app.js +++ /dev/null @@ -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}`) -}) diff --git a/models.js b/models.js deleted file mode 100644 index 53d57f2..0000000 --- a/models.js +++ /dev/null @@ -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 } -}); diff --git a/package.json b/package.json deleted file mode 100644 index 39db95f..0000000 --- a/package.json +++ /dev/null @@ -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" - } -} From f4a6dc1c6a215373450b7770c48af127ab9252f8 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 17 Sep 2023 20:48:50 -0400 Subject: [PATCH 02/34] .NET template project --- Controllers/WeatherForecastController.cs | 32 ++++++++++++++++++ Program.cs | 25 +++++++++++++++ Properties/launchSettings.json | 41 ++++++++++++++++++++++++ TextSender-API.csproj | 15 +++++++++ WeatherForecast.cs | 12 +++++++ appsettings.Development.json | 8 +++++ appsettings.json | 9 ++++++ 7 files changed, 142 insertions(+) create mode 100644 Controllers/WeatherForecastController.cs create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 TextSender-API.csproj create mode 100644 WeatherForecast.cs create mode 100644 appsettings.Development.json create mode 100644 appsettings.json diff --git a/Controllers/WeatherForecastController.cs b/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..4cc5e9e --- /dev/null +++ b/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TextSender_API.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..e609dd5 --- /dev/null +++ b/Properties/launchSettings.json @@ -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" + } + } + } +} diff --git a/TextSender-API.csproj b/TextSender-API.csproj new file mode 100644 index 0000000..5038d09 --- /dev/null +++ b/TextSender-API.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + enable + TextSender_API + + + + + + + + diff --git a/WeatherForecast.cs b/WeatherForecast.cs new file mode 100644 index 0000000..7df7d6d --- /dev/null +++ b/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace TextSender_API; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From ee2068a87e6742177f9ce8eb4be8c3e1bb4e9b9e Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 17 Sep 2023 20:52:27 -0400 Subject: [PATCH 03/34] Added gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1746e32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj From 3d45646a962981172547f1517bc2cf107d05f657 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 17 Sep 2023 21:05:12 -0400 Subject: [PATCH 04/34] Added Models --- Models/Contact.cs | 15 +++++++++++++++ Models/Text.cs | 14 ++++++++++++++ Models/TextQueue.cs | 16 ++++++++++++++++ Models/User.cs | 16 ++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 Models/Contact.cs create mode 100644 Models/Text.cs create mode 100644 Models/TextQueue.cs create mode 100644 Models/User.cs diff --git a/Models/Contact.cs b/Models/Contact.cs new file mode 100644 index 0000000..58e3446 --- /dev/null +++ b/Models/Contact.cs @@ -0,0 +1,15 @@ +using System; + +namespace TextSender_API.Models; + +public class Contact +{ +#region Properties + public int ContactID { get; set; } + public string Firstname { get; set; } + public string Lastname { get; set; } + public string PhoneNumber { get; set; } + public DateTime DateCreated { get; set; } + public int UserID { get; set; } +#endregion +} diff --git a/Models/Text.cs b/Models/Text.cs new file mode 100644 index 0000000..53595fa --- /dev/null +++ b/Models/Text.cs @@ -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; get; } +#endregion +} diff --git a/Models/TextQueue.cs b/Models/TextQueue.cs new file mode 100644 index 0000000..8b45bc8 --- /dev/null +++ b/Models/TextQueue.cs @@ -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 +} diff --git a/Models/User.cs b/Models/User.cs new file mode 100644 index 0000000..dd590b5 --- /dev/null +++ b/Models/User.cs @@ -0,0 +1,16 @@ +using System; + +namespace TextSender_API.Models; + +public class User +{ +#region Properties + public int UserID { get; set; } + public string Firstname { get; set; } + public string Lastname { get; set; } + public string PhoneNumber { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public DateTime DateCreated { get; set; } +#endregion +} From 00c20c806a89b833d748fd88663f0b7508f39087 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 17 Sep 2023 21:09:03 -0400 Subject: [PATCH 05/34] Fix build issue Property had two two get keywords declared --- Models/Text.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Text.cs b/Models/Text.cs index 53595fa..35c0d9e 100644 --- a/Models/Text.cs +++ b/Models/Text.cs @@ -9,6 +9,6 @@ public class Text public string Subject { get; set; } public string Message { get; set; } public int UserID { get; set; } - public DateTime DateCreated { get; get; } + public DateTime DateCreated { get; set; } #endregion } From 44958b5c18d9c88e55a745a5a9d652eace24c208 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 18 Sep 2023 20:47:39 -0400 Subject: [PATCH 06/34] Added new class Added template endpoint to upload contact files. These are just plain text files of phone numbers separated by a new line --- Controllers/ContactQueueCreationController.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Controllers/ContactQueueCreationController.cs diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs new file mode 100644 index 0000000..0cfe19a --- /dev/null +++ b/Controllers/ContactQueueCreationController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TextSender_API.Controllers; + +[ApiController] +[Route("api/v1/contact/queue")] +public class ContactQueueCreationController : ControllerBase +{ + #region Fields + private readonly ILogger _logger; + #endregion + + + #region Constructors + + public ContactQueueCreationController(ILogger logger) + { + this._logger = logger; + } + #endregion + + #region Methods + [HttpPost("upload"), DisableRequestSizeLimit] + public IActionResult Upload([FromForm(Name = "file")] List contactFile, + [FromForm(Name = "user_id")] string userID) + { + return Ok(); + } + #endregion +} \ No newline at end of file From 713fa01d2518215e8e7d59c70e9f7b6b62fb36e5 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 19 Sep 2023 19:00:30 -0400 Subject: [PATCH 07/34] Updated config files --- appsettings.Development.json | 3 +++ appsettings.json | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/appsettings.Development.json b/appsettings.Development.json index 0c208ae..6dff811 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -4,5 +4,8 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" } } diff --git a/appsettings.json b/appsettings.json index 10f68b8..c249556 100644 --- a/appsettings.json +++ b/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" + } } From 179acc1920649ec0d21bfa0709814dfb898c70d1 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 19 Sep 2023 19:02:31 -0400 Subject: [PATCH 08/34] Added dependencies --- TextSender-API.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TextSender-API.csproj b/TextSender-API.csproj index 5038d09..bd2bfef 100644 --- a/TextSender-API.csproj +++ b/TextSender-API.csproj @@ -9,6 +9,8 @@ + + From 18ebc9eefae77f788e5565d655ad9fb3646e8835 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 19 Sep 2023 19:36:17 -0400 Subject: [PATCH 09/34] Saving changes --- Controllers/RegisterController.cs | 32 +++++++++++++++++++++++++++++++ Models/User.cs | 25 +++++++++++++++++------- Repositories/UserRepository.cs | 17 ++++++++++++++++ 3 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 Controllers/RegisterController.cs create mode 100644 Repositories/UserRepository.cs diff --git a/Controllers/RegisterController.cs b/Controllers/RegisterController.cs new file mode 100644 index 0000000..d706c72 --- /dev/null +++ b/Controllers/RegisterController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +using TextSender_API.Models; + +namespace TextSender_API.Controllers; + +[ApiController] +[Route("api/v1/user")] +public class RegisterController : ControllerBase +{ + #region Fields + private readonly ILogger _logger; + #endregion + + + #region Constructors + + public RegisterController(ILogger logger) + { + this._logger = logger; + } + #endregion + + #region Methods + [HttpPost("register"), DisableRequestSizeLimit] + public IActionResult Upload([FromForm(Name = "file")] List contactFile, + [FromForm(Name = "user_id")] string userID) + { + return Ok(); + } + #endregion +} diff --git a/Models/User.cs b/Models/User.cs index dd590b5..da52645 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -1,16 +1,27 @@ using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; + namespace TextSender_API.Models; public class User { #region Properties - public int UserID { get; set; } - public string Firstname { get; set; } - public string Lastname { get; set; } - public string PhoneNumber { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public DateTime DateCreated { get; set; } + [BsonElement("_id")] + public ObjectId UserID { get; set; } + [BsonElement("firstname")] + public string? Firstname { get; set; } + [BsonElement("lastname")] + public string? Lastname { get; set; } + [BsonElement("phonenumber")] + public string? PhoneNumber { get; set; } + [BsonElement("username")] + public string? Username { get; set; } + [BsonElement("password")] + public string? Password { get; set; } + [BsonElement("datecreated")] + public DateTime? DateCreated { get; set; } #endregion } diff --git a/Repositories/UserRepository.cs b/Repositories/UserRepository.cs new file mode 100644 index 0000000..df83640 --- /dev/null +++ b/Repositories/UserRepository.cs @@ -0,0 +1,17 @@ +using System; + +using MongoDB.Bson; +using MongoDB.Driver; + +namespace TextSender_API.Repositories; + + +public class UserRepository +{ + #region Properties + #endregion + + + #region Constructors + #endregion +} \ No newline at end of file From b8a9142f05172dc68d09eb0be45aab628b24b691 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Wed, 20 Sep 2023 20:46:58 -0400 Subject: [PATCH 10/34] Updated model and Repository class --- Models/User.cs | 4 ++-- Repositories/UserRepository.cs | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Models/User.cs b/Models/User.cs index da52645..673682f 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -9,8 +9,8 @@ namespace TextSender_API.Models; public class User { #region Properties - [BsonElement("_id")] - public ObjectId UserID { get; set; } + // [BsonElement("_id")] + public ObjectId Id { get; set; } [BsonElement("firstname")] public string? Firstname { get; set; } [BsonElement("lastname")] diff --git a/Repositories/UserRepository.cs b/Repositories/UserRepository.cs index df83640..c0c5101 100644 --- a/Repositories/UserRepository.cs +++ b/Repositories/UserRepository.cs @@ -3,15 +3,40 @@ using System; using MongoDB.Bson; using MongoDB.Driver; +using TextSender_API.Models; + namespace TextSender_API.Repositories; public class UserRepository { - #region Properties + #region Fields + private string _connectionString; + private string _databaseName; + private string _tableName; #endregion #region Constructors + public UserRepository(string connectionString) + { + this._connectionString = connectionString; + this._databaseName = "TextSender"; + this._tableName = "Users"; + } + #endregion + + #region Metods + public void CreateUser(User user) + { + var client = this.InitializeClient(); + + var coll = client.GetDatabase(this._databaseName).GetCollection(this._tableName); + coll.InsertOne(user); + } + private MongoClient InitializeClient() + { + return new MongoClient(this._connectionString); + } #endregion } \ No newline at end of file From 8f090bd2e2df9dd8bbf1edb30474e6ab9206eab1 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 26 Sep 2023 06:28:45 -0400 Subject: [PATCH 11/34] Able to create and read data from the DB --- Controllers/RegisterController.cs | 33 ++++++++++++++++++++++++++++--- Models/User.cs | 6 ++++-- Program.cs | 5 +++++ Repositories/UserRepository.cs | 27 +++++++++++++++++++++---- TextSender-API.csproj | 1 + 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Controllers/RegisterController.cs b/Controllers/RegisterController.cs index d706c72..e15a45a 100644 --- a/Controllers/RegisterController.cs +++ b/Controllers/RegisterController.cs @@ -1,6 +1,9 @@ +using System.Linq; + using Microsoft.AspNetCore.Mvc; using TextSender_API.Models; +using TextSender_API.Repositories; namespace TextSender_API.Controllers; @@ -10,22 +13,46 @@ public class RegisterController : ControllerBase { #region Fields private readonly ILogger _logger; + private IConfiguration _config; #endregion #region Constructors - public RegisterController(ILogger logger) + public RegisterController(ILogger logger, IConfiguration config) { this._logger = logger; + this._config = config; } #endregion #region Methods [HttpPost("register"), DisableRequestSizeLimit] - public IActionResult Upload([FromForm(Name = "file")] List contactFile, - [FromForm(Name = "user_id")] string userID) + public IActionResult Upload([FromBody] User userRequest) { + try + { + var connString = this._config.GetConnectionString("MongoDBURI"); + var userRepo = new UserRepository(connString); + var allUsers = userRepo.RetrieveAllUsers(); + var result = allUsers.Exists(ea => ea.Username.Equals(userRequest.Username)); + + if (!result) + { + this._logger.LogInformation("Creating user"); + userRequest.DateCreated = DateTime.Now; + userRepo.CreateUser(userRequest); + } + else + { + this._logger.LogInformation("User already exists"); + } + } + catch (Exception ex) + { + this._logger.LogError($"An error occurred: {ex.Message}"); + } + return Ok(); } #endregion diff --git a/Models/User.cs b/Models/User.cs index 673682f..9889755 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -2,6 +2,7 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Conventions; using MongoDB.Driver; namespace TextSender_API.Models; @@ -9,8 +10,9 @@ namespace TextSender_API.Models; public class User { #region Properties - // [BsonElement("_id")] - public ObjectId Id { get; set; } + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } [BsonElement("firstname")] public string? Firstname { get; set; } [BsonElement("lastname")] diff --git a/Program.cs b/Program.cs index 48863a6..065e1e6 100644 --- a/Program.cs +++ b/Program.cs @@ -1,8 +1,12 @@ +using Newtonsoft.Json; + 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(); @@ -16,6 +20,7 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } + app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/Repositories/UserRepository.cs b/Repositories/UserRepository.cs index c0c5101..03a34b4 100644 --- a/Repositories/UserRepository.cs +++ b/Repositories/UserRepository.cs @@ -14,6 +14,7 @@ public class UserRepository private string _connectionString; private string _databaseName; private string _tableName; + private readonly IMongoCollection _bookCollection; #endregion @@ -23,17 +24,35 @@ public class UserRepository this._connectionString = connectionString; this._databaseName = "TextSender"; this._tableName = "Users"; + var client = this.InitializeClient(); + this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); } #endregion #region Metods public void CreateUser(User user) { - var client = this.InitializeClient(); - - var coll = client.GetDatabase(this._databaseName).GetCollection(this._tableName); - coll.InsertOne(user); + this._bookCollection.InsertOne(user); + var i = 0; } + + public List RetrieveAllUsers() + { + return this._bookCollection.Find(_ => true).ToList(); + } + + public void Update(User user) + { + var filter = Builders.Filter.Eq(u => u.Id, user.Id); + this._bookCollection.ReplaceOne(filter, user); + } + + public void Delete(User user) + { + var filter = Builders.Filter.Eq(u => u.Id, user.Id); + this._bookCollection.DeleteOne(filter); + } + private MongoClient InitializeClient() { return new MongoClient(this._connectionString); diff --git a/TextSender-API.csproj b/TextSender-API.csproj index bd2bfef..3e29007 100644 --- a/TextSender-API.csproj +++ b/TextSender-API.csproj @@ -8,6 +8,7 @@ + From d2c84966b9ed1bf43fbb861207c39d660964c6ba Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 26 Sep 2023 18:58:46 -0400 Subject: [PATCH 12/34] Refactoring and password hashing --- Controllers/PasswordVerification.cs | 42 +++++++ ...gisterController.cs => UsersController.cs} | 19 ++- Models/User.cs | 14 +++ Repositories/UserRepository.cs | 61 ---------- Repositories/UsersRepository.cs | 112 ++++++++++++++++++ 5 files changed, 182 insertions(+), 66 deletions(-) create mode 100644 Controllers/PasswordVerification.cs rename Controllers/{RegisterController.cs => UsersController.cs} (61%) delete mode 100644 Repositories/UserRepository.cs create mode 100644 Repositories/UsersRepository.cs diff --git a/Controllers/PasswordVerification.cs b/Controllers/PasswordVerification.cs new file mode 100644 index 0000000..1bee7b0 --- /dev/null +++ b/Controllers/PasswordVerification.cs @@ -0,0 +1,42 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +using TextSender_API.Models; + +namespace TextSender_API.Controllers; + +public class PasswordVerification +{ + #region Fiends + private int _keySize = 64; + private int _iterations = 350000; + #endregion + + #region Constructors + #endregion + + #region Methods + public Salt CreateSalt(User user) + { + var salt = new Salt(); + salt.Key = RandomNumberGenerator.GetBytes(this._keySize); + + return salt; + } + + public string HashPassword(User user, Salt salt) + { + var hashAlgorithm = HashAlgorithmName.SHA512; + + var hash = Rfc2898DeriveBytes.Pbkdf2( + Encoding.UTF8.GetBytes(user.Password!), + salt.Key!, + this._iterations, + hashAlgorithm, + this._keySize); + + return Convert.ToHexString(hash); + } + #endregion +} \ No newline at end of file diff --git a/Controllers/RegisterController.cs b/Controllers/UsersController.cs similarity index 61% rename from Controllers/RegisterController.cs rename to Controllers/UsersController.cs index e15a45a..af82d06 100644 --- a/Controllers/RegisterController.cs +++ b/Controllers/UsersController.cs @@ -9,17 +9,17 @@ namespace TextSender_API.Controllers; [ApiController] [Route("api/v1/user")] -public class RegisterController : ControllerBase +public class UsersController : ControllerBase { #region Fields - private readonly ILogger _logger; + private readonly ILogger _logger; private IConfiguration _config; #endregion #region Constructors - public RegisterController(ILogger logger, IConfiguration config) + public UsersController(ILogger logger, IConfiguration config) { this._logger = logger; this._config = config; @@ -33,15 +33,24 @@ public class RegisterController : ControllerBase try { var connString = this._config.GetConnectionString("MongoDBURI"); - var userRepo = new UserRepository(connString); + var userRepo = new UsersRepository(connString!); + var saltRepo = new SaltRepository(connString!); var allUsers = userRepo.RetrieveAllUsers(); - var result = allUsers.Exists(ea => ea.Username.Equals(userRequest.Username)); + var result = allUsers.Exists(ea => ea.Username!.Equals(userRequest.Username)); if (!result) { + var pwdVerify = new PasswordVerification(); + 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); + + salt.UserId = userRequest.Id; + saltRepo.Create(salt); } else { diff --git a/Models/User.cs b/Models/User.cs index 9889755..4dc8992 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -3,6 +3,7 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.IdGenerators; using MongoDB.Driver; namespace TextSender_API.Models; @@ -27,3 +28,16 @@ public class User 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 +} diff --git a/Repositories/UserRepository.cs b/Repositories/UserRepository.cs deleted file mode 100644 index 03a34b4..0000000 --- a/Repositories/UserRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; - -using MongoDB.Bson; -using MongoDB.Driver; - -using TextSender_API.Models; - -namespace TextSender_API.Repositories; - - -public class UserRepository -{ - #region Fields - private string _connectionString; - private string _databaseName; - private string _tableName; - private readonly IMongoCollection _bookCollection; - #endregion - - - #region Constructors - public UserRepository(string connectionString) - { - this._connectionString = connectionString; - this._databaseName = "TextSender"; - this._tableName = "Users"; - var client = this.InitializeClient(); - this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); - } - #endregion - - #region Metods - public void CreateUser(User user) - { - this._bookCollection.InsertOne(user); - var i = 0; - } - - public List RetrieveAllUsers() - { - return this._bookCollection.Find(_ => true).ToList(); - } - - public void Update(User user) - { - var filter = Builders.Filter.Eq(u => u.Id, user.Id); - this._bookCollection.ReplaceOne(filter, user); - } - - public void Delete(User user) - { - var filter = Builders.Filter.Eq(u => u.Id, user.Id); - this._bookCollection.DeleteOne(filter); - } - - private MongoClient InitializeClient() - { - return new MongoClient(this._connectionString); - } - #endregion -} \ No newline at end of file diff --git a/Repositories/UsersRepository.cs b/Repositories/UsersRepository.cs new file mode 100644 index 0000000..fa10199 --- /dev/null +++ b/Repositories/UsersRepository.cs @@ -0,0 +1,112 @@ +using System; + +using MongoDB.Bson; +using MongoDB.Driver; + +using TextSender_API.Models; + +namespace TextSender_API.Repositories; + + +public class UsersRepository +{ + #region Fields + private string _connectionString; + private string _databaseName; + private string _tableName; + private readonly IMongoCollection _bookCollection; + #endregion + + + #region Constructors + public UsersRepository(string connectionString) + { + this._connectionString = connectionString; + this._databaseName = "TextSender"; + this._tableName = "users"; + var client = this.InitializeClient(); + this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); + } + #endregion + + #region Metods + public void CreateUser(User user) + { + this._bookCollection.InsertOne(user); + } + + public List RetrieveAllUsers() + { + return this._bookCollection.Find(_ => true).ToList(); + } + + public void Update(User user) + { + var filter = Builders.Filter.Eq(u => u.Id, user.Id); + this._bookCollection.ReplaceOne(filter, user); + } + + public void Delete(User user) + { + var filter = Builders.Filter.Eq(u => u.Id, user.Id); + this._bookCollection.DeleteOne(filter); + } + + private MongoClient InitializeClient() + { + return new MongoClient(this._connectionString); + } + #endregion +} + + +public class SaltRepository +{ + #region Fields + private string _connectionString; + private string _databaseName; + private string _tableName; + private readonly IMongoCollection _bookCollection; + #endregion + + + #region Constructors + public SaltRepository(string connectionString) + { + this._connectionString = connectionString; + this._databaseName = "TextSender"; + this._tableName = "salt"; + var client = this.InitializeClient(); + this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); + } + #endregion + + #region Metods + public void Create(Salt salt) + { + this._bookCollection.InsertOne(salt); + } + + public List RetrieveAll() + { + return this._bookCollection.Find(_ => true).ToList(); + } + + public void Update(Salt salt) + { + var filter = Builders.Filter.Eq(u => u.Id, salt.Id); + this._bookCollection.ReplaceOne(filter, salt); + } + + public void Delete(Salt salt) + { + var filter = Builders.Filter.Eq(u => u.Id, salt.Id); + this._bookCollection.DeleteOne(filter); + } + + private MongoClient InitializeClient() + { + return new MongoClient(this._connectionString); + } + #endregion +} \ No newline at end of file From f6afac5fd8bc473746d198ba8b66374ee96060c3 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Thu, 28 Sep 2023 06:58:42 -0400 Subject: [PATCH 13/34] Added login endpoint Started adding endpoint to login users --- Controllers/PasswordVerification.cs | 26 +++++++++++++++++++++++++- Controllers/UsersController.cs | 28 ++++++++++++++++++++++++++++ Repositories/UsersRepository.cs | 19 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Controllers/PasswordVerification.cs b/Controllers/PasswordVerification.cs index 1bee7b0..4016532 100644 --- a/Controllers/PasswordVerification.cs +++ b/Controllers/PasswordVerification.cs @@ -3,20 +3,39 @@ 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() + { + } + + 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(); @@ -25,10 +44,15 @@ public class PasswordVerification return salt; } - public string HashPassword(User user, Salt salt) + public string HashPassword(User user, Salt salt, string password = "") { var hashAlgorithm = HashAlgorithmName.SHA512; + if (!string.IsNullOrEmpty(password)) + { + user.Password = password; + } + var hash = Rfc2898DeriveBytes.Pbkdf2( Encoding.UTF8.GetBytes(user.Password!), salt.Key!, diff --git a/Controllers/UsersController.cs b/Controllers/UsersController.cs index af82d06..9c203c9 100644 --- a/Controllers/UsersController.cs +++ b/Controllers/UsersController.cs @@ -27,6 +27,34 @@ public class UsersController : ControllerBase #endregion #region Methods + [HttpPost("login")] + public IActionResult Login([FromBody] User userRequest) + { + 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!)) + { + Ok(result.Item2); + } + else + { + return NotFound(); + } + } + catch (Exception ex) + { + this._logger.LogError($"An error occurred: {ex.Message}"); + } + + return Ok(); + } + [HttpPost("register"), DisableRequestSizeLimit] public IActionResult Upload([FromBody] User userRequest) { diff --git a/Repositories/UsersRepository.cs b/Repositories/UsersRepository.cs index fa10199..5088769 100644 --- a/Repositories/UsersRepository.cs +++ b/Repositories/UsersRepository.cs @@ -30,6 +30,20 @@ public class UsersRepository #endregion #region Metods + public Tuple Exists(User user) + { + var allUsers = this.RetrieveAllUsers(); + var result = allUsers.FirstOrDefault(ea => ea.Username!.Equals(user.Username)); + + if (result != null) + { + return new Tuple(true, result!); + } + else + { + return new Tuple(false, new User()); + } + } public void CreateUser(User user) { this._bookCollection.InsertOne(user); @@ -92,6 +106,11 @@ public class SaltRepository 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.Filter.Eq(u => u.Id, salt.Id); From bc31f033fcb7024fef8b8873aea8d50cbba7f9f9 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 06:34:19 -0400 Subject: [PATCH 14/34] Changed database name --- Repositories/BaseRepository.cs | 11 +++++++++++ Repositories/UsersRepository.cs | 12 ++---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 Repositories/BaseRepository.cs diff --git a/Repositories/BaseRepository.cs b/Repositories/BaseRepository.cs new file mode 100644 index 0000000..4d721fb --- /dev/null +++ b/Repositories/BaseRepository.cs @@ -0,0 +1,11 @@ + +using TextSender_API.Repositories; + +public class BaseRepository +{ + #region Fields + protected string _connectionString; + protected string _databaseName = "textSender"; + protected string _tableName; + #endregion +} \ No newline at end of file diff --git a/Repositories/UsersRepository.cs b/Repositories/UsersRepository.cs index 5088769..e07720d 100644 --- a/Repositories/UsersRepository.cs +++ b/Repositories/UsersRepository.cs @@ -8,12 +8,9 @@ using TextSender_API.Models; namespace TextSender_API.Repositories; -public class UsersRepository +public class UsersRepository : BaseRepository { #region Fields - private string _connectionString; - private string _databaseName; - private string _tableName; private readonly IMongoCollection _bookCollection; #endregion @@ -22,7 +19,6 @@ public class UsersRepository public UsersRepository(string connectionString) { this._connectionString = connectionString; - this._databaseName = "TextSender"; this._tableName = "users"; var client = this.InitializeClient(); this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); @@ -74,12 +70,9 @@ public class UsersRepository } -public class SaltRepository +public class SaltRepository : BaseRepository { #region Fields - private string _connectionString; - private string _databaseName; - private string _tableName; private readonly IMongoCollection _bookCollection; #endregion @@ -88,7 +81,6 @@ public class SaltRepository public SaltRepository(string connectionString) { this._connectionString = connectionString; - this._databaseName = "TextSender"; this._tableName = "salt"; var client = this.InitializeClient(); this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); From 145eea8e924a81dccf6354f90dc8233726d4b9f1 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 06:44:11 -0400 Subject: [PATCH 15/34] User authentication is functional --- Controllers/PasswordVerification.cs | 3 +++ Repositories/BaseRepository.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Controllers/PasswordVerification.cs b/Controllers/PasswordVerification.cs index 4016532..9a09812 100644 --- a/Controllers/PasswordVerification.cs +++ b/Controllers/PasswordVerification.cs @@ -47,6 +47,7 @@ public class PasswordVerification public string HashPassword(User user, Salt salt, string password = "") { var hashAlgorithm = HashAlgorithmName.SHA512; + var dbHashedPassword = user.Password; if (!string.IsNullOrEmpty(password)) { @@ -60,6 +61,8 @@ public class PasswordVerification hashAlgorithm, this._keySize); + user.Password = dbHashedPassword; + return Convert.ToHexString(hash); } #endregion diff --git a/Repositories/BaseRepository.cs b/Repositories/BaseRepository.cs index 4d721fb..1c69eb8 100644 --- a/Repositories/BaseRepository.cs +++ b/Repositories/BaseRepository.cs @@ -4,8 +4,8 @@ using TextSender_API.Repositories; public class BaseRepository { #region Fields - protected string _connectionString; + protected string? _connectionString; protected string _databaseName = "textSender"; - protected string _tableName; + protected string? _tableName; #endregion } \ No newline at end of file From 192208fe2a3ae022216c299a19674bc445677a73 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 19:08:34 -0400 Subject: [PATCH 16/34] Addressing build warnings --- Models/Contact.cs | 6 +++--- Models/Text.cs | 4 ++-- Models/TextQueue.cs | 4 ++-- Repositories/BaseRepository.cs | 2 +- Repositories/UsersRepository.cs | 5 +---- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Models/Contact.cs b/Models/Contact.cs index 58e3446..3f8cfdd 100644 --- a/Models/Contact.cs +++ b/Models/Contact.cs @@ -6,9 +6,9 @@ public class Contact { #region Properties public int ContactID { get; set; } - public string Firstname { get; set; } - public string Lastname { get; set; } - public string PhoneNumber { get; set; } + public string? Firstname { get; set; } + public string? Lastname { get; set; } + public string? PhoneNumber { get; set; } public DateTime DateCreated { get; set; } public int UserID { get; set; } #endregion diff --git a/Models/Text.cs b/Models/Text.cs index 35c0d9e..8a5e8b0 100644 --- a/Models/Text.cs +++ b/Models/Text.cs @@ -6,8 +6,8 @@ public class Text { #region Properties public int TextID { get; set; } - public string Subject { get; set; } - public string Message { get; set; } + public string? Subject { get; set; } + public string? Message { get; set; } public int UserID { get; set; } public DateTime DateCreated { get; set; } #endregion diff --git a/Models/TextQueue.cs b/Models/TextQueue.cs index 8b45bc8..9d7327b 100644 --- a/Models/TextQueue.cs +++ b/Models/TextQueue.cs @@ -6,8 +6,8 @@ public class TextQueue { #region Properties public int TextQueueID { get; set; } - public string Status { get; set; } - public string ContactPhoneNumber { 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; } diff --git a/Repositories/BaseRepository.cs b/Repositories/BaseRepository.cs index 1c69eb8..e437670 100644 --- a/Repositories/BaseRepository.cs +++ b/Repositories/BaseRepository.cs @@ -1,5 +1,5 @@ -using TextSender_API.Repositories; +namespace TextSender_API.Repositories; public class BaseRepository { diff --git a/Repositories/UsersRepository.cs b/Repositories/UsersRepository.cs index e07720d..9f47561 100644 --- a/Repositories/UsersRepository.cs +++ b/Repositories/UsersRepository.cs @@ -1,6 +1,3 @@ -using System; - -using MongoDB.Bson; using MongoDB.Driver; using TextSender_API.Models; @@ -100,7 +97,7 @@ public class SaltRepository : BaseRepository public Salt Retrieve(string userId) { - return this._bookCollection.Find(s => s.UserId.Equals(userId)).FirstOrDefault(); + return this._bookCollection.Find(s => s.UserId!.Equals(userId)).FirstOrDefault(); } public void Update(Salt salt) From 4c232a500db66bdaf2cbf4915aaf9b25c0f9a1ab Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 19:09:17 -0400 Subject: [PATCH 17/34] Adding structure to responses --- Controllers/PasswordVerification.cs | 11 ++---- Controllers/UsersController.cs | 56 ++++++++++++++++++++++++++--- Models/User.cs | 13 ++++--- Program.cs | 2 -- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Controllers/PasswordVerification.cs b/Controllers/PasswordVerification.cs index 9a09812..0eac000 100644 --- a/Controllers/PasswordVerification.cs +++ b/Controllers/PasswordVerification.cs @@ -1,4 +1,3 @@ -using System; using System.Security.Cryptography; using System.Text; @@ -10,17 +9,13 @@ namespace TextSender_API.Controllers; public class PasswordVerification { #region Fiends - private UsersRepository? _userRepo; - private SaltRepository? _saltRepo; + private UsersRepository _userRepo; + private SaltRepository _saltRepo; private int _keySize = 64; private int _iterations = 350000; #endregion #region Constructors - public PasswordVerification() - { - } - public PasswordVerification(UsersRepository userRepo, SaltRepository saltRepo) { this._userRepo = userRepo; @@ -31,7 +26,7 @@ public class PasswordVerification #region Methods public bool VerifyPassword(User user, string password) { - var salt = this._saltRepo.Retrieve(user.Id); + var salt = this._saltRepo.Retrieve(user.Id!); var hashedPassword = this.HashPassword(user, salt, password); return hashedPassword.Equals(user.Password); diff --git a/Controllers/UsersController.cs b/Controllers/UsersController.cs index 9c203c9..86f5119 100644 --- a/Controllers/UsersController.cs +++ b/Controllers/UsersController.cs @@ -1,6 +1,7 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; using TextSender_API.Models; using TextSender_API.Repositories; @@ -30,6 +31,8 @@ public class UsersController : ControllerBase [HttpPost("login")] public IActionResult Login([FromBody] User userRequest) { + var response = new LoginResponse(); + try { var connString = this._config.GetConnectionString("MongoDBURI"); @@ -40,11 +43,12 @@ public class UsersController : ControllerBase if (result.Item1 && pwdVerify.VerifyPassword(result.Item2, userRequest.Password!)) { - Ok(result.Item2); + response.Data.Add(result.Item2); + Ok(response); } else { - return NotFound(); + return NotFound(response); } } catch (Exception ex) @@ -52,12 +56,14 @@ public class UsersController : ControllerBase this._logger.LogError($"An error occurred: {ex.Message}"); } - return Ok(); + return Ok(response); } [HttpPost("register"), DisableRequestSizeLimit] public IActionResult Upload([FromBody] User userRequest) { + var response = new RegisterUserResponse(); + try { var connString = this._config.GetConnectionString("MongoDBURI"); @@ -68,7 +74,7 @@ public class UsersController : ControllerBase if (!result) { - var pwdVerify = new PasswordVerification(); + var pwdVerify = new PasswordVerification(userRepo, saltRepo); var salt = pwdVerify.CreateSalt(userRequest); var hashedPassword = pwdVerify.HashPassword(userRequest, salt); @@ -77,6 +83,8 @@ public class UsersController : ControllerBase userRequest.DateCreated = DateTime.Now; userRepo.CreateUser(userRequest); + response.Data.Add(userRequest); + salt.UserId = userRequest.Id; saltRepo.Create(salt); } @@ -90,7 +98,45 @@ public class UsersController : ControllerBase this._logger.LogError($"An error occurred: {ex.Message}"); } - return Ok(); + return Ok(response); + } + #endregion + + #region Responses + public class LoginResponse + { + #region Properties + [JsonProperty("data")] + public List Data { get; set; } + #endregion + + #region Constructors + public LoginResponse() + { + this.Data = new List(); + } + #endregion + + #region Methods + #endregion + } + + public class RegisterUserResponse + { + #region Properties + [JsonProperty("data")] + public List Data { get; set; } + #endregion + + #region Constructors + public RegisterUserResponse() + { + this.Data = new List(); + } + #endregion + + #region Methods + #endregion } #endregion } diff --git a/Models/User.cs b/Models/User.cs index 4dc8992..9cbf051 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -1,10 +1,6 @@ -using System; - using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Bson.Serialization.Conventions; -using MongoDB.Bson.Serialization.IdGenerators; -using MongoDB.Driver; +using Newtonsoft.Json; namespace TextSender_API.Models; @@ -13,18 +9,25 @@ 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 } diff --git a/Program.cs b/Program.cs index 065e1e6..99cc324 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,3 @@ -using Newtonsoft.Json; - var builder = WebApplication.CreateBuilder(args); // Add services to the container. From 36e3722a015fb91fa88030049d57a373a03c2e58 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 19:33:15 -0400 Subject: [PATCH 18/34] Updated config files Config files now have JWT store credentials --- appsettings.Development.json | 4 ++++ appsettings.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/appsettings.Development.json b/appsettings.Development.json index 6dff811..7899e75 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -5,6 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, + "JWT": { + "Audience": "", + "Issuer": "" + }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" } diff --git a/appsettings.json b/appsettings.json index c249556..bccbec9 100644 --- a/appsettings.json +++ b/appsettings.json @@ -6,6 +6,10 @@ } }, "AllowedHosts": "*", + "JWT": { + "Audience": "", + "Issuer": "" + }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" } From 616bd97b64b9c19746c59f3c6e024798214b9080 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 19:33:37 -0400 Subject: [PATCH 19/34] Adding token support --- Models/Token.cs | 13 +++++++++++++ Program.cs | 25 +++++++++++++++++++++++++ TextSender-API.csproj | 2 ++ 3 files changed, 40 insertions(+) create mode 100644 Models/Token.cs diff --git a/Models/Token.cs b/Models/Token.cs new file mode 100644 index 0000000..e1310f9 --- /dev/null +++ b/Models/Token.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace TextSender_API.Models; + +public class Token +{ + #region Properties + [JsonProperty("access_token")] + public string? AccessToken { get; set; } + [JsonProperty("issued")] + public DateTime? Issued { get; set; } + #endregion +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 99cc324..d22fd0b 100644 --- a/Program.cs +++ b/Program.cs @@ -1,3 +1,11 @@ +using System.Text; + +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -11,6 +19,22 @@ builder.Services.AddSwaggerGen(); var app = builder.Build(); +var connString = builder.Configuration.GetConnectionString("DefaultConnection"); + +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"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"]!)) + }; +}); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -21,6 +45,7 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/TextSender-API.csproj b/TextSender-API.csproj index 3e29007..d4d3f0b 100644 --- a/TextSender-API.csproj +++ b/TextSender-API.csproj @@ -8,11 +8,13 @@ + + From 97ea7262b798e08f61425953eecb16b8a307d8f0 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 29 Sep 2023 19:37:58 -0400 Subject: [PATCH 20/34] Changing login response to returning token --- Controllers/UsersController.cs | 12 +++++++++--- Models/Token.cs | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Controllers/UsersController.cs b/Controllers/UsersController.cs index 86f5119..01625e0 100644 --- a/Controllers/UsersController.cs +++ b/Controllers/UsersController.cs @@ -43,7 +43,13 @@ public class UsersController : ControllerBase if (result.Item1 && pwdVerify.VerifyPassword(result.Item2, userRequest.Password!)) { - response.Data.Add(result.Item2); + var token = new Token(); + // TODO: Generate access token + token.AccessToken = string.Empty; + token.Issued = DateTime.Now; + token.UserId = result.Item2.Id; + + response.Data.Add(token); Ok(response); } else @@ -107,13 +113,13 @@ public class UsersController : ControllerBase { #region Properties [JsonProperty("data")] - public List Data { get; set; } + public List Data { get; set; } #endregion #region Constructors public LoginResponse() { - this.Data = new List(); + this.Data = new List(); } #endregion diff --git a/Models/Token.cs b/Models/Token.cs index e1310f9..2e2adaf 100644 --- a/Models/Token.cs +++ b/Models/Token.cs @@ -9,5 +9,7 @@ public class Token public string? AccessToken { get; set; } [JsonProperty("issued")] public DateTime? Issued { get; set; } + [JsonProperty("id")] + public string? UserId { get; set; } #endregion } \ No newline at end of file From 4bb3933bd2841bf25ab535cf86d592acb9532f13 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 1 Oct 2023 10:28:04 -0400 Subject: [PATCH 21/34] Updated config file The config file was missing the secret field --- appsettings.Development.json | 3 ++- appsettings.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/appsettings.Development.json b/appsettings.Development.json index 7899e75..a90ea32 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -7,7 +7,8 @@ }, "JWT": { "Audience": "", - "Issuer": "" + "Issuer": "", + "Secret": "" }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" diff --git a/appsettings.json b/appsettings.json index bccbec9..a6b4942 100644 --- a/appsettings.json +++ b/appsettings.json @@ -8,7 +8,8 @@ "AllowedHosts": "*", "JWT": { "Audience": "", - "Issuer": "" + "Issuer": "", + "Secret": "" }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" From 78614e5629c21a2f32ffd9e943173d1f80d24675 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 1 Oct 2023 11:06:10 -0400 Subject: [PATCH 22/34] Updated config file Added subject field --- appsettings.Development.json | 3 ++- appsettings.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/appsettings.Development.json b/appsettings.Development.json index a90ea32..72fac3d 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -8,7 +8,8 @@ "JWT": { "Audience": "", "Issuer": "", - "Secret": "" + "Secret": "", + "Subject": "" }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" diff --git a/appsettings.json b/appsettings.json index a6b4942..153bffa 100644 --- a/appsettings.json +++ b/appsettings.json @@ -9,7 +9,8 @@ "JWT": { "Audience": "", "Issuer": "", - "Secret": "" + "Secret": "", + "Subject": "" }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" From b9d6318d0e98b24de65b4c0bf730e5dc0ad21c9a Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Sun, 1 Oct 2023 12:10:13 -0400 Subject: [PATCH 23/34] Added authorization Endpoint to create contacts with the queueing system is now functional --- Controllers/ContactQueueCreationController.cs | 2 + Controllers/UsersController.cs | 86 ++++++++++++++++++- Models/Token.cs | 2 + Program.cs | 19 ++-- 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs index 0cfe19a..a6af7ad 100644 --- a/Controllers/ContactQueueCreationController.cs +++ b/Controllers/ContactQueueCreationController.cs @@ -1,7 +1,9 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace TextSender_API.Controllers; +[Authorize] [ApiController] [Route("api/v1/contact/queue")] public class ContactQueueCreationController : ControllerBase diff --git a/Controllers/UsersController.cs b/Controllers/UsersController.cs index 01625e0..c90b7aa 100644 --- a/Controllers/UsersController.cs +++ b/Controllers/UsersController.cs @@ -1,6 +1,13 @@ +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; @@ -28,6 +35,53 @@ public class UsersController : ControllerBase #endregion #region Methods + private string AllScopes() + { + var allScopes = new List() + { + "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 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() + { + 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) { @@ -43,11 +97,37 @@ public class UsersController : ControllerBase if (result.Item1 && pwdVerify.VerifyPassword(result.Item2, userRequest.Password!)) { + var user = result.Item2; var token = new Token(); - // TODO: Generate access token - token.AccessToken = string.Empty; token.Issued = DateTime.Now; - token.UserId = result.Item2.Id; + 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); diff --git a/Models/Token.cs b/Models/Token.cs index 2e2adaf..afbe858 100644 --- a/Models/Token.cs +++ b/Models/Token.cs @@ -7,6 +7,8 @@ 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")] diff --git a/Program.cs b/Program.cs index d22fd0b..dcd5257 100644 --- a/Program.cs +++ b/Program.cs @@ -1,10 +1,10 @@ using System.Text; -using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); @@ -17,10 +17,6 @@ builder.Services.AddControllers().AddNewtonsoftJson(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -var app = builder.Build(); - -var connString = builder.Configuration.GetConnectionString("DefaultConnection"); - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => { options.RequireHttpsMetadata = false; @@ -31,10 +27,16 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJw 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 app = builder.Build(); + +var connString = builder.Configuration.GetConnectionString("DefaultConnection"); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -46,8 +48,13 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); app.UseAuthentication(); +app.UseRouting(); app.UseAuthorization(); +app.UseEndpoints(endpoints => +{ + endpoints.MapControllers(); +}); -app.MapControllers(); +// app.MapControllers(); app.Run(); From 48e52aed3dd5352de34fbc3840228cec9db94147 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 2 Oct 2023 06:34:43 -0400 Subject: [PATCH 24/34] Removing files --- Controllers/WeatherForecastController.cs | 32 ------------------------ WeatherForecast.cs | 12 --------- 2 files changed, 44 deletions(-) delete mode 100644 Controllers/WeatherForecastController.cs delete mode 100644 WeatherForecast.cs diff --git a/Controllers/WeatherForecastController.cs b/Controllers/WeatherForecastController.cs deleted file mode 100644 index 4cc5e9e..0000000 --- a/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace TextSender_API.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} diff --git a/WeatherForecast.cs b/WeatherForecast.cs deleted file mode 100644 index 7df7d6d..0000000 --- a/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace TextSender_API; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} From 13cb3949fc2bf6fd45251fa6264ce57c5b93f306 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 2 Oct 2023 06:37:47 -0400 Subject: [PATCH 25/34] Updated config file --- appsettings.Development.json | 3 +++ appsettings.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/appsettings.Development.json b/appsettings.Development.json index 72fac3d..e0a901f 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -11,6 +11,9 @@ "Secret": "", "Subject": "" }, + "Paths": { + "ContactQueueDirectory": "/home/user/path/" + }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" } diff --git a/appsettings.json b/appsettings.json index 153bffa..8a3238e 100644 --- a/appsettings.json +++ b/appsettings.json @@ -12,6 +12,9 @@ "Secret": "", "Subject": "" }, + "Paths": { + "ContactQueueDirectory": "/home/user/path/" + }, "ConnectionStrings": { "MongoDBURI": "mongodb+srv://:@cluster0.abc.mongodb.net/?retryWrites=true&w=majority" } From 794368883dd3e5aa629a6436a14530e986fcfbe1 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 2 Oct 2023 06:49:02 -0400 Subject: [PATCH 26/34] Added model and started working on endpoint Working on endpoint to create contacts using a file --- Controllers/ContactQueueCreationController.cs | 2 ++ Models/Contact.cs | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs index a6af7ad..491b24e 100644 --- a/Controllers/ContactQueueCreationController.cs +++ b/Controllers/ContactQueueCreationController.cs @@ -26,6 +26,8 @@ public class ContactQueueCreationController : ControllerBase public IActionResult Upload([FromForm(Name = "file")] List contactFile, [FromForm(Name = "user_id")] string userID) { + // TODO: Save the file on the filesystem to be processed by a separate + // system return Ok(); } #endregion diff --git a/Models/Contact.cs b/Models/Contact.cs index 3f8cfdd..23a9470 100644 --- a/Models/Contact.cs +++ b/Models/Contact.cs @@ -1,5 +1,8 @@ using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + namespace TextSender_API.Models; public class Contact @@ -13,3 +16,23 @@ public class Contact public int 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 +} \ No newline at end of file From 2a9abbb760b4e404c4690022a247726cff6c56ee Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 3 Oct 2023 06:55:23 -0400 Subject: [PATCH 27/34] Adding functionality to queueing contact creation --- Controllers/ContactManagement.cs | 52 +++++++++++++++++++ Controllers/ContactQueueCreationController.cs | 18 ++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 Controllers/ContactManagement.cs diff --git a/Controllers/ContactManagement.cs b/Controllers/ContactManagement.cs new file mode 100644 index 0000000..eed03b2 --- /dev/null +++ b/Controllers/ContactManagement.cs @@ -0,0 +1,52 @@ +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 + // TODO: Need to implement + // Queues the contact file to be processed latter + public void QueueContact(IFormFile file) + { + var directory = this._config["Paths:ContactQueueDirectory"]; + + if (!System.IO.File.Exists(directory)) + { + // Directory does not exist + return; + } + + 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 = $"{filename}{extension}"; + + if (System.IO.File.Exists(contactPath)) + { + // Already exists + return; + } + + using (var filestream = new FileStream(contactPath, FileMode.Create)) + { + file.CopyTo(filestream); + } + } + #endregion +} \ No newline at end of file diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs index 491b24e..11d6a36 100644 --- a/Controllers/ContactQueueCreationController.cs +++ b/Controllers/ContactQueueCreationController.cs @@ -9,15 +9,17 @@ namespace TextSender_API.Controllers; public class ContactQueueCreationController : ControllerBase { #region Fields + private IConfiguration _config; private readonly ILogger _logger; #endregion #region Constructors - public ContactQueueCreationController(ILogger logger) + public ContactQueueCreationController(ILogger logger, IConfiguration config) { this._logger = logger; + this._config = config; } #endregion @@ -28,6 +30,20 @@ public class ContactQueueCreationController : ControllerBase { // TODO: Save the file on the filesystem to be processed by a separate // system + + try + { + var ctMgr = new ContactManagement(this._config); + + foreach (var file in contactFile) + { + ctMgr.QueueContact(file); + } + } + catch (Exception ex) + { + } + return Ok(); } #endregion From 4accff050c69499af5fcc4717c09362e2c556e34 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Wed, 4 Oct 2023 06:59:12 -0400 Subject: [PATCH 28/34] Added functionality to create contact queue --- Controllers/ContactManagement.cs | 26 +++++-- Controllers/ContactQueueCreationController.cs | 42 +++++++++++- Repositories/ContactRepository.cs | 68 +++++++++++++++++++ Repositories/UsersRepository.cs | 16 +++++ 4 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 Repositories/ContactRepository.cs diff --git a/Controllers/ContactManagement.cs b/Controllers/ContactManagement.cs index eed03b2..59d1464 100644 --- a/Controllers/ContactManagement.cs +++ b/Controllers/ContactManagement.cs @@ -1,3 +1,5 @@ +using TextSender_API.Models; + namespace TextSender_API.Controllers; public class ContactManagement @@ -18,14 +20,22 @@ public class ContactManagement #region Methods // TODO: Need to implement // Queues the contact file to be processed latter - public void QueueContact(IFormFile file) + public ContactCreationQueue QueueContact(IFormFile file) { + var queue = new ContactCreationQueue(); + queue.Processed = false; var directory = this._config["Paths:ContactQueueDirectory"]; - if (!System.IO.File.Exists(directory)) + if (directory!.ElementAt(directory!.Length - 1) != '/') { + directory.Append('/'); + } + + if (!System.IO.Directory.Exists(directory)) + { + queue.Status = "Missing directory"; // Directory does not exist - return; + return queue; } var length = 16; @@ -35,18 +45,24 @@ public class ContactManagement s[random.Next(s.Length)]).ToArray()); var extension = ".txt"; - var contactPath = $"{filename}{extension}"; + var contactPath = $"{directory}/{filename}{extension}"; + queue.Filepath = contactPath; if (System.IO.File.Exists(contactPath)) { // Already exists - return; + queue.Status = "File exists"; + return queue; } + queue.Status = "Created"; + using (var filestream = new FileStream(contactPath, FileMode.Create)) { file.CopyTo(filestream); } + + return queue; } #endregion } \ No newline at end of file diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs index 11d6a36..1711a93 100644 --- a/Controllers/ContactQueueCreationController.cs +++ b/Controllers/ContactQueueCreationController.cs @@ -1,5 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +using TextSender_API.Models; +using TextSender_API.Repositories; namespace TextSender_API.Controllers; @@ -30,21 +34,55 @@ public class ContactQueueCreationController : ControllerBase { // TODO: 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 ContactCreationQueueepository(connString!); try { var ctMgr = new ContactManagement(this._config); + var user = userRepo.Exists(userID); + + if (!user.Item1) + { + return NotFound(response); + } foreach (var file in contactFile) { - ctMgr.QueueContact(file); + 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(); + return Ok(response); + } + #endregion + + + #region Responses + public class CreateQueueResponse + { + #region Properties + [JsonProperty("data")] + public List Data { get; set; } + #endregion + + #region Constructors + public CreateQueueResponse() + { + this.Data = new List(); + } + #endregion } #endregion } \ No newline at end of file diff --git a/Repositories/ContactRepository.cs b/Repositories/ContactRepository.cs new file mode 100644 index 0000000..9d68112 --- /dev/null +++ b/Repositories/ContactRepository.cs @@ -0,0 +1,68 @@ +using MongoDB.Driver; + +using TextSender_API.Models; + +namespace TextSender_API.Repositories; + + +public class ContactCreationQueueepository : BaseRepository +{ + #region Fields + private readonly IMongoCollection _bookCollection; + #endregion + + + #region Constructors + public ContactCreationQueueepository(string connectionString) + { + this._connectionString = connectionString; + this._tableName = "contactCreationQueue"; + var client = this.InitializeClient(); + this._bookCollection = client.GetDatabase(this._databaseName).GetCollection(this._tableName); + } + #endregion + + #region Metods + public Tuple Exists(string id) + { + var allQueue = this.RetrieveAll(); + var result = allQueue.FirstOrDefault(q => q.Id!.Equals(id)); + + if (result != null) + { + return new Tuple(true, result!); + } + else + { + return new Tuple(false, new ContactCreationQueue()); + } + } + + public void Create(ContactCreationQueue queue) + { + this._bookCollection.InsertOne(queue); + } + + public List RetrieveAll() + { + return this._bookCollection.Find(_ => true).ToList(); + } + + public void Update(ContactCreationQueue queue) + { + var filter = Builders.Filter.Eq(q => q.Id, queue.Id); + this._bookCollection.ReplaceOne(filter, queue); + } + + public void Delete(ContactCreationQueue queue) + { + var filter = Builders.Filter.Eq(q => q.Id, queue.Id); + this._bookCollection.DeleteOne(filter); + } + + private MongoClient InitializeClient() + { + return new MongoClient(this._connectionString); + } + #endregion +} diff --git a/Repositories/UsersRepository.cs b/Repositories/UsersRepository.cs index 9f47561..c9a86a1 100644 --- a/Repositories/UsersRepository.cs +++ b/Repositories/UsersRepository.cs @@ -37,6 +37,22 @@ public class UsersRepository : BaseRepository return new Tuple(false, new User()); } } + + public Tuple Exists(string userId) + { + var allUsers = this.RetrieveAllUsers(); + var result = allUsers.FirstOrDefault(ea => ea.Id!.Equals(userId)); + + if (result != null) + { + return new Tuple(true, result!); + } + else + { + return new Tuple(false, new User()); + } + } + public void CreateUser(User user) { this._bookCollection.InsertOne(user); From 421488c1205bcec0f9bf85f5d6f7a68a4bcaf8f1 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 6 Oct 2023 22:18:23 -0400 Subject: [PATCH 29/34] Refactoring and adding new endpoint --- Controllers/ContactManagement.cs | 1 - Controllers/ContactQueueCreationController.cs | 6 +- Controllers/ContactsController.cs | 86 +++++++++++++++++++ Models/Contact.cs | 12 ++- Program.cs | 7 +- Repositories/ContactRepository.cs | 67 ++++++++++++++- 6 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 Controllers/ContactsController.cs diff --git a/Controllers/ContactManagement.cs b/Controllers/ContactManagement.cs index 59d1464..36f0630 100644 --- a/Controllers/ContactManagement.cs +++ b/Controllers/ContactManagement.cs @@ -18,7 +18,6 @@ public class ContactManagement #region Methods - // TODO: Need to implement // Queues the contact file to be processed latter public ContactCreationQueue QueueContact(IFormFile file) { diff --git a/Controllers/ContactQueueCreationController.cs b/Controllers/ContactQueueCreationController.cs index 1711a93..4f9ae4a 100644 --- a/Controllers/ContactQueueCreationController.cs +++ b/Controllers/ContactQueueCreationController.cs @@ -32,12 +32,12 @@ public class ContactQueueCreationController : ControllerBase public IActionResult Upload([FromForm(Name = "file")] List contactFile, [FromForm(Name = "user_id")] string userID) { - // TODO: Save the file on the filesystem to be processed by a separate + // 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 ContactCreationQueueepository(connString!); + var userRepo = new UsersRepository(connString!); + var queueRepo = new ContactCreationQueueRepository(connString!); try { diff --git a/Controllers/ContactsController.cs b/Controllers/ContactsController.cs new file mode 100644 index 0000000..ccb3e00 --- /dev/null +++ b/Controllers/ContactsController.cs @@ -0,0 +1,86 @@ +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 _logger; + #endregion + + + #region Constructors + + public ContactsController(ILogger 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(); + + if (contacts.Exists(c => c.PhoneNumber!.Equals(contact.PhoneNumber)) || + string.IsNullOrEmpty(contact.UserID)) + { + // The Contact already exists + return BadRequest(response); + } + + var user = new User { Id = contact.UserID }; + + if (!userRepo.Exists(user).Item1) + { + return BadRequest(response); + } + + 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 Data { get; set; } + #endregion + + #region Constructors + public CreateContactResponse() + { + this.Data = new List(); + } + #endregion + } + #endregion +} diff --git a/Models/Contact.cs b/Models/Contact.cs index 23a9470..a658a26 100644 --- a/Models/Contact.cs +++ b/Models/Contact.cs @@ -8,12 +8,20 @@ namespace TextSender_API.Models; public class Contact { #region Properties - public int ContactID { get; set; } + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } + [BsonElement("firstname")] public string? Firstname { get; set; } + [BsonElement("lastname")] public string? Lastname { get; set; } + [BsonElement("phonenumber")] public string? PhoneNumber { get; set; } + [BsonElement("date_created")] public DateTime DateCreated { get; set; } - public int UserID { get; set; } + // [BsonRepresentation(BsonType.ObjectId)] + [BsonElement("user_id")] + public string? UserID { get; set; } #endregion } diff --git a/Program.cs b/Program.cs index dcd5257..66aca82 100644 --- a/Program.cs +++ b/Program.cs @@ -6,6 +6,8 @@ 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. @@ -33,9 +35,10 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJw }; }); -var app = builder.Build(); +// var connectionString = builder.Configuration.GetConnectionString("MongoDBURI"); -var connString = builder.Configuration.GetConnectionString("DefaultConnection"); + +var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) diff --git a/Repositories/ContactRepository.cs b/Repositories/ContactRepository.cs index 9d68112..b4d9ceb 100644 --- a/Repositories/ContactRepository.cs +++ b/Repositories/ContactRepository.cs @@ -5,7 +5,7 @@ using TextSender_API.Models; namespace TextSender_API.Repositories; -public class ContactCreationQueueepository : BaseRepository +public class ContactCreationQueueRepository : BaseRepository { #region Fields private readonly IMongoCollection _bookCollection; @@ -13,7 +13,7 @@ public class ContactCreationQueueepository : BaseRepository #region Constructors - public ContactCreationQueueepository(string connectionString) + public ContactCreationQueueRepository(string connectionString) { this._connectionString = connectionString; this._tableName = "contactCreationQueue"; @@ -66,3 +66,66 @@ public class ContactCreationQueueepository : BaseRepository } #endregion } + + +public class ContactsRepository : BaseRepository +{ + #region Fields + private readonly IMongoCollection _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(this._tableName); + } + #endregion + + #region Metods + public Tuple Exists(string id) + { + var allContacts= this.RetrieveAll(); + var result = allContacts.FirstOrDefault(c => c.Id!.Equals(id)); + + if (result != null) + { + return new Tuple(true, result!); + } + else + { + return new Tuple(false, new Contact()); + } + } + + public void Create(Contact contact) + { + this._bookCollection.InsertOne(contact); + } + + public List RetrieveAll() + { + return this._bookCollection.Find(_ => true).ToList(); + } + + public void Update(Contact contact) + { + var filter = Builders.Filter.Eq(c => c.Id, contact.Id); + this._bookCollection.ReplaceOne(filter, contact); + } + + public void Delete(Contact contact) + { + var filter = Builders.Filter.Eq(c => c.Id, contact.Id); + this._bookCollection.DeleteOne(filter); + } + + private MongoClient InitializeClient() + { + return new MongoClient(this._connectionString); + } + #endregion +} \ No newline at end of file From 6b065ed2045b063eba96763db32030fa2abec5ee Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 16 Oct 2023 21:01:22 -0400 Subject: [PATCH 30/34] Updated gitignore file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1746e32..399c344 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.vscode +*.json bin obj From 47d9f75619264571211e231b79bf1d72bed3f2bb Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 16 Oct 2023 21:01:45 -0400 Subject: [PATCH 31/34] Added solution file --- textsender-api.sln | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 textsender-api.sln diff --git a/textsender-api.sln b/textsender-api.sln new file mode 100644 index 0000000..b57d8ea --- /dev/null +++ b/textsender-api.sln @@ -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 From 9bf7709b9b3d5b4154e159838445e0128943b739 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 16 Oct 2023 21:02:04 -0400 Subject: [PATCH 32/34] Added endpoint to create new Contact --- Controllers/ContactsController.cs | 28 ++++++++++++++++++---------- Models/Contact.cs | 7 +++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Controllers/ContactsController.cs b/Controllers/ContactsController.cs index ccb3e00..9d492d8 100644 --- a/Controllers/ContactsController.cs +++ b/Controllers/ContactsController.cs @@ -39,21 +39,29 @@ public class ContactsController : ControllerBase try { var contacts = contactsRepo.RetrieveAll(); + var users = userRepo.RetrieveAllUsers(); - if (contacts.Exists(c => c.PhoneNumber!.Equals(contact.PhoneNumber)) || - string.IsNullOrEmpty(contact.UserID)) - { - // The Contact already exists - return BadRequest(response); - } - - var user = new User { Id = contact.UserID }; - - if (!userRepo.Exists(user).Item1) + 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); } diff --git a/Models/Contact.cs b/Models/Contact.cs index a658a26..72da9a0 100644 --- a/Models/Contact.cs +++ b/Models/Contact.cs @@ -2,6 +2,7 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; +using Newtonsoft.Json; namespace TextSender_API.Models; @@ -10,17 +11,23 @@ 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 } From 2407aa7c467bd2787f9668624b3c767b09cdc37f Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 16 Oct 2023 21:03:24 -0400 Subject: [PATCH 33/34] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 399c344..16ac8e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode +appsettings.Development.json *.json bin obj From e2c3aaab952e0e7a9d7ef4e0607cd236f31de969 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Mon, 16 Oct 2023 21:04:44 -0400 Subject: [PATCH 34/34] Updated readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33e7395..b8543de 100644 --- a/README.md +++ b/README.md @@ -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 +```