Skip to content

adrianbarbe/usv-stud-docs

Repository files navigation

MIT License LinkedIn


USV-docs

USV StudDocs – facilitating interaction with your University

An awesome software tool for interacting with the dean office for getting student's certificate!
Explore the docs »

View Demo · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Project Design
  3. Implementation
  4. Deployment
  5. Getting Started
  6. Usage
  7. Roadmap
  8. Contributing
  9. License
  10. Contact
  11. Acknowledgments

About The Project

This is a piece of software which is aimed to facilitate the interaction with the dean office in USV. It uses USV Google oAuth2 authentication which makes it even easier to use.

USV StudDocs Main Auth Screen

Admin user could do the managing for the dictionaries and basic settings:

USV StudDocs Administration

USV StudDocs Administration

USV StudDocs Administration

USV StudDocs Administration

Student would use the same auth screen and inside the app will see the list of already made requests and the button for creating a new one.

USV StudDocs Student

USV StudDocs Student

Dean office secretary will receive the certificate request and will follow up it according to the procedure.

USV StudDocs Student

USV StudDocs Student

(back to top)

Built With

  • .NET
  • Vue
  • PostgreSQL

(back to top)

Project design

This project is build as a Client-API application with a client developed using the VueJs 3 front-end framework and API developed by using .NET 6 C#. We used Entity Framework as main ORM and the chosen approach was Code First. This choice is dictated by the usability and maintainability of this approach. It creates uniform namings across the DB, there's no need to write down SQL code and maintain SQL code base.

The project is a solution that consists of 5 .NEt projects that corresponds to the architectural layers.

For this project we have chosen 3-tier / Layer architecure. It comprise of 3-tiers, Presentation Layer, Business Logic Layer, and Data Access Layer. Each Layer will have its own namespace, assembly and project (classes and folders)in the solution.

We considered this architecture becuase there are some benefits of N-tier Architecture:

  • Reuse of the code
  • Improve of the Maintainability
  • Looser Coupling

In our project we have next sub-projects:

  • USVStudDocs.DAL - Data access layer, where the entity configurations, migrations and EF Context is saved.
  • USVStudDocs.Entities – a class project where the Entities are grouped. They are suffixed by "*Entity"
  • USVStudDocs.Models - a class project where the Data Transfer Objects (DTO) are located
  • USVStudDocs.BLL - Business logic layer, a class project where the services and other business logic (authentication, 3rd party API integration, validation) is stored.
  • USVStudDocs.Web – API project, presentation layer. A set of Controllers with exposed API endpoints, where the routing, authentication guards are implemented. USVStudDocs.UI – UI project, VueJs. Is what the user could see.

Implementation

Some notable libraries (NuGets) used in our application are:

  • FluentValidation – for DTO validation
  • RestSharp - for sending API requests during the validation of oAuth2 code
  • Serilog and Serilog.Sinks.Console and Serilog.Sinks.Telegram – for collecting and sending logs to console, and warnings and up – to Telegram.
  • JWt - for generating user-facing JWT

Business layer

Business layer contains logic for Authentication, Custom Exceptions, Extension methods, Helpers, Mappes (between Entities and DTOs and vice-versa) and the most important – Services.

Validators are created based on FluentValidation.

Among services, there are CRUD Services for administration, for creating the certificate requeste, the sevices for use by dean office secretary. There is also AwsMinioClient service that is used for uploading files to MinIO

Some notable places in BLL are:

  • use of IHttpContextAccessor in BLL for getting the current user.
var subValue = _httpContextAccessor.HttpContext.User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value;

            bool parseRes = int.TryParse(subValue, out var userId);

            if (!parseRes)
            {
                Log.ForContext<AuthorizationService>().Error("Cannot parse user Id from {SubValue}", subValue);
                
                throw new ValidationException("User id is not an integer");
            }

            return userId;
        }
  • JWT generation
var jwtSecretKey = Environment.GetEnvironmentVariable("JwtSecretKey") ?? _jwtSettings.SecretKey;

        var tokenBuilder = new JwtBuilder()
            .WithAlgorithm(new HMACSHA256Algorithm())
            .WithSecret(jwtSecretKey)
            .AddClaim(JwtRegisteredClaimNames.Exp,
                DateTimeOffset.UtcNow.AddMinutes(_jwtSettings.ExpirationMinutes).ToUnixTimeSeconds())
            .AddClaim(JwtRegisteredClaimNames.Iss, _jwtSettings.Issuer)
            .AddClaim(JwtRegisteredClaimNames.Aud, _jwtSettings.Audience)
            .AddClaim(JwtRegisteredClaimNames.Sub, userEntity.Id)
            .AddClaim(JwtRegisteredClaimNames.UniqueName, userEntity.Username)
            .AddClaim(JwtRegisteredClaimNames.Email, userEntity.Email)
            .AddClaim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.AddMilliseconds(1).ToUnixTimeSeconds());
        
        tokenBuilder.AddClaim("role", roles);
            
        return tokenBuilder.Encode();
  • Splitter of the Surname, Patornymic initials and name

    var studentSurnameNamePatronymic = res.Result.SurnameNamePatronymic.Trim()
                          .Replace("`", "'").Replace("'", "'")
                          .Replace("’", "'").Replace("  ", " ")
                          .Split(" ");
    
                      List<string> surname = new List<string>();
                      List<string> name = new List<string>();
                      List<string> patronymic = new List<string>();
    
                      foreach (var studentString in studentSurnameNamePatronymic)
                      {
                          if (!studentString.Contains('.') && patronymic.Count == 0)
                          {
                              surname.Add(studentString);
                          }
                          else if (studentString.Contains('.') && patronymic.Count == 0)
                          {
                              patronymic.Add(studentString);
                          }
                          else if (!studentString.Contains('.') && patronymic.Count > 0)
                          {
                              name.Add(studentString);
                          }
                      }
    • Authorizing e-mail service to use Gmail account
    
  var clientSecrets = new ClientSecrets
        {
            ClientId = clientId,
            ClientSecret = clientSecret
        };
        
        var token = new TokenResponse
        {
            AccessToken = accessToken,
            RefreshToken = refreshToken
        };

        var credential = CreateUserCredential(clientSecrets, token, oAuthEmailSenderEmail.Value);

        // Check if the access token has expired
        if (credential.Token.IsExpired(credential.Flow.Clock))
        {
            // Refresh the access token
            if (credential.RefreshTokenAsync(CancellationToken.None).Result)
            {
                accessToken = credential.Token.AccessToken;
                oAuthEmailAccessToken.Value = accessToken;
                _context.Settings.Update(oAuthEmailAccessToken);
                
                refreshToken = credential.Token.RefreshToken;
                oAuthEmailRefreshToken.Value = refreshToken;
                _context.Settings.Update(oAuthEmailRefreshToken);

                _context.SaveChanges();
            }
            else
            {
                throw new Exception("Failed to refresh access token.");
            }
        }

        var service = new GmailService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential
        });

Deployment

For the project deployment we used Docker containers. In Docker_files you'll find configurations for the API and UI.

One of the simplest ways to run the app on the server is running by the means of Docker Swarm, using docker-composer.yml. As a server we used Ubuntu Server 22.04, and nginx as a reverse-proxy server. The following instruction will be for Ubuntu 22.04.

  1. Please install Docker engine.
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  • you may not like to use "sudo" every time you execute Docker commmands.

If you want to omit this, then execute the following:

sudo groupadd docker
sudo gpasswd -a $USER docker
  1. Initialize the Swarm:
sudo docker swarm init

Make a note of the command output, as it will contain the token needed to add other nodes to the Swarm.

  1. Create a directory on your home directory or another which you prefer (e.g. /home/usv-stud-docs)

  2. Copy the file docker-compose.yml to this directory on the server.

  3. Update the ENVs in the docker-compose.yml.

    • POSTGRES_PASSWORD - the desired PostresDB instance password.
    • ASPNETCORE_ENVIRONMENT - to Development or Production
    • DbConnectionString - according to this sample: Host=<host ip>;Port=15432;Database=usv-stud-docs;Username=usv-user;Password=<password> indicated in POSTGRES_PASSWORD
    • GoogleClientId - which you obtained by visiting https://console.cloud.google.com/apis/credentials and creating oAuth2 credentials (after you created the project)
    • GoogleClientSecret - the secret from the previous place
    • GoogleRedirectUri - should correspond to the one indicated in Google Console, and should be according to this sample: https://<host_name>/auth/redirect
    • GoogleEmailRedirectUri - this is used for authorizing for sending emails, should correspond to the one indicated in Google Console, and should be according to this sample: https://<host_name>/auth/redirect
    • JwtSecretKey - a random string 16-32 chars length
  4. Create new directory called db, assign the ownership to the user that will run the app (e.g. usvdocs) and assign 777 access rights.

mkdir db
sudo chown usvdocs db
sudo chmod 777 db
  1. In real-world scenario you'd need to push the Docker image to your own private DockerHub, where the Docker images are stored. In our case, you could use the already stored public images, that are indicated in the docker-compose.yml (this needs no changes). This would require to exeute docker login -u {username} commane.

  2. You need to start your services by executing next command:

docker stack deploy --compose-file docker-compose.yml usv-docs --with-registry-auth
  1. You may want to check the services status:
docker service ls
  1. After the services were created, please connect to the DB (postgresql), don't forget to use the corresponding port from the docker-compose.yml and exeucte the file USVStudDocs.DAL/MigrationSQL/migration.sql.
  2. After this your app instance will be populated with the data structure and the initial data.
  3. You may want to set-up the load balancer, to serve the L7 network, e.g. nginx. You could find a sample Nginx set-up in Docker_files/nginx_lb/usvstud.conf which should be places in /etc/nginx/sites-available and then a symbolic link (ln -s) should be created to /etc/nginx/sites-enabled.
  4. You may also want to install some LetsEncypt SSL cetificates in case no other options are available:
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx

Getting Started

This is a description of how you should run up this project locally. To get a local copy up and running follow these simple example steps.

Prerequisites

First of all, make sure you have installed NodeJs major version 16. You could download it from the official site or by using nvm. I highly recommend you to use Yarn as a package manager tool. Please install it by next command:

npm install --global yarn

Here you could see the Swagger API documentation for the back-end end-points: View API documentaion

USV StudDocs Swagger

Installation

There are two projects in the Git repository – UI and API. You'll find the UI project into RemoteFinder.UI directory.

  1. git clone https://github.com/adrianbarbe/RemoteFinder.git
  2. Install the NPM packages by executing
    yarn install
  3. Run the project for development
    yarn serve

To run the API project, you'll need to create a .env file in RemoteFinder.Web with the following keys:

  1. ASPNETCORE_ENVIRONMENT which could be Development or Production
  2. DbConnectionString the connection string to your PostgreSQL database engine installation.
  3. GoogleClientId is the client id for the Google oAuth2 Credentials. For creating new credentials please access Google Developers Console
  4. GoogleClientSecret client secret generated in Google Developers Console
  5. GoogleRedirectUri redirect URI which you indicated in the Google oAuth2 application settings
  6. GoogleEmailRedirectUri redirect URI for email sending, which you indicated in the Google oAuth2 application settings
  7. JwtSettings:SecretKey and JwtSecretKey – a hash string for JWT tokens.

After creating the .env file you could start your project locally by running dotnet run command or by using the configuration profile for JetBrains Rider which is stored in the project repository.

Deployment

In the project root you'll find a directory Docker_files that contains Docker definitions for projects and a docker-compose.yml file. You could use it for deploying the project into a Docker Swarm cluster. You could create it by installing Docker on the server and initialize a Swarm by doker swarm init command. Then, by placing the docker-compose.yml file in the desired directory, execute next command to initialize the Docker Swarm services:

docker stack deploy --compose-file docker-compose.yml usv-docs --with-registry-auth

Don't forget to update correspondingly the environment variables in the docker-compose.yml file for be service.

(back to top)

Usage

Here you could see the usage examples for the student.

View the Demo Here »»»

To add a new request for a certificate to your dean office just click the main button above the list of the request and you'll see this Add New Certificate request modal: USV StudDocs Add New

By default student will see the list of already made the requests. Stuent is able to add new request only then the previous one was solved: USV StudDocs Add New

(back to top)

Roadmap

  • Add Basic functionality
  • Add Multiple Faculties
  • Add possibility to check the status of the certificate request
  • Receive email when the status is changed
  • Administration of the data from the UI
  • Make mobile-ready web-version
  • Multi-language Support

See the open issues for a full list of proposed features (and known issues).

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

License

Distributed under the GNU GPL License. See LICENSE.txt for more information.

(back to top)

Contact

Adrian Barbe - @adryanbarbe - adryanbarbe@gmail.com

Project Link: https://github.com/adrianbarbe/usv-stud-docs

(back to top)

Acknowledgments

(back to top)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors