diff --git a/SurveyBackend/SurveyBackend.API/Controllers/ExportController.cs b/SurveyBackend/SurveyBackend.API/Controllers/ExportController.cs new file mode 100644 index 0000000..23ef2e0 --- /dev/null +++ b/SurveyBackend/SurveyBackend.API/Controllers/ExportController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using SurveyLib.Tools.Tools; + +namespace SurveyBackend.Controllers; + +[ApiController] +[Route("api/export")] +public class ExportController +{ + private readonly TableExporter _tableExporter; + + public ExportController(TableExporter tableExporter) + { + _tableExporter = tableExporter; + } + + [HttpGet("excel/{surveyId:int}")] + public async Task ExportSurveyById(int surveyId) + { + var fileBytes = await _tableExporter.ExportDataBySurveyIdAsync(surveyId); + return new FileContentResult(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + FileDownloadName = $"survey_{surveyId}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.xlsx" + }; + } +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index 9f1b5dc..670bb54 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs @@ -70,8 +70,8 @@ public class QuestionController : ControllerBase { var question = QuestionMapper.QuestionUpdateToModel(dto, id); await _questionService.UpdateQuestionAsync(question); - var result = QuestionMapper.ModelToQuestionDto(question); - return Ok(result); + var updatedQuestion = await _questionService.GetQuestionByIdAsync(id); + return Ok(updatedQuestion); } /// diff --git a/SurveyBackend/SurveyBackend.API/Program.cs b/SurveyBackend/SurveyBackend.API/Program.cs index 3c66899..4529b73 100644 --- a/SurveyBackend/SurveyBackend.API/Program.cs +++ b/SurveyBackend/SurveyBackend.API/Program.cs @@ -12,6 +12,7 @@ using SurveyBackend.Middlewares; using SurveyBackend.Services; using SurveyLib.Infrastructure.EFCore; using SurveyLib.Infrastructure.EFCore.Data; +using SurveyLib.Tools.Tools; namespace SurveyBackend; @@ -38,6 +39,8 @@ public class Program builder.Services.AddSurveyLibInfrastructure(); builder.Services.AddSurveyBackendServices(); + builder.Services.AddScoped(); + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { diff --git a/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj b/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj index 85458f2..54e0234 100644 --- a/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj +++ b/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj @@ -33,6 +33,7 @@ + diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.Designer.cs b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.Designer.cs new file mode 100644 index 0000000..53120df --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.Designer.cs @@ -0,0 +1,328 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SurveyBackend.Infrastructure.Data; + +#nullable disable + +namespace SurveyBackend.Infrastructure.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250608095147_Update answers")] + partial class Updateanswers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.15"); + + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("INTEGER"); + + b.Property("UsersId") + .HasColumnType("INTEGER"); + + b.HasKey("GroupsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("SurveyBackend.Core.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("SurveyBackend.Core.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AnswerText") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CompletionId") + .HasColumnType("INTEGER"); + + b.Property("QuestionId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("CompletionId", "QuestionId", "AnswerText") + .IsUnique(); + + b.ToTable("Answers"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.AnswerVariant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("QuestionId") + .HasColumnType("INTEGER"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.ToTable("AnswerVariants"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Completion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CompletedBy") + .HasColumnType("INTEGER"); + + b.Property("FinishedAt") + .HasColumnType("TEXT"); + + b.Property("SurveyId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompletedBy"); + + b.HasIndex("SurveyId"); + + b.ToTable("Completions"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionBase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("SurveyId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SurveyId"); + + b.ToTable("Questions"); + + b.HasDiscriminator().HasValue("QuestionBase"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Survey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Surveys"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionVariants.MultipleAnswerQuestion", b => + { + b.HasBaseType("SurveyLib.Core.Models.QuestionBase"); + + b.HasDiscriminator().HasValue("MultipleAnswerQuestion"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionVariants.SingleAnswerQuestion", b => + { + b.HasBaseType("SurveyLib.Core.Models.QuestionBase"); + + b.HasDiscriminator().HasValue("SingleAnswerQuestion"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionVariants.TextQuestion", b => + { + b.HasBaseType("SurveyLib.Core.Models.QuestionBase"); + + b.HasDiscriminator().HasValue("TextQuestion"); + }); + + modelBuilder.Entity("GroupUser", b => + { + b.HasOne("SurveyBackend.Core.Models.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SurveyBackend.Core.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Answer", b => + { + b.HasOne("SurveyLib.Core.Models.Completion", "Completion") + .WithMany("Answers") + .HasForeignKey("CompletionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SurveyLib.Core.Models.QuestionBase", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Completion"); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.AnswerVariant", b => + { + b.HasOne("SurveyLib.Core.Models.QuestionBase", "Question") + .WithMany("AnswerVariants") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Completion", b => + { + b.HasOne("SurveyBackend.Core.Models.User", null) + .WithMany() + .HasForeignKey("CompletedBy") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SurveyLib.Core.Models.Survey", "Survey") + .WithMany("Completions") + .HasForeignKey("SurveyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Survey"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionBase", b => + { + b.HasOne("SurveyLib.Core.Models.Survey", "Survey") + .WithMany("Questions") + .HasForeignKey("SurveyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Survey"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Survey", b => + { + b.HasOne("SurveyBackend.Core.Models.User", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Completion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.QuestionBase", b => + { + b.Navigation("AnswerVariants"); + + b.Navigation("Answers"); + }); + + modelBuilder.Entity("SurveyLib.Core.Models.Survey", b => + { + b.Navigation("Completions"); + + b.Navigation("Questions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.cs b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.cs new file mode 100644 index 0000000..88e5298 --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/20250608095147_Update answers.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SurveyBackend.Infrastructure.Data.Migrations +{ + /// + public partial class Updateanswers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Answers", + table: "Answers"); + + migrationBuilder.AddColumn( + name: "Id", + table: "Answers", + type: "INTEGER", + nullable: false, + defaultValue: 0) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.Sql(@"UPDATE ""Answers"" SET ""Id"" = rowid;"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Answers", + table: "Answers", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_Answers_CompletionId_QuestionId_AnswerText", + table: "Answers", + columns: new[] { "CompletionId", "QuestionId", "AnswerText" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Answers", + table: "Answers"); + + migrationBuilder.DropIndex( + name: "IX_Answers_CompletionId_QuestionId_AnswerText", + table: "Answers"); + + migrationBuilder.DropColumn( + name: "Id", + table: "Answers"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Answers", + table: "Answers", + columns: new[] { "CompletionId", "QuestionId" }); + } + } +} diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 1d266bb..f27bb62 100644 --- a/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SurveyBackend/SurveyBackend.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -76,20 +76,27 @@ namespace SurveyBackend.Infrastructure.Data.Migrations modelBuilder.Entity("SurveyLib.Core.Models.Answer", b => { - b.Property("CompletionId") - .HasColumnType("INTEGER"); - - b.Property("QuestionId") + b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); b.Property("AnswerText") .IsRequired() .HasColumnType("TEXT"); - b.HasKey("CompletionId", "QuestionId"); + b.Property("CompletionId") + .HasColumnType("INTEGER"); + + b.Property("QuestionId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); b.HasIndex("QuestionId"); + b.HasIndex("CompletionId", "QuestionId", "AnswerText") + .IsUnique(); + b.ToTable("Answers"); }); diff --git a/SurveyBackend/SurveyBackend.sln b/SurveyBackend/SurveyBackend.sln index 56dba6f..6f4db84 100644 --- a/SurveyBackend/SurveyBackend.sln +++ b/SurveyBackend/SurveyBackend.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurveyLib.Core", "..\Survey EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurveyBackend.Services", "SurveyBackend.Services\SurveyBackend.Services.csproj", "{3CDA6495-4FB2-4F07-8B2F-15BFD2A35181}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurveyLib.Tools", "..\SurveyLib\SurveyLib.Tools\SurveyLib.Tools.csproj", "{DA10F9E0-2682-438E-BC2B-C22B6BBD13CB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {3CDA6495-4FB2-4F07-8B2F-15BFD2A35181}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CDA6495-4FB2-4F07-8B2F-15BFD2A35181}.Release|Any CPU.ActiveCfg = Release|Any CPU {3CDA6495-4FB2-4F07-8B2F-15BFD2A35181}.Release|Any CPU.Build.0 = Release|Any CPU + {DA10F9E0-2682-438E-BC2B-C22B6BBD13CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA10F9E0-2682-438E-BC2B-C22B6BBD13CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA10F9E0-2682-438E-BC2B-C22B6BBD13CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA10F9E0-2682-438E-BC2B-C22B6BBD13CB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/SurveyLib b/SurveyLib index d9f16ee..aae9d32 160000 --- a/SurveyLib +++ b/SurveyLib @@ -1 +1 @@ -Subproject commit d9f16ee761e31bb7af7a067c38f5fa02083f9d6c +Subproject commit aae9d32397b784f115111f6a05c6dcdd1fc4f91f