summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/Elements.Backend/Controllers/AuthController.cs106
-rw-r--r--backend/Elements.Backend/Controllers/UserController.cs41
-rw-r--r--backend/Elements.Backend/Controllers/WeatherForecastController.cs32
-rw-r--r--backend/Elements.Backend/Elements.Backend.csproj41
-rw-r--r--backend/Elements.Backend/Program.cs53
-rw-r--r--backend/Elements.Backend/WeatherForecast.cs12
-rw-r--r--backend/Elements.Data/ApplicationContextFactory.cs15
-rw-r--r--backend/Elements.Data/ApplicationDbContext.cs9
-rw-r--r--backend/Elements.Data/Elements.Data.csproj5
-rw-r--r--backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs80
-rw-r--r--backend/Elements.Data/Migrations/20231020142907_Initial.cs64
-rw-r--r--backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs77
-rw-r--r--backend/Elements.Data/Models/Element.cs13
-rw-r--r--backend/Elements.Data/Models/User.cs9
14 files changed, 484 insertions, 73 deletions
diff --git a/backend/Elements.Backend/Controllers/AuthController.cs b/backend/Elements.Backend/Controllers/AuthController.cs
new file mode 100644
index 0000000..56e7c3b
--- /dev/null
+++ b/backend/Elements.Backend/Controllers/AuthController.cs
@@ -0,0 +1,106 @@
+using System.Runtime.Serialization;
+using System.Security.Claims;
+using System.Text.Json;
+using Elements.Data;
+using Elements.Data.Models;
+using Google.Apis.Auth;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elements.Backend.Controllers;
+
+[ApiController]
+[Route("[controller]/[action]")]
+public class AuthController : ControllerBase
+{
+ private readonly IConfiguration _config;
+ private readonly ApplicationDbContext _dbContext;
+
+ public AuthController(IConfiguration config, ApplicationDbContext dbContext)
+ {
+ _config = config;
+ _dbContext = dbContext;
+ }
+
+ public class LoginModel
+ {
+ public required string GoogleToken { get; init; }
+ }
+
+ [HttpPost]
+ public async Task<IActionResult> Login([FromBody] LoginModel model)
+ {
+ GoogleJsonWebSignature.Payload? payload = await VerifyGoogleIdToken(model.GoogleToken);
+ if (payload == null)
+ return Unauthorized();
+
+ User? user = await _dbContext.Users.SingleOrDefaultAsync(u => u.GoogleId == payload.Subject);
+ if (user != null)
+ {
+ //Check if user's name changed and update if it did
+ if (user.Name != payload.Name)
+ user.Name = payload.Name;
+ }
+ else
+ {
+ user = new User()
+ {
+ Name = payload.Name,
+ GoogleId = payload.Subject,
+ Elements = new List<Element>()
+ };
+
+ await _dbContext.Users.AddAsync(user);
+ }
+
+ await _dbContext.SaveChangesAsync();
+
+ List<Claim> claims = new()
+ {
+ new Claim("id", user.Id.ToString()),
+ new Claim(ClaimTypes.Role, "User")
+ };
+ ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);
+
+ AuthenticationProperties authProperties = new()
+ {
+ IsPersistent = true,
+ AllowRefresh = true
+ };
+
+ await HttpContext.SignInAsync(
+ CookieAuthenticationDefaults.AuthenticationScheme,
+ new ClaimsPrincipal(claimsIdentity),
+ authProperties);
+
+ var response = new
+ {
+ Id = user.Id.ToString()
+ };
+
+ return Ok(JsonSerializer.Serialize(response));
+ }
+
+ [HttpPost]
+ public async Task<IActionResult> Logout()
+ {
+ await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+ return Ok();
+ }
+
+ private async Task<GoogleJsonWebSignature.Payload?> VerifyGoogleIdToken(string token)
+ {
+ try
+ {
+ GoogleJsonWebSignature.Payload? payload = await GoogleJsonWebSignature.ValidateAsync(token);
+ return payload;
+ }
+ catch (InvalidJwtException)
+ {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/backend/Elements.Backend/Controllers/UserController.cs b/backend/Elements.Backend/Controllers/UserController.cs
new file mode 100644
index 0000000..bde93aa
--- /dev/null
+++ b/backend/Elements.Backend/Controllers/UserController.cs
@@ -0,0 +1,41 @@
+using System.Security.Claims;
+using System.Text.Json;
+using Elements.Data;
+using Elements.Data.Models;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elements.Backend.Controllers;
+
+[ApiController]
+[Route("[controller]/[action]")]
+public class UserController: ControllerBase
+{
+ private readonly ApplicationDbContext _dbContext;
+
+ public UserController(ApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ [HttpGet]
+ [Authorize]
+ [Route("/user/{id:int}")]
+ public async Task<IActionResult> Users(int id)
+ {
+ IEnumerable<Claim> claims = User.Claims;
+ string? currentUserId = claims.FirstOrDefault(claim => claim.Type == "id")?.Value;
+ if (currentUserId == null)
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ if (currentUserId != id.ToString())
+ return Unauthorized();
+
+ User? user = await _dbContext.Users.FirstOrDefaultAsync(user => user.Id == id);
+ if (user == null)
+ return StatusCode(StatusCodes.Status500InternalServerError);
+
+ string userJson = JsonSerializer.Serialize(user);
+ return Ok(userJson);
+ }
+} \ No newline at end of file
diff --git a/backend/Elements.Backend/Controllers/WeatherForecastController.cs b/backend/Elements.Backend/Controllers/WeatherForecastController.cs
deleted file mode 100644
index 9a70e36..0000000
--- a/backend/Elements.Backend/Controllers/WeatherForecastController.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace Elements.Backend.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<WeatherForecastController> _logger;
-
- public WeatherForecastController(ILogger<WeatherForecastController> logger)
- {
- _logger = logger;
- }
-
- [HttpGet(Name = "GetWeatherForecast")]
- public IEnumerable<WeatherForecast> Get()
- {
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateOnly.FromDateTime(DateTime.Now),
- TemperatureC = Random.Shared.Next(-20, 55),
- Summary = Summaries[Random.Shared.Next(Summaries.Length)]
- })
- .ToArray();
- }
-}
diff --git a/backend/Elements.Backend/Elements.Backend.csproj b/backend/Elements.Backend/Elements.Backend.csproj
index 0c664a6..787d048 100644
--- a/backend/Elements.Backend/Elements.Backend.csproj
+++ b/backend/Elements.Backend/Elements.Backend.csproj
@@ -1,18 +1,27 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
-
- <PropertyGroup>
- <TargetFramework>net7.0</TargetFramework>
- <Nullable>enable</Nullable>
- <ImplicitUsings>enable</ImplicitUsings>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
- </ItemGroup>
-
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <UserSecretsId>dae266ca-b349-4b87-a992-5470f9dd635d</UserSecretsId>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Google.Apis.Auth" Version="1.62.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="7.0.12" />
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" />
+ <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.10" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
+ </ItemGroup>
+
<ItemGroup>
<ProjectReference Include="..\Elements.Data\Elements.Data.csproj" />
- </ItemGroup>
-
-</Project>
+ </ItemGroup>
+
+</Project>
diff --git a/backend/Elements.Backend/Program.cs b/backend/Elements.Backend/Program.cs
index 15eacee..50e5255 100644
--- a/backend/Elements.Backend/Program.cs
+++ b/backend/Elements.Backend/Program.cs
@@ -1,23 +1,54 @@
+using System.Text.Json;
+using Elements.Data;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.Google;
+using Microsoft.EntityFrameworkCore;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
+builder.Services.AddRouting(options => options.LowercaseUrls = true);
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+});
-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())
+if (builder.Environment.IsDevelopment())
{
- app.UseSwagger();
- app.UseSwaggerUI();
+ builder.Services.AddDbContext<ApplicationDbContext>(options =>
+ {
+ options.UseSqlite("Data Source=elements.db");
+ });
}
+builder.Services
+.AddAuthentication(options =>
+{
+ options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+})
+.AddCookie(options =>
+{
+ options.Events = new()
+ {
+ OnRedirectToLogin = (ctx) =>
+ {
+ ctx.Response.StatusCode = 401;
+ return Task.CompletedTask;
+ },
+ OnRedirectToAccessDenied = (ctx) =>
+ {
+ ctx.Response.StatusCode = 401;
+ return Task.CompletedTask;
+ }
+
+ };
+});
+
+var app = builder.Build();
+
app.UseHttpsRedirection();
+app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
diff --git a/backend/Elements.Backend/WeatherForecast.cs b/backend/Elements.Backend/WeatherForecast.cs
deleted file mode 100644
index 7c8962e..0000000
--- a/backend/Elements.Backend/WeatherForecast.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Elements.Backend;
-
-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/backend/Elements.Data/ApplicationContextFactory.cs b/backend/Elements.Data/ApplicationContextFactory.cs
new file mode 100644
index 0000000..b8e4ebd
--- /dev/null
+++ b/backend/Elements.Data/ApplicationContextFactory.cs
@@ -0,0 +1,15 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+namespace Elements.Data;
+
+public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
+{
+ public ApplicationDbContext CreateDbContext(string[] args)
+ {
+ DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new();
+ optionsBuilder.UseSqlite("Data Source=elements.db");
+
+ return new ApplicationDbContext(optionsBuilder.Options);
+ }
+} \ No newline at end of file
diff --git a/backend/Elements.Data/ApplicationDbContext.cs b/backend/Elements.Data/ApplicationDbContext.cs
index 619719a..a912cc4 100644
--- a/backend/Elements.Data/ApplicationDbContext.cs
+++ b/backend/Elements.Data/ApplicationDbContext.cs
@@ -1,7 +1,14 @@
-using Microsoft.EntityFrameworkCore;
+using Elements.Data.Models;
+using Microsoft.EntityFrameworkCore;
namespace Elements.Data;
public class ApplicationDbContext : DbContext
{
+ public DbSet<Element> Elements { get; set; }
+ public DbSet<User> Users { get; set; }
+ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
+ {
+
+ }
}
diff --git a/backend/Elements.Data/Elements.Data.csproj b/backend/Elements.Data/Elements.Data.csproj
index 408fea2..2fb1f63 100644
--- a/backend/Elements.Data/Elements.Data.csproj
+++ b/backend/Elements.Data/Elements.Data.csproj
@@ -7,6 +7,11 @@
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.12" />
</ItemGroup>
diff --git a/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs b/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs
new file mode 100644
index 0000000..1272d62
--- /dev/null
+++ b/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs
@@ -0,0 +1,80 @@
+// <auto-generated />
+using Elements.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elements.Data.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20231020142907_Initial")]
+ partial class Initial
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.12");
+
+ modelBuilder.Entity("Elements.Data.Models.Element", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<int>("State")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Elements");
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.User", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("GoogleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.Element", b =>
+ {
+ b.HasOne("Elements.Data.Models.User", null)
+ .WithMany("Elements")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.User", b =>
+ {
+ b.Navigation("Elements");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/backend/Elements.Data/Migrations/20231020142907_Initial.cs b/backend/Elements.Data/Migrations/20231020142907_Initial.cs
new file mode 100644
index 0000000..8257e5b
--- /dev/null
+++ b/backend/Elements.Data/Migrations/20231020142907_Initial.cs
@@ -0,0 +1,64 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elements.Data.Migrations
+{
+ /// <inheritdoc />
+ public partial class Initial : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ GoogleId = table.Column<string>(type: "TEXT", nullable: false),
+ Name = table.Column<string>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Elements",
+ columns: table => new
+ {
+ Id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column<int>(type: "INTEGER", nullable: false),
+ Name = table.Column<string>(type: "TEXT", nullable: false),
+ State = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Elements", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Elements_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Elements_UserId",
+ table: "Elements",
+ column: "UserId");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Elements");
+
+ migrationBuilder.DropTable(
+ name: "Users");
+ }
+ }
+}
diff --git a/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 0000000..b713511
--- /dev/null
+++ b/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,77 @@
+// <auto-generated />
+using Elements.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elements.Data.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.12");
+
+ modelBuilder.Entity("Elements.Data.Models.Element", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<int>("State")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Elements");
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.User", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("GoogleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.Element", b =>
+ {
+ b.HasOne("Elements.Data.Models.User", null)
+ .WithMany("Elements")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Elements.Data.Models.User", b =>
+ {
+ b.Navigation("Elements");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/backend/Elements.Data/Models/Element.cs b/backend/Elements.Data/Models/Element.cs
index 2f602e5..08f25a8 100644
--- a/backend/Elements.Data/Models/Element.cs
+++ b/backend/Elements.Data/Models/Element.cs
@@ -1,5 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
namespace Elements.Data.Models;
+public enum ElementState {
+ HasColor,
+ HasIcon
+}
+
public class Element {
-
+ public int Id {get; init;}
+ public required int UserId { get; init; }
+ public required string Name {get; init;}
+ public required ElementState State {get; init;}
} \ No newline at end of file
diff --git a/backend/Elements.Data/Models/User.cs b/backend/Elements.Data/Models/User.cs
new file mode 100644
index 0000000..b44a1e2
--- /dev/null
+++ b/backend/Elements.Data/Models/User.cs
@@ -0,0 +1,9 @@
+namespace Elements.Data.Models;
+
+public class User
+{
+ public int Id { get; init; }
+ public required string GoogleId { get; init; }
+ public required string Name { get; set; }
+ public required ICollection<Element> Elements { get; set; }
+} \ No newline at end of file