dentallapp / back-end Goto Github PK
View Code? Open in Web Editor NEWA web application that manages appointment scheduling, reminders and cancellation of appointments
License: GNU Affero General Public License v3.0
A web application that manages appointment scheduling, reminders and cancellation of appointments
License: GNU Affero General Public License v3.0
I have deployed this project on a test virtual server and in addition, I created the Azure Bot Service resource to register the bot and attach the DirectLine channel in the resource, however, when interacting with the bot from the WebChat (a component of Bot Framework-WebChat) the following error occurs on the server side:
Jan 12 13:50:15 DentallApp bash[1582]: info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Jan 12 13:50:15 DentallApp bash[1582]: token: '{"alg":"RS256","kid":"d25T3rS8ZCu8VUIqDV3fV14llFI","x5t":"d25T3rS8ZCu8VUIqDV3fV14llFI","typ":"JWT","cty":"JWT"}.{"serviceurl":"https://directline.botframework.com/","nbf":1673549415,"exp":1673550015,"iss":"https://api.botframework.com","aud":"023e5038-8c60-45ee-87a2-852aa984480c"}'.
Jan 12 13:50:15 DentallApp bash[1582]: '.
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters, BaseConfiguration configuration)
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm, Boolean cacheProvider)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
Jan 12 13:50:15 DentallApp bash[1582]: is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
Jan 12 13:50:15 DentallApp bash[1582]: Algorithm: 'RS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'd1JZdyHpnBVySxTBXp85dMx1k7Hb2R4DAYUctaaQCtE'.'
Jan 12 13:50:15 DentallApp bash[1582]: 'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Jan 12 13:50:15 DentallApp bash[1582]: Exceptions caught:
Jan 12 13:50:15 DentallApp bash[1582]: Number of keys in Configuration: '1'.
Jan 12 13:50:15 DentallApp bash[1582]: Number of keys in TokenValidationParameters: '0'.
Jan 12 13:50:15 DentallApp bash[1582]: kid: 'd25T3rS8ZCu8VUIqDV3fV14llFI'.
Jan 12 13:50:15 DentallApp bash[1582]: Bearer was not authenticated. Failure message: IDX10501: Signature validation failed. Unable to match key:
Jan 12 13:50:15 DentallApp bash[1582]: info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7]
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, JwtSecurityToken outerToken, TokenValidationParameters validationParameters, SecurityToken& signatureValidatedToken)
Jan 12 13:50:15 DentallApp bash[1582]: --- End of stack trace from previous location ---
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters, BaseConfiguration configuration)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(SecurityToken securityToken, Nullable`1 notBefore, Nullable`1 expires, String kid, TokenValidationParameters validationParameters, BaseConfiguration configuration, StringBuilder exceptionStrings, Int32 numKeysInConfiguration, Int32 numKeysInTokenValidationParameters)
Jan 12 13:50:15 DentallApp bash[1582]: token: '{"alg":"RS256","kid":"d25T3rS8ZCu8VUIqDV3fV14llFI","x5t":"d25T3rS8ZCu8VUIqDV3fV14llFI","typ":"JWT","cty":"JWT"}.{"serviceurl":"https://directline.botframework.com/","nbf":1673549415,"exp":1673550015,"iss":"https://api.botframework.com","aud":"023e5038-8c60-45ee-87a2-852aa984480c"}'.
Jan 12 13:50:15 DentallApp bash[1582]: '.
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters, BaseConfiguration configuration)
Jan 12 13:50:15 DentallApp bash[1582]: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm, Boolean cacheProvider)
Jan 12 13:50:15 DentallApp bash[1582]: at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
Jan 12 13:50:15 DentallApp bash[1582]: is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
Jan 12 13:50:15 DentallApp bash[1582]: Algorithm: 'RS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'd1JZdyHpnBVySxTBXp85dMx1k7Hb2R4DAYUctaaQCtE'.'
Jan 12 13:50:15 DentallApp bash[1582]: 'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Jan 12 13:50:15 DentallApp bash[1582]: Exceptions caught:
Jan 12 13:50:15 DentallApp bash[1582]: Number of keys in Configuration: '1'.
Jan 12 13:50:15 DentallApp bash[1582]: Number of keys in TokenValidationParameters: '0'.
Jan 12 13:50:15 DentallApp bash[1582]: kid: 'd25T3rS8ZCu8VUIqDV3fV14llFI'.
Jan 12 13:50:15 DentallApp bash[1582]: Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match key:
Jan 12 13:50:15 DentallApp bash[1582]: Failed to validate the token.
This error occurs every time a message is sent to the bot, although the bot still works without any problem.
These are the packages to be upgraded to v4.18.1:
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Version="4.18.1" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.18.1" />
Now with this change, the project can change from target framework to .net 7.0
, previously it was not possible because bot framework worked with .netcoreapp 3.1
(see microsoft/botframework-sdk#6551).
The following method gets the list of the basic user's favorite dentists and also includes the dentists that are not preferred by the userId
:
back-end/src/Features/FavoriteDentists/FavoriteDentistRepository.cs
Lines 23 to 61 in bd530a6
But the curious line of code is the following:
The above line EF Core translates it to:
`f`.`id` IS NOT NULL AS `IsFavorite`
f
is a simple alias of the favorite_dentists table.
The strangest thing is that if the code is changed to:
IsFavorite = favoriteDentist.Id != null
And the result is the same as in the previous case:
`f`.`id` IS NOT NULL AS `IsFavorite`
In both cases the same sql code is generated and returns the expected result, only that in this expression a warning is thrown that an int
cannot be compared with null
:
// Generates a warning but the LINQ query continues to work.
favoriteDentist.Id != null
There is an issue that talks about this: dotnet/efcore#22517
PD: The final SQL code generated by EF Core is:
SELECT
`e`.`id` AS `DentistId`,
CONCAT(CONCAT(COALESCE(`p`.`names`, ''), ' '), COALESCE(`p`.`last_names`, '')) AS `FullName`,
`e`.`pregrade_university` AS `PregradeUniversity`,
`e`.`postgrade_university` AS `PostgradeUniversity`,
`e`.`office_id` AS `OfficeId`,
`t`.`name` AS `OfficeName`,
`f`.`id` IS NOT NULL AS `IsFavorite`
FROM `employees` AS `e`
INNER JOIN `persons` AS `p` ON `e`.`person_id` = `p`.`id`
INNER JOIN (
SELECT `o`.`id`, `o`.`name`
FROM `offices` AS `o`
WHERE NOT (`o`.`is_deleted`)
) AS `t` ON `e`.`office_id` = `t`.`id`
INNER JOIN `user_roles` AS `u` ON (4 = `u`.`role_id`) AND (`e`.`user_id` = `u`.`user_id`)
LEFT JOIN `favorite_dentists` AS `f` ON (@__userId_0 = `f`.`user_id`) AND (`e`.`id` = `f`.`dentist_id`)
WHERE NOT (`e`.`is_deleted`)
It is necessary to create an endpoint that is responsible for calling the DirectLine API to generate a token and this is because if it is done from the client, the secret key would be exposed, this brings as a consequence that any malicious user could access any conversation associated with the bot.
The solution is to create a service on the back-end side in which it is in charge of making a request to the next endpoint:
POST https://directline.botframework.com/v3/directline/tokens/generate
Authorization: Bearer SECRET_KEY
This can be done on the front-end side but it would be insecure.
The following codes can be simplified by using the ternary operator:
back-end/src/Features/Appointments/AppointmentController.cs
Lines 29 to 36 in f8604f2
back-end/src/Features/Appointments/AppointmentController.cs
Lines 43 to 50 in f8604f2
The superadmin should have an option to add the days that are public holidays so that the system can validate.
It is related to #118.
This code:
back-end/src/Extensions/DateTimeExtensions.cs
Lines 14 to 20 in d26a589
Can be converted to:
public static string GetDateAndHourInSpanishFormat(this DateTime? dt)
=> dt?.ToString("f", new System.Globalization.CultureInfo("es-ES"));
The compiler translates the above code to:
public static string GetDateAndHourInSpanishFormat(Nullable<DateTime> dt)
{
return dt.HasValue ? dt.GetValueOrDefault().ToString("f", new CultureInfo("es-ES")) : null;
}
Should be appointment instead of appoinment (several namespaces, classes, methods, local variables and endpoints have this spelling error).
This package is used to validate requests sent by web clients.
The background process(job) can perform this task at night at 21:00 pm (this could be changed from a .env file).
The advantage of this functionality is that the dentist no longer has to keep an eye on changing the appointment status in case the patient does not show up for the appointment.
Something is happening but unit tests are taking a long time to run. I'm not sure if it's the simulation tests that are performed between the bot and the user, as these tests are more of integration, because they depend on the file system.
For more information: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
The idea is that instances of the repository or service are reused in the same request.
The following files should be created:
Dockerfile
docker-compose.yml
: It has three services: webapi, database and InDirectLine.Must also create the InDirectLine
image and upload it in DockerHub to use it from docker-compose.yml
.
The client must perform the following steps to deploy the back-end:
docker compose up --build -d
.This error occurs when testing the bot on a test server connected to Azure Bot Service. When using Direct Line with Azure, user IDs are directly linked to a token and in addition, they start with the prefix dl_
. The problem is that the bot does not accept this type of ID, and therefore throws this error.
It is not necessary to call the LinqToDBForEFTools.Initialize()
method in a static constructor of each class. In fact, it can be called only once in the Startup.cs or Program.cs file. See linq2db/linq2db.EntityFrameworkCore#95.
Note: In this application the user name is the email.
See the following discussion: https://news.ycombinator.com/item?id=16300641.
Child layer repositories such as Kinship, Gender and Appointment Status only add unnecessary complexity, so they can be eliminated as they are very simple queries.
I want to base my work on this template: https://github.com/nadirbad/VerticalSliceArchitecture
PD: It will not be done exactly the same as the template.
This mechanism is necessary because if the reminders are scheduled to be sent on the first day of each month at 8:00 a.m. but if the server shuts down for several hours before the scheduled time, then the reminder will not be sent when the server starts because it is after 8 a.m.
See Job Stores.
Since the target-framework will be changed (see #67), can take advantage of this feature: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements#start-exploring to completely remove the Startup.cs
file
For example:
back-end/src/Features/Offices/OfficeService.cs
Lines 56 to 63 in 50ff4b1
These methods are useless, they do not provide any benefit, they only make maintenance more difficult.
In the code the generic Set<T>
method is being used, so these properties are unusable and only saturate the context instance:
back-end/src/DataAccess/AppDbContext.cs
Lines 5 to 21 in 50ff4b1
The purpose of separating these layers in your own project is to ensure that the interdependence between them is not broken.
For example, the shared layer does not need to depend on any other layer. It is a simple "mediator" that allows communication between the layers, thus avoiding coupling between them.
In case I accidentally add a class from another layer in the shared layer, the compiler will generate a compilation error that the type does not exist or there is no reference to another project. This avoids accidentally coupling one layer with another.
On Windows there are no problems but on Linux distributions when a file name has whitespace, it is appended around single quotes. This leads to invalid behavior, because in the database the image name is stored without single quotes but in the file system it is (this should not be the case).
This would cause problems, for example, when you want to edit the image of a dental service, it will not be possible to delete the old image because the image name in the database will not match the image in the file system.
File system:
'brackets_0023222.png'
database:
brackets_0023222.png
For example, this class is coupled to MySqlConnection and should actually depend on an abstraction to facilitate dependency inversion:
back-end/src/Features/Reports/ReportQuery.cs
Lines 17 to 21 in 50ff4b1
Also, in this case, the using global directive could be used to avoid having to import the types in each file:
back-end/src/Features/Reports/ReportQuery.cs
Lines 1 to 2 in 50ff4b1
The same code is repeated in these steps:
ShowNameOfOffices,
ShowNameOfServices,
ShowNameOfDentists,
ShowAppoinmentDate,
ShowSchedules
The following LINQ:
back-end/src/Features/Users/UserRepository.cs
Lines 28 to 38 in 9a93908
SELECT `u`.`id` AS `UserId`, `u`.`username` AS `UserName`, `p`.`names` AS `Name`, `u`.`password` AS `Password`
FROM `users` AS `u`
INNER JOIN `persons` AS `p` ON `u`.`person_id` = `p`.`id`
WHERE `u`.`username` = @__username_0
LIMIT 1
In this case EF Core selects the columns associated to UserResetPasswordDto
.
But when the mapper is called in the Select
method:
.Select(user => user.MapToUserResetPasswordDto())
EF Core translates it to:
SELECT `u`.`id`, `u`.`created_at`, `u`.`password`, `u`.`person_id`, `u`.`refresh_token`, `u`.`refresh_token_expiry`, `u`.`updated_at`, `u`.`username`, `p`.`id`, `p`.`cell_phone`, `p`.`created_at`, `p`.`date_birth`, `p`.`document`, `p`.`email`, `p`.`gender_id`, `p`.`last_names`, `p`.`names`, `p`.`updated_at`
FROM `users` AS `u`
INNER JOIN `persons` AS `p` ON `u`.`person_id` = `p`.`id`
WHERE `u`.`username` = @__username_0
LIMIT 1
EF Core selects ALL columns. The mapper code is:
back-end/src/Features/Users/UserMapper.cs
Lines 64 to 71 in 9a93908
There is a possible solution in this StackOverflow answer: https://stackoverflow.com/a/62138200 (must be tested)
This problem is also mentioned here: dotnet/efcore#24509
The abstraction is only used once and has only one implementation associated with it. This only adds unnecessary complexity to the project and complicates maintenance, since changes must be made to both the abstraction and the concrete class when a new method needs to be added.
This constraint is necessary to avoid duplicate entries such as a username.
See https://enterprisecraftsmanship.com/posts/handling-unique-constraint-violations/.
The CLI displays the following warnings:
warn: Microsoft.EntityFrameworkCore.Model.Validation[10622]
Entity 'Employee' has a global query filter defined and is the required end of a relationship with the entity 'Appoinment'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
warn: Microsoft.EntityFrameworkCore.Model.Validation[10622]
Entity 'Employee' has a global query filter defined and is the required end of a relationship with the entity 'EmployeeSchedule'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
warn: Microsoft.EntityFrameworkCore.Model.Validation[10622]
Entity 'Employee' has a global query filter defined and is the required end of a relationship with the entity 'FavoriteDentist'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
warn: Microsoft.EntityFrameworkCore.Model.Validation[10622]
Entity 'GeneralTreatment' has a global query filter defined and is the required end of a relationship with the entity 'SpecificTreatment'. This may lead to unexpected results when the required entity is filtered out. Either configure the navigation as optional, or define matching query filters for both entities in the navigation. See https://go.microsoft.com/fwlink/?linkid=2131316 for more information.
Could these warnings impair the application or bring unexpected results?
Currently the endpoint where the bot listens is public, so anyone could communicate with the bot. It would be nice if only authenticated users could access the bot from the website.
In addition, the same security token can be passed to the bot as the one provide by the website when the user logs in.
In that case, on the bot side, the access token would have to be validated to check if the user has authenticated.
If no check is performed, any user can schedule appointments for other users simply by knowing the user ID (this is known as user impersonation).
Currently the IDateTimeProvider
interface (which is in the Helpers module) has been added for this problem, but some modules still need to be refactored to depend on the interface to facilitate unit testing.
The advantage of this is that it complies with the principle of separation of concerns and makes the code more readable and orderly. This makes maintenance easier in the long run, because it is easier to find any error, since each class is in charge of doing one thing.
Currently, there is a background process that sends an it hourly request to the database in order to get the appointments that are close to the stipulated date and then send the appointment reminders to the patients. Instead of sending it hourly, the request could be sent in the morning at 8:00 a.m. (this could be modified from a .env file).
PD: So with the above change, the HasReminder
property of the Appointment
model would no longer be needed (this property was used to ensure that the reminder is sent only once to the patient).
When accessing an unprotected endpoint such as http://localhost:3978/api/gender
, the following message is displayed:
dbug: DentallApp.Extensions.AuthenticationJwtBearer+CustomJwtBearerHandler[9]
AuthenticationScheme: Bearer was not authenticated.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
This should not be displayed because at no point is the Authorization header included in the HTTP request, i.e. the Bearer does not actually provide any access token.
This is strange behavior, as this does not impair the operation of the application. An important detail is that this message is only displayed in development mode.
If we change ASPNETCORE_ENVIRONMENT
to Production, the message disappears.
PD: This behavior arose from PR #136.
In the Dependents
module there would be four files representing the vertical slice:
CreateDependent.cs
DeleteDependent.cs
UpdateDependent.cs
GetDependentsByUserId.cs
Each file represents a feature and in it are the related components such as the use case, mapper, validator and the DTOs (request/response).
The following keys must be set up from an .env
:
MICROSOFT_APP_TYPE=
MICROSOFT_APP_ID=
MICROSOFT_APP_PASSWORD=
MICROSOFT_APP_TENANT_ID=
The default value of each key must be an empty string.
When deploying in a development or test environment, five user accounts should be created by default: basic user, secretary, dentist, admin and superadmin. This would be useful for people who want to test the application.
Apply in:
Names
LastNames
Document
This connector is unnecessary, since you can use the provider class as MySqlConnection
:
back-end/src/DataAccess/DbConnectors/MariaDbConnector.cs
Lines 1 to 14 in 2f464a7
That abstraction layer is not needed, because IDbConnection can be registered as a service, for example:
builder.Services.AddScoped<IDbConnection>(
serviceProvider => new MySqlConnection("MY_CONNECTION_STRING"));
Another advantage of this approach is that it is no longer necessary to use the using statement to release the resource, because the DI container takes care of that responsibility (behind the scenes it invokes the Dispose method).
Sample code:
back-end/src/Features/Reports/ReportQuery.cs
Lines 8 to 12 in 678c362
This class uses both an ORM and raw sql. The problem lies when an operation is executed using raw sql together with Dapper, so the DI container injects an instance of AppDbContext
in the constructor of the ReportQuery
class, this leads to a waste of memory (because the AppDbContext is never used).
In this case, it would be useful to use the MediatR library and create a per-request handler so that each one uses an AppDbContext or raw sql with Dapper in isolation.
One important detail is that AppDbContext
is registered as scoped, so an instance is created in each HTTP request, while IDbConnector
is registered as singleton, so it is the same instance in each request.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.