Techninė duomenų reprezentacija
Dokumentacija apie laukų reikšmes
{{ $store.state.auth.tokenData }}
@@ -92,7 +92,7 @@ export default {
serverVerify() {
this.verificationResult = null
axios
- .get('/test/authed', {
+ .get('/api/AuthMetadata/authed', {
headers: { Authorization: `Bearer ${this.$store.state.auth.token}` },
})
.then(response => {
diff --git a/KTUSAPS/ClientApp/src/store/index.js b/KTUSAPS/ClientApp/src/store/index.js
index 4464de2..7ba006d 100644
--- a/KTUSAPS/ClientApp/src/store/index.js
+++ b/KTUSAPS/ClientApp/src/store/index.js
@@ -1,7 +1,7 @@
-import { createStore, createLogger } from "vuex";
-import auth from "./modules/auth";
+import { createStore, createLogger } from 'vuex'
+import auth from './modules/auth'
-const debug = process.env.NODE_ENV !== "production";
+const debug = process.env.NODE_ENV !== 'production'
export default createStore({
modules: {
@@ -9,4 +9,4 @@ export default createStore({
},
strict: debug,
plugins: debug ? [createLogger()] : [],
-});
+})
diff --git a/KTUSAPS/ClientApp/src/store/modules/auth.js b/KTUSAPS/ClientApp/src/store/modules/auth.js
index 925628a..a64ce91 100644
--- a/KTUSAPS/ClientApp/src/store/modules/auth.js
+++ b/KTUSAPS/ClientApp/src/store/modules/auth.js
@@ -52,7 +52,15 @@ const getters = {
},
userId(state, getters) {
if (!getters.isValid) return null
- return state.tokenData.email
+ return state.tokenData.sub
+ },
+ isExpiringSoon(state, getters) {
+ if (!getters.isValid) return false
+ return true
+ },
+ expires(state, getters) {
+ if (!getters.isValid) return 0
+ return new Date(state.tokenData.exp * 1000)
},
loginUrl(state, getters) {
if (!getters.isReady) return null
diff --git a/KTUSAPS/Controllers/AuthMetadataController.cs b/KTUSAPS/Controllers/AuthMetadataController.cs
index c1c62d1..d967653 100644
--- a/KTUSAPS/Controllers/AuthMetadataController.cs
+++ b/KTUSAPS/Controllers/AuthMetadataController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
@@ -10,15 +11,39 @@ namespace KTUSAPS.Controllers
{
[Route("api/[controller]")]
[ApiController]
+ [Produces("application/json")]
public class AuthMetadataController : ControllerBase
{
+ public class AuthMetadata
+ {
+ public string ClientId { get; set; }
+ public string Authority { get; set; }
+ public string Tenant { get; set; }
+ }
+
private readonly IConfiguration _configuration;
public AuthMetadataController(IConfiguration configuration)
{
_configuration = configuration;
}
+ ///
+ /// Get authethication metadata needed to obtain token.
+ ///
+ ///
[HttpGet]
- public object Index() => new { ClientId = _configuration["ClientId"], Authority = _configuration["Authority"], Tenant = _configuration["Tenant"] };
+ public AuthMetadata Index() => new AuthMetadata { ClientId = _configuration["ClientId"], Authority = _configuration["Authority"], Tenant = _configuration["Tenant"] };
+
+ ///
+ /// Returns true is provided token is valid, else throws exception
+ ///
+ ///
+ /// Provided token is correct.
+ /// No valid token provided.
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [HttpGet("Authed")]
+ public bool IsAuthed() => true;
}
}
diff --git a/KTUSAPS/Controllers/IssueController.cs b/KTUSAPS/Controllers/IssueController.cs
new file mode 100644
index 0000000..7956d54
--- /dev/null
+++ b/KTUSAPS/Controllers/IssueController.cs
@@ -0,0 +1,96 @@
+using KTUSAPS.Data.Model;
+using KTUSAPS.Extensions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace KTUSAPS.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class IssueController : ControllerBase
+ {
+ private readonly Data.SAPSDataContext dataContext;
+
+ public IssueController(Data.SAPSDataContext dataContext)
+ {
+ this.dataContext = dataContext;
+ }
+
+
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public IEnumerable GetIssues()
+ {
+ return dataContext.Issues;
+ }
+
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task> CreateIssueAsync([FromBody] Issue issueToCreate)
+ {
+ if (issueToCreate == null)
+ return BadRequest("No data provided for object to be created.");
+ if (issueToCreate.Id != default)
+ return BadRequest("Id has been set on create request, please do not do that, set id to 0 or ommit it.");
+ if (issueToCreate.IssueTypeId == default)
+ return BadRequest("No typeId has been specified");
+ if (issueToCreate.Problem != null && issueToCreate.Feedback != null && issueToCreate.IssueType != null)
+ return BadRequest("Do not privide navigation property values.");
+ // TODO: Enable next line and make thoes two fields come from user identity
+ //if (issueToCreate.UserID != default || issueToCreate.Email != default)
+ // return BadRequest("Do not provide indentity values.");
+
+ var createdValue = await dataContext.AddAsync(issueToCreate);
+ await dataContext.SaveChangesAsync();
+ var url = Url.ActionLink(action: nameof(GetIssue), values: new { Id = createdValue.Entity.Id });
+ return Created(url, createdValue.Entity);
+ }
+
+ [HttpGet("{id}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public ActionResult GetIssue(int id)
+ {
+ var issue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
+ if(issue == default)
+ return NotFound();
+ return Ok(issue);
+ }
+
+ [HttpPatch("{id}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> UpdateIssueAsync(int id, [FromBody] Issue issue)
+ {
+ var databaseIssue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
+ if (databaseIssue == default)
+ return NotFound();
+ var eIssue = dataContext.Attach(databaseIssue);
+ eIssue.MovePropertyDataWhiteList(issue, new string[] {
+ nameof(databaseIssue.Description),
+ nameof(databaseIssue.IssueTypeId),
+ nameof(databaseIssue.Publishable)
+ });
+ await dataContext.SaveChangesAsync();
+ return Ok(eIssue.Entity);
+ }
+
+ [HttpDelete("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteIssueAsync(int id)
+ {
+ var issue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
+ if (issue == default)
+ return NotFound();
+ dataContext.Issues.Remove(issue);
+ await dataContext.SaveChangesAsync();
+ return NoContent();
+ }
+ }
+}
diff --git a/KTUSAPS/Controllers/TestController.cs b/KTUSAPS/Controllers/TestController.cs
deleted file mode 100644
index fcaf3f7..0000000
--- a/KTUSAPS/Controllers/TestController.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Microsoft.AspNetCore.Authentication.JwtBearer;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace KTUSAPS.Controllers
-{
- [Route("[controller]")]
- [Authorize]
- [ApiController]
- public class TestController : ControllerBase
- {
- [HttpGet]
- public object[] Index()
- {
- return HttpContext.User.Claims.Select(x => new { Name = x.Type, Value= x.Value }).ToArray();
- }
-
- [HttpGet("authed")]
- public bool IsAuthed() => true;
- }
-}
diff --git a/KTUSAPS/Extensions/EntityEntryExtensions.cs b/KTUSAPS/Extensions/EntityEntryExtensions.cs
new file mode 100644
index 0000000..156c7f3
--- /dev/null
+++ b/KTUSAPS/Extensions/EntityEntryExtensions.cs
@@ -0,0 +1,44 @@
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace KTUSAPS.Extensions
+{
+ public static class EntityEntryExtensions
+ {
+ public static void MovePropertyDataBlackList(this EntityEntry target, object source, IEnumerable blacklistedProprties)
+ {
+ MovePropertyData(target, source, (prop) => blacklistedProprties.Contains(prop.Metadata.Name));
+ }
+
+ public static void MovePropertyDataWhiteList(this EntityEntry target, object source, IEnumerable whitelistedProprties)
+ {
+ MovePropertyData(target, source, (prop) => !whitelistedProprties.Contains(prop.Metadata.Name));
+ }
+
+ public static void MovePropertyData(this EntityEntry target, object source, Func isBlacklisted)
+ {
+ foreach (var prop in target.Properties)
+ {
+ if (isBlacklisted(prop))
+ continue;
+
+ var propertyInfo = prop.Metadata.PropertyInfo;
+ var newValue = propertyInfo.GetValue(source);
+ if (!newValue.isDefault()) {
+ prop.CurrentValue = newValue;
+ }
+ }
+ }
+
+ private static bool isDefault(this object value)
+ {
+ if(value == default)
+ return true;
+ if (value is int || value is long)
+ return (int)value == default(int);
+ return false;
+ }
+ }
+}
diff --git a/KTUSAPS/KTUSAPS.csproj b/KTUSAPS/KTUSAPS.csproj
index 149e5f5..ddd326c 100644
--- a/KTUSAPS/KTUSAPS.csproj
+++ b/KTUSAPS/KTUSAPS.csproj
@@ -11,8 +11,10 @@
-
-
+
+
+
+
@@ -22,6 +24,10 @@
+
+
+
+
diff --git a/KTUSAPS/Services/DatabaseInitializationService.cs b/KTUSAPS/Services/DatabaseInitializationService.cs
new file mode 100644
index 0000000..45a0d5a
--- /dev/null
+++ b/KTUSAPS/Services/DatabaseInitializationService.cs
@@ -0,0 +1,138 @@
+using KTUSAPS.Data;
+using KTUSAPS.Data.Model;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace KTUSAPS.Services
+{
+ public class DatabaseInitializationService : IHostedService
+ {
+ private readonly IServiceProvider serviceProvider;
+ private readonly ILogger logger;
+
+ public DatabaseInitializationService(IServiceProvider serviceProvider, ILogger logger)
+ {
+ this.serviceProvider = serviceProvider;
+ this.logger = logger;
+ }
+
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ using var scope = serviceProvider.CreateScope();
+ var dataContext = scope.ServiceProvider.GetRequiredService();
+ var migrations = (await dataContext.Database.GetPendingMigrationsAsync(cancellationToken: cancellationToken)).ToList();
+ if(migrations.Any())
+ {
+ logger.LogInformation($"There are {migrations.Count} pending migrations. Applying them");
+ try
+ {
+ await dataContext.Database.MigrateAsync(cancellationToken: cancellationToken);
+ await Seed(dataContext);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("Migration failed. Database may be corrupt!");
+ logger.LogError(ex, "Migration failed.");
+ }
+ }
+ }
+
+ public async Task Seed(SAPSDataContext dataContext)
+ {
+ var generalIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
+ {
+ Name = "Bendra",
+ NameEn = "General"
+ });
+ var otherIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
+ {
+ Name = "Kita",
+ NameEn = "Other"
+ });
+ var feedbackIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
+ {
+ Name = "Atsiliepimas",
+ NameEn = "Feedback"
+ });
+ await dataContext.SaveChangesAsync();
+
+ var issue1 = await dataContext.Issues.AddAsync(new Issue()
+ {
+ Created = DateTime.Now.AddDays(-5),
+ Description = "Man nepatinka dėstytojas.",
+ Email = "karolis.kundrotas@ktu.edu",
+ Publishable = true,
+ IssueType = generalIssueType.Entity
+ });
+ var issue2 = await dataContext.Issues.AddAsync(new Issue()
+ {
+ Created = DateTime.Now.AddDays(-12).AddHours(3),
+ Description = "Dėtytoja atsiskaitymo metu leido nusirašynėti kitiems, o man neleido.",
+ Email = "karolis.kundrotas@ktu.edu",
+ Publishable = true,
+ IssueType = otherIssueType.Entity
+ });
+ var issue3 = await dataContext.Issues.AddAsync(new Issue()
+ {
+ Created = DateTime.Now.AddDays(-18),
+ Description = "Tinklų destytoja per paskaitą neatsako į klausimus ir per paskaitą nieko neišmoko.",
+ Email = "karolis.kundrotas@ktu.edu",
+ Publishable = false,
+ IssueType = generalIssueType.Entity
+ });
+ var issue4 = await dataContext.Issues.AddAsync(new Issue()
+ {
+ Created = DateTime.Now.AddDays(-18),
+ Description = "Saitynų destytojas Tomas labai maloniai ir profesonaliai bendrauja.",
+ Email = "karolis.kundrotas@ktu.edu",
+ Publishable = true,
+ IssueType = feedbackIssueType.Entity,
+ Solved = true
+ });
+ await dataContext.SaveChangesAsync();
+
+ await dataContext.PublishedFeedbacks.AddAsync(new PublishedFeedback()
+ {
+ Issue = issue4.Entity,
+ FeedbackLt = "Studentas mano kad Saitynų dėstytojas Tomas yra profesonalus ir mandagiai bendraujantis.",
+ FeedbackEn = "Student thinks that Site creation module lecturer Tomas is profesonal ir pleasant at communications.",
+ });
+
+ var problem1 = await dataContext.PublishedProblems.AddAsync(new PublishedProblem()
+ {
+ Issue = issue2.Entity,
+ ProblemLt = "Atsikaitymo metu buvo leista nusirašynėti.",
+ ProblemEn = "During exam cheating was allowed.",
+ });
+ var problem2 = await dataContext.PublishedProblems.AddAsync(new PublishedProblem()
+ {
+ Issue = issue3.Entity,
+ ProblemLt = "Dėstytoja V. Pavardenė nemoko studentų per paskaitas, neraguoja į studentų klausimus, nesuteikia pagalbos.",
+ ProblemEn = "Lecturer V. Pavardenė does not lecture students, do not react to student questions and doesn't provide help."
+ });
+
+ await dataContext.SaveChangesAsync();
+
+ await dataContext.Solutions.AddAsync(new Solution()
+ {
+ Problem = problem2.Entity,
+ SolutionLt = "V. Parvedenei buvo priskirta tarnybinę nuobauda.",
+ SolutionEn = ""
+ });
+
+ await dataContext.SaveChangesAsync();
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/KTUSAPS/Startup.cs b/KTUSAPS/Startup.cs
index d2aee56..f658c02 100644
--- a/KTUSAPS/Startup.cs
+++ b/KTUSAPS/Startup.cs
@@ -1,9 +1,12 @@
+using KTUSAPS.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using System;
using VueCliMiddleware;
namespace KTUSAPS
@@ -39,7 +42,12 @@ namespace KTUSAPS
.RequireAuthenticatedUser()
.Build();
});
-
+
+ var connectionString = Configuration.GetConnectionString("Main");
+ services.AddDbContext((options) => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
+ services.AddHostedService();
+
+ services.AddSwaggerGen();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -48,8 +56,16 @@ namespace KTUSAPS
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
+
+ app.UseSwaggerUI(c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
+ });
}
+ app.UseSwagger(c =>
+ {
+ });
app.UseRouting();
app.UseSpaStaticFiles();
app.UseAuthentication();
diff --git a/KTUSAPS/appsettings.Development.json b/KTUSAPS/appsettings.Development.json
index 5f5e00e..b53a0da 100644
--- a/KTUSAPS/appsettings.Development.json
+++ b/KTUSAPS/appsettings.Development.json
@@ -1,4 +1,7 @@
{
+ "ConnectionStrings": {
+ "Main": "Server=localhost;User=saps_dev;Password=;Database=saps_dev"
+ },
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/KTUSAPS/appsettings.json b/KTUSAPS/appsettings.json
index 547bd36..7555ad8 100644
--- a/KTUSAPS/appsettings.json
+++ b/KTUSAPS/appsettings.json
@@ -1,4 +1,7 @@
{
+ "ConnectionStrings": {
+ "Main": "Server=localhost;User=saps;Password=B35eJUmIJxeG0g9yi6ni;Database=saps"
+ },
"Logging": {
"LogLevel": {
"Default": "Information",