From 5fe1666163ea42eb400041a9ceeba33487276bc6 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sun, 15 Mar 2026 12:28:03 -0500 Subject: [PATCH 01/47] init From 96026d448f80b000ad481e6c3b4afa83c830b0f6 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Mon, 16 Mar 2026 21:20:54 -0500 Subject: [PATCH 02/47] scaffold auth api infrastructure (doesn't work) --- api/Program.cs | 32 +++++++++++++++++++++++++ api/agologum-api.csproj | 2 ++ api/src/Controllers/UsersController.cs | 33 +++++++++++++++----------- api/src/Models/User.cs | 1 + api/src/Services/UserService.cs | 4 ++++ 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/api/Program.cs b/api/Program.cs index efa938c..a05ead5 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -1,16 +1,41 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; using agologumApi.Services; var builder = WebApplication.CreateBuilder(args); +var key = builder.Configuration["Jwt:Key"]; +if(key == null) return; + builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddControllers(); + +// services builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// configuration for jwt authentication +builder.Services.AddAuthentication(options => { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters { + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; +}); + +builder.Services.AddAuthorization(); // configuration for behind my nginx proxy builder.Services.Configure(options => @@ -41,10 +66,17 @@ builder.Services.AddCors(options => builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +// https://www.reddit.com/r/dotnet/comments/1h7vzbs/how_do_you_guys_handle_authorization_on_a_web_api/ +// add authorization here +// controllers will have endpoints based on authorization +// frontend is a different story + var app = builder.Build(); app.UseForwardedHeaders(); app.UseCors("dev"); +app.UseAuthentication(); +app.UseAuthorization(); // Configure the HTTP request pipeline. if (app.Environment.IsEnvironment("Development")) { diff --git a/api/agologum-api.csproj b/api/agologum-api.csproj index 77eaeef..82bd216 100644 --- a/api/agologum-api.csproj +++ b/api/agologum-api.csproj @@ -8,6 +8,8 @@ + + diff --git a/api/src/Controllers/UsersController.cs b/api/src/Controllers/UsersController.cs index 4178d19..7962381 100644 --- a/api/src/Controllers/UsersController.cs +++ b/api/src/Controllers/UsersController.cs @@ -1,28 +1,30 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + using agologumApi.Models; using agologumApi.Services; [ApiController] [Route("api/[controller]")] -public class UsersController : ControllerBase -{ +public class UsersController : ControllerBase { + private readonly UserService service_; - public UsersController(UserService service) - { + public UsersController(UserService service) { service_ = service; } + [AllowAnonymous] // accessible if not authorized [HttpGet] - public async Task>> getUsers() - { + public async Task>> getUsers() { return Ok(await service_.GetAll()); } + [AllowAnonymous] [HttpGet("{id:int}")] - public async Task> getUser(int id) - { + public async Task> getUser(int id) { + var user = await service_.Get(id); if (user == null) return NotFound(); @@ -30,9 +32,10 @@ public class UsersController : ControllerBase return Ok(user); } + [Authorize] // testing the authorization [HttpPost] - public async Task> createUser(User user) - { + public async Task> createUser(User user) { + var created = await service_.Create(user); return CreatedAtAction( @@ -42,9 +45,10 @@ public class UsersController : ControllerBase ); } + [Authorize] [HttpPut("{id}")] - public async Task> updateUser(int id, User user) - { + public async Task> updateUser(int id, User user) { + var updated = await service_.Update(user); if (updated == null) return NotFound(); @@ -52,9 +56,10 @@ public class UsersController : ControllerBase return Ok(updated); } + [Authorize] [HttpDelete("{id}")] - public async Task deleteUser(int id) - { + public async Task deleteUser(int id) { + var success = await service_.Delete(id); if (!success) return NotFound(); diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index b42e2d8..17d0363 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -6,5 +6,6 @@ public class User { public int Id { get; set; } public string Name { get; set; } = ""; public string Email { get; set; } = ""; + public string PasswordHash { get; set; } = ""; }; diff --git a/api/src/Services/UserService.cs b/api/src/Services/UserService.cs index 0b085ec..1f9f247 100644 --- a/api/src/Services/UserService.cs +++ b/api/src/Services/UserService.cs @@ -21,6 +21,10 @@ public class UserService { return await db_.Users.FindAsync(id); } + public async Task Get(string username) { + return await db_.Users.FirstOrDefaultAsync(u => u.Name == username); + } + public async Task Create(User user) { db_.Users.Add(user); await db_.SaveChangesAsync(); From d8f64754b4722433cdcd7a270ee347f9e29c1377 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Mon, 16 Mar 2026 21:44:33 -0500 Subject: [PATCH 03/47] add auth files --- api/src/Controllers/AuthController.cs | 62 +++++++++++++++++++++++++++ api/src/Models/DTO.cs | 15 +++++++ api/src/Services/JwtService.cs | 42 ++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 api/src/Controllers/AuthController.cs create mode 100644 api/src/Models/DTO.cs create mode 100644 api/src/Services/JwtService.cs diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs new file mode 100644 index 0000000..0b53759 --- /dev/null +++ b/api/src/Controllers/AuthController.cs @@ -0,0 +1,62 @@ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + +using agologumApi.Models; +using agologumApi.Services; + +[ApiController] +[Route("api/[controller]")] +public class AuthController : ControllerBase { + + private readonly UserService users_; + private readonly JwtService jwt_; + + public AuthController(UserService users, JwtService jwt) + { + users_ = users; + jwt_ = jwt; + } + + [HttpPost("register")] + public async Task Register(RegisterDto dto) { + var user = new User { + Name = dto.Username, + PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password) // TODO: hashing stage in client + }; + + var newUser = await users_.Create(user); + return CreatedAtAction( + nameof(Register), + new { id = newUser.Id }, + user + ); + } + + [HttpPost("login")] + public async Task Login(LoginDto dto) + { + var user = await users_.Get(dto.Username); + + if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.PasswordHash)) { + return Unauthorized(); + } + + var token = jwt_.GenerateJwt(user); + + return Ok(new { token }); + } + + [Authorize] // authorize is handled by middleware + [HttpPost("logout")] + public ActionResult Logout() { + // dummy endpoint + // logout happens upon client-side jwt removal + return Ok(); + } + + // TODO + // refresh tokens + // email verification + // password reset +} \ No newline at end of file diff --git a/api/src/Models/DTO.cs b/api/src/Models/DTO.cs new file mode 100644 index 0000000..ea508f6 --- /dev/null +++ b/api/src/Models/DTO.cs @@ -0,0 +1,15 @@ + +public class RegisterDto { + + public string Username { get; set; } = ""; + public string Email { get; set; } = ""; + public string Password { get; set; } = ""; + +} + +public class LoginDto { + + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; + +} diff --git a/api/src/Services/JwtService.cs b/api/src/Services/JwtService.cs new file mode 100644 index 0000000..14e0b69 --- /dev/null +++ b/api/src/Services/JwtService.cs @@ -0,0 +1,42 @@ + +using Microsoft.IdentityModel.Tokens; +using System.Text; +using System.Security.Claims; +using System.IdentityModel.Tokens.Jwt; + +using agologumApi.Models; + +public class JwtService { + + private readonly IConfiguration config_; + + public JwtService(IConfiguration config) { // why the heck does c# not have initializer lists ? + config_ = config; + } + + public string? GenerateJwt(User user) { + + string? jwtKey = config_["Jwt:Key"]; + if(jwtKey == null) return null; + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)); + + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + // not too sure + var claims = new[] { + new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) + }; + + var token = new JwtSecurityToken( + claims: claims, + expires: DateTime.UtcNow.AddHours(2), // will add a refresher later + signingCredentials: creds + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + + } + + +} \ No newline at end of file From c19cd0c718d7d58a562be40c35720f3bff902cd3 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Mon, 16 Mar 2026 21:49:12 -0500 Subject: [PATCH 04/47] add auth migration --- ...260317024844_AddUserAuthFields.Designer.cs | 53 +++++++++++++++++++ .../20260317024844_AddUserAuthFields.cs | 29 ++++++++++ api/Migrations/AppDbContextModelSnapshot.cs | 4 ++ api/appsettings.json | 7 ++- 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 api/Migrations/20260317024844_AddUserAuthFields.Designer.cs create mode 100644 api/Migrations/20260317024844_AddUserAuthFields.cs diff --git a/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs b/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs new file mode 100644 index 0000000..6a1cde8 --- /dev/null +++ b/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs @@ -0,0 +1,53 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260317024844_AddUserAuthFields")] + partial class AddUserAuthFields + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260317024844_AddUserAuthFields.cs b/api/Migrations/20260317024844_AddUserAuthFields.cs new file mode 100644 index 0000000..4c533f6 --- /dev/null +++ b/api/Migrations/20260317024844_AddUserAuthFields.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class AddUserAuthFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PasswordHash", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PasswordHash", + table: "Users"); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index 112584c..f1b5d4f 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -36,6 +36,10 @@ namespace agologum_api.Migrations .IsRequired() .HasColumnType("text"); + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + b.HasKey("Id"); b.ToTable("Users"); diff --git a/api/appsettings.json b/api/appsettings.json index 3bbbad3..1ee7388 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -9,5 +9,10 @@ "DefaultConnection": "Host=agologum-net;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD}" }, "AllowedHosts": "*", - "https_port": 443 + "https_port": 443, + "Jwt": { + "Key": "local_secret", + "Issuer": "agologum-api", + "Audience": "agologum-users" + } } From 661bb03d1d9faf1c577fd64d6d54e38ab22e9cac Mon Sep 17 00:00:00 2001 From: Blitblank Date: Tue, 17 Mar 2026 22:30:59 -0500 Subject: [PATCH 05/47] preliminary frontend for ther auth api --- api/src/Models/User.cs | 2 ++ client/src/api/UsersApi.ts | 9 +------- client/src/models/User.ts | 3 ++- client/src/pages/UserForm.vue | 3 ++- client/src/pages/UsersList.vue | 9 ++++++++ client/src/router/index.ts | 39 ++++++++++++++++++++++++++++----- client/src/stores/UsersStore.ts | 10 ++++----- 7 files changed, 54 insertions(+), 21 deletions(-) diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index 17d0363..2a44e84 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -7,5 +7,7 @@ public class User { public string Name { get; set; } = ""; public string Email { get; set; } = ""; public string PasswordHash { get; set; } = ""; + public string Role { get; set; } + public DateTime CreatedAt { get; set; } }; diff --git a/client/src/api/UsersApi.ts b/client/src/api/UsersApi.ts index 150a63a..608b879 100644 --- a/client/src/api/UsersApi.ts +++ b/client/src/api/UsersApi.ts @@ -1,19 +1,12 @@ // services are kinda whatever, but in general its a good idea for all api calls to be within a service (at least thats how angular handles it) // this user service will handle all to <-> from the server when handling user objects -// should be injected with the http client (I think its axios ?) -import axios from "axios"; -import type {AxiosResponse } from "axios"; +import api from "./axios.ts" import type { User } from "../models/User.ts"; const API_URL: string = "/users"; -const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api" // TODO: overarching api service -const api = axios.create({ - baseURL: baseUrl -}); - export const getUsers = () => api.get(`${API_URL}`); export const getUser = (id: number) => api.get(`${API_URL}/${id}`); diff --git a/client/src/models/User.ts b/client/src/models/User.ts index 03387ef..a5e3504 100644 --- a/client/src/models/User.ts +++ b/client/src/models/User.ts @@ -4,5 +4,6 @@ export interface User { id: number; name: string; - email: string + email: string; + password: string; } diff --git a/client/src/pages/UserForm.vue b/client/src/pages/UserForm.vue index 96510d3..75079bf 100644 --- a/client/src/pages/UserForm.vue +++ b/client/src/pages/UserForm.vue @@ -13,9 +13,10 @@ const route = useRoute(); const router = useRouter(); const user = ref({ - name: "", id: 0, + name: "", email: "", + password: "" }); const id: string | undefined = route.params.id as string | undefined diff --git a/client/src/pages/UsersList.vue b/client/src/pages/UsersList.vue index 0fb4214..342b4b9 100644 --- a/client/src/pages/UsersList.vue +++ b/client/src/pages/UsersList.vue @@ -2,14 +2,22 @@ \ No newline at end of file diff --git a/client/src/router/index.ts b/client/src/router/index.ts index e96dc27..dd201b8 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -2,21 +2,48 @@ // the router creates front-end endpoints and serves pages to them import { createRouter, createWebHistory } from "vue-router"; +import LoginForm from "../pages/LoginForm.vue"; +import RegisterForm from "../pages/RegisterForm.vue"; import UsersList from "../pages/UsersList.vue"; import UserForm from "../pages/UserForm.vue"; import index from "../pages/index.vue"; // link path to the page component const routes = [ - { path: "/", component: index }, - { path: "/users", component: UsersList }, - { path: "/user/new", component: UserForm }, - { path: "/user/:id", component: UserForm } + { path: "/", component: index }, + { path: "/login", component: LoginForm }, + { path: "/register", component: RegisterForm }, + { path: "/users", component: UsersList }, + { path: "/user/new", component: UserForm, meta: { requiresAuth: true } }, + { path: "/user/:id", component: UserForm, meta: { requiresAuth: true } } ]; // I really like this const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: routes, + history: createWebHistory(import.meta.env.BASE_URL), + routes: routes, +}); + +// intercept before routing +router.beforeEach((to, from, next) => { + + const token = localStorage.getItem("token"); + if(to.meta.requiresAuth && !token) { // if the page requires use to be signed in, they must have at least a token set + next("/login"); + } else { + next(); + } + // TODO: if they have a token, but invalid, it will still send them to the page (the api will catch non-authorized though) + // maybe have a "validate token" from the api and refresh it if valid + /* + } else { + bool authorizedUser = authApi.refreshToken(token); + if(authorizedUser) { + next(); + } else { + next("/login"); + } + } + */ }); export default router; diff --git a/client/src/stores/UsersStore.ts b/client/src/stores/UsersStore.ts index a4d4b01..1d78fb0 100644 --- a/client/src/stores/UsersStore.ts +++ b/client/src/stores/UsersStore.ts @@ -6,7 +6,7 @@ import { defineStore } from "pinia"; import type { User } from "../models/User.ts"; -import * as api from "../api/UsersApi"; +import * as usersApi from "../api/UsersApi"; interface UserState { users: User[]; @@ -23,24 +23,24 @@ export const useUsersStore = defineStore("users", { actions: { async fetchUsers() { this.loading = true; - const response = await api.getUsers(); + const response = await usersApi.getUsers(); this.users = response.data; this.loading = false; }, async addUser(user: User) { - const response = await api.createUser(user); + const response = await usersApi.createUser(user); this.users.push(response.data); }, async updateUser(id: number, user: User) { - await api.updateUser(id, user); + await usersApi.updateUser(id, user); const index = this.users.findIndex(i => i.id === id); this.users[index] = user; }, async removeUser(id: number) { - await api.deleteUser(id); + await usersApi.deleteUser(id); this.users = this.users.filter(i => i.id !== id); } } From 8c86f5ddcec0a13d638c0239d05517b2dbaaa637 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Tue, 17 Mar 2026 22:31:13 -0500 Subject: [PATCH 06/47] literally every time --- client/src/api/AuthApi.ts | 49 ++++++++++++++++++++++++++++++ client/src/api/axios.ts | 24 +++++++++++++++ client/src/pages/LoginForm.vue | 47 +++++++++++++++++++++++++++++ client/src/pages/RegisterForm.vue | 50 +++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 client/src/api/AuthApi.ts create mode 100644 client/src/api/axios.ts create mode 100644 client/src/pages/LoginForm.vue create mode 100644 client/src/pages/RegisterForm.vue diff --git a/client/src/api/AuthApi.ts b/client/src/api/AuthApi.ts new file mode 100644 index 0000000..8ed5b4c --- /dev/null +++ b/client/src/api/AuthApi.ts @@ -0,0 +1,49 @@ + +// service to interact with the api/auth endpoints +// handles user registration, user logins, tokens, password reset, etc. + +import api from "./axios.ts" +import type { User } from "../models/User.ts"; + +const API_URL: string = "/auth"; + +export const register = async (user: { username: string; email: string; password: string }) => { + + try { + const response = await api.post(`${API_URL}/register`, user); + + // TODO: if valid + + return true; + + // else return false + + } catch (err) { + return false; + } + +} + +export const login = async (user: { username: string; password: string }) => { + + try { + const response = await api.post(`${API_URL}/login`, user); + const token = response.data.token; + + localStorage.setItem("token", token); + + return true; + + } catch (err) { + return false; + } + +} + +export const logout = () => { + localStorage.removeItem("token"); +} + +export const getToken = () => { + return localStorage.getItem("token"); +} diff --git a/client/src/api/axios.ts b/client/src/api/axios.ts new file mode 100644 index 0000000..496a119 --- /dev/null +++ b/client/src/api/axios.ts @@ -0,0 +1,24 @@ + +// http service hub +// handles interceptors and such + +import axios from "axios"; + +const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api" +const api = axios.create({ + baseURL: baseUrl +}); + +api.interceptors.request.use(config => { + + const token = localStorage.getItem("token"); + + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + return config; + +}); + +export default api; diff --git a/client/src/pages/LoginForm.vue b/client/src/pages/LoginForm.vue new file mode 100644 index 0000000..ce652f3 --- /dev/null +++ b/client/src/pages/LoginForm.vue @@ -0,0 +1,47 @@ + + + + \ No newline at end of file diff --git a/client/src/pages/RegisterForm.vue b/client/src/pages/RegisterForm.vue new file mode 100644 index 0000000..179e7fb --- /dev/null +++ b/client/src/pages/RegisterForm.vue @@ -0,0 +1,50 @@ + + + + \ No newline at end of file From 74200c457563d816b27bd675ef0bea9673b33481 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Tue, 17 Mar 2026 22:40:22 -0500 Subject: [PATCH 07/47] add the login buttons --- client/src/pages/index.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/pages/index.vue b/client/src/pages/index.vue index 4e9fc51..10d5615 100644 --- a/client/src/pages/index.vue +++ b/client/src/pages/index.vue @@ -13,4 +13,12 @@ + + + + + + + + From ce701cd43dce045c1c19bd4d8c885b157f8bd416 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Tue, 17 Mar 2026 22:41:42 -0500 Subject: [PATCH 08/47] =?UTF-8?q?(=20=CB=98=EF=B8=B9=CB=98=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/index.vue b/client/src/pages/index.vue index 10d5615..1cd6f5a 100644 --- a/client/src/pages/index.vue +++ b/client/src/pages/index.vue @@ -14,11 +14,11 @@ - + - + From 21fad5f698fb06e1b4b661128277b40f61489a0c Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 20:02:51 -0500 Subject: [PATCH 09/47] new migration --- ...0321010235_CreateExtraUserInfo.Designer.cs | 61 +++++++++++++++++++ .../20260321010235_CreateExtraUserInfo.cs | 41 +++++++++++++ api/Migrations/AppDbContextModelSnapshot.cs | 8 +++ 3 files changed, 110 insertions(+) create mode 100644 api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs create mode 100644 api/Migrations/20260321010235_CreateExtraUserInfo.cs diff --git a/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs b/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs new file mode 100644 index 0000000..6689bac --- /dev/null +++ b/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs @@ -0,0 +1,61 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260321010235_CreateExtraUserInfo")] + partial class CreateExtraUserInfo + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260321010235_CreateExtraUserInfo.cs b/api/Migrations/20260321010235_CreateExtraUserInfo.cs new file mode 100644 index 0000000..07f3d06 --- /dev/null +++ b/api/Migrations/20260321010235_CreateExtraUserInfo.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class CreateExtraUserInfo : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Users", + type: "timestamp with time zone", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "Role", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Role", + table: "Users"); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index f1b5d4f..472f8c1 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -1,4 +1,5 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -28,6 +29,9 @@ namespace agologum_api.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + b.Property("Email") .IsRequired() .HasColumnType("text"); @@ -40,6 +44,10 @@ namespace agologum_api.Migrations .IsRequired() .HasColumnType("text"); + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + b.HasKey("Id"); b.ToTable("Users"); From ae888d29739a365152891863e8b1c550943a62d2 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 20:07:25 -0500 Subject: [PATCH 10/47] fix non-null register attributes --- api/src/Controllers/AuthController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index 0b53759..e38f04b 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -22,7 +22,10 @@ public class AuthController : ControllerBase { public async Task Register(RegisterDto dto) { var user = new User { Name = dto.Username, - PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password) // TODO: hashing stage in client + Email = "dummy@goofy.xyz", + PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password), // TODO: secondary hashing stage in client + Role = "admin (FOR NOW !!)", + CreatedAt = DateTime.UtcNow // yeah why not utc }; var newUser = await users_.Create(user); From a343c2e246b24605698c53186833c4be3ccf4c4b Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 20:11:58 -0500 Subject: [PATCH 11/47] test longer jwt secret --- api/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/appsettings.json b/api/appsettings.json index 1ee7388..f4a8f74 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -11,7 +11,7 @@ "AllowedHosts": "*", "https_port": 443, "Jwt": { - "Key": "local_secret", + "Key": "local_secret_but_longer_this_time_so_it_actually_works", "Issuer": "agologum-api", "Audience": "agologum-users" } From 7229e369ae05185cbd5bf4df3f530fe0ae2a0870 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 20:52:59 -0500 Subject: [PATCH 12/47] secretize jwt secret --- .gitea/workflows/deploy-api.yaml | 1 + api/appsettings.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy-api.yaml b/.gitea/workflows/deploy-api.yaml index 6dc4943..39d0f84 100644 --- a/.gitea/workflows/deploy-api.yaml +++ b/.gitea/workflows/deploy-api.yaml @@ -39,5 +39,6 @@ jobs: - name: Deploy container run: | export POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} + export JWT_SECRET=${{ secrets.JWT_SECRET }} docker compose -f ./api/docker-compose.prod.yaml pull agologum-api docker compose -f ./api/docker-compose.prod.yaml up -d --force-recreate agologum-api diff --git a/api/appsettings.json b/api/appsettings.json index f4a8f74..c34474e 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -11,7 +11,7 @@ "AllowedHosts": "*", "https_port": 443, "Jwt": { - "Key": "local_secret_but_longer_this_time_so_it_actually_works", + "Key": "${JWT_SECRET}", "Issuer": "agologum-api", "Audience": "agologum-users" } From 109d5f88ea480175044f848eed7d57242b203ea1 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 21:08:32 -0500 Subject: [PATCH 13/47] user dtos as strict object templates --- api/src/Controllers/AuthController.cs | 4 ++-- api/src/Models/DTO.cs | 15 --------------- api/src/Models/User.cs | 2 +- client/src/api/AuthApi.ts | 6 +++--- client/src/models/User.ts | 12 ++++++++++++ client/src/pages/LoginForm.vue | 9 +++++---- client/src/pages/RegisterForm.vue | 9 +++++---- 7 files changed, 28 insertions(+), 29 deletions(-) delete mode 100644 api/src/Models/DTO.cs diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index e38f04b..6cb3db8 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -22,9 +22,9 @@ public class AuthController : ControllerBase { public async Task Register(RegisterDto dto) { var user = new User { Name = dto.Username, - Email = "dummy@goofy.xyz", + Email = dto.Email, PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password), // TODO: secondary hashing stage in client - Role = "admin (FOR NOW !!)", + Role = "user", CreatedAt = DateTime.UtcNow // yeah why not utc }; diff --git a/api/src/Models/DTO.cs b/api/src/Models/DTO.cs deleted file mode 100644 index ea508f6..0000000 --- a/api/src/Models/DTO.cs +++ /dev/null @@ -1,15 +0,0 @@ - -public class RegisterDto { - - public string Username { get; set; } = ""; - public string Email { get; set; } = ""; - public string Password { get; set; } = ""; - -} - -public class LoginDto { - - public string Username { get; set; } = ""; - public string Password { get; set; } = ""; - -} diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index 2a44e84..ab3618a 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -7,7 +7,7 @@ public class User { public string Name { get; set; } = ""; public string Email { get; set; } = ""; public string PasswordHash { get; set; } = ""; - public string Role { get; set; } + public string Role { get; set; } = ""; public DateTime CreatedAt { get; set; } }; diff --git a/client/src/api/AuthApi.ts b/client/src/api/AuthApi.ts index 8ed5b4c..3fac7ec 100644 --- a/client/src/api/AuthApi.ts +++ b/client/src/api/AuthApi.ts @@ -3,11 +3,11 @@ // handles user registration, user logins, tokens, password reset, etc. import api from "./axios.ts" -import type { User } from "../models/User.ts"; +import type { User, RegisterDto, LoginDto } from "../models/User.ts"; const API_URL: string = "/auth"; -export const register = async (user: { username: string; email: string; password: string }) => { +export const register = async (user: RegisterDto) => { try { const response = await api.post(`${API_URL}/register`, user); @@ -24,7 +24,7 @@ export const register = async (user: { username: string; email: string; password } -export const login = async (user: { username: string; password: string }) => { +export const login = async (user: LoginDto ) => { try { const response = await api.post(`${API_URL}/login`, user); diff --git a/client/src/models/User.ts b/client/src/models/User.ts index a5e3504..6bc0c95 100644 --- a/client/src/models/User.ts +++ b/client/src/models/User.ts @@ -1,5 +1,6 @@ // models are the data objects stored in the database. models defined here must match models defined in api/models +// dtos here must match the the dtos in api/src/Modelts/Dto.cs in name (case insensitive) (types are intermediately serialized to strings) export interface User { id: number; @@ -7,3 +8,14 @@ export interface User { email: string; password: string; } + +export interface RegisterDto { + name: string; + email: string; + password: string; +} + +export interface LoginDto { + name: string; + password: string; +} diff --git a/client/src/pages/LoginForm.vue b/client/src/pages/LoginForm.vue index ce652f3..7f79c15 100644 --- a/client/src/pages/LoginForm.vue +++ b/client/src/pages/LoginForm.vue @@ -4,13 +4,14 @@ import { onMounted, reactive } from "vue"; import { useRoute, useRouter } from "vue-router"; +import type { LoginDto } from "../models/User.ts"; import * as authApi from "../api/AuthApi"; const router = useRouter(); -const user = reactive({ - username: "", - password: "" +const user = reactive({ // the template ensures type consistency + name: "", + password: "", }); onMounted(() => { @@ -37,7 +38,7 @@ async function login(): Promise {

Login

- + diff --git a/client/src/pages/RegisterForm.vue b/client/src/pages/RegisterForm.vue index 179e7fb..97a904e 100644 --- a/client/src/pages/RegisterForm.vue +++ b/client/src/pages/RegisterForm.vue @@ -4,14 +4,15 @@ import { onMounted, reactive } from "vue"; import { useRoute, useRouter } from "vue-router"; +import type { RegisterDto } from "../models/User.ts"; import * as authApi from "../api/AuthApi"; const router = useRouter(); -const user = reactive({ - username: "", +const user = reactive({ // the template ensures type consistency + name: "", email: "", - password: "" + password: "", }); onMounted(() => { @@ -39,7 +40,7 @@ async function register(): Promise {

Register

- + From f7b537cbedb26356855e8794a22eef945829e64d Mon Sep 17 00:00:00 2001 From: Blitblank Date: Fri, 20 Mar 2026 21:09:16 -0500 Subject: [PATCH 14/47] i changed a filename oops --- api/src/Models/Dto.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 api/src/Models/Dto.cs diff --git a/api/src/Models/Dto.cs b/api/src/Models/Dto.cs new file mode 100644 index 0000000..ea508f6 --- /dev/null +++ b/api/src/Models/Dto.cs @@ -0,0 +1,15 @@ + +public class RegisterDto { + + public string Username { get; set; } = ""; + public string Email { get; set; } = ""; + public string Password { get; set; } = ""; + +} + +public class LoginDto { + + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; + +} From 9fea2c5b7b2adf0819d91bbde5bb4f6500aef1d2 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 00:16:52 -0500 Subject: [PATCH 15/47] properly set environment secret --- .gitea/workflows/deploy-api.yaml | 1 - api/appsettings.json | 2 +- api/docker-compose.prod.yaml | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy-api.yaml b/.gitea/workflows/deploy-api.yaml index 39d0f84..6dc4943 100644 --- a/.gitea/workflows/deploy-api.yaml +++ b/.gitea/workflows/deploy-api.yaml @@ -39,6 +39,5 @@ jobs: - name: Deploy container run: | export POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} - export JWT_SECRET=${{ secrets.JWT_SECRET }} docker compose -f ./api/docker-compose.prod.yaml pull agologum-api docker compose -f ./api/docker-compose.prod.yaml up -d --force-recreate agologum-api diff --git a/api/appsettings.json b/api/appsettings.json index c34474e..12184eb 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -11,7 +11,7 @@ "AllowedHosts": "*", "https_port": 443, "Jwt": { - "Key": "${JWT_SECRET}", + "Key": "", "Issuer": "agologum-api", "Audience": "agologum-users" } diff --git a/api/docker-compose.prod.yaml b/api/docker-compose.prod.yaml index 0a86a5f..4c96763 100644 --- a/api/docker-compose.prod.yaml +++ b/api/docker-compose.prod.yaml @@ -12,6 +12,8 @@ services: - "5000:5000" networks: - agologum-net + environment: + - Jwt__Key=${JWT_SECRET} # this seems to be the right way to pass env variables with secrets networks: agologum-net: From be731be7241d19fa926749bd4e6df927eba96539 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 00:20:25 -0500 Subject: [PATCH 16/47] yaml syuntax --- api/docker-compose.prod.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/docker-compose.prod.yaml b/api/docker-compose.prod.yaml index 4c96763..e6d658b 100644 --- a/api/docker-compose.prod.yaml +++ b/api/docker-compose.prod.yaml @@ -7,13 +7,12 @@ services: container_name: agologum-api restart: always environment: - ConnectionStrings__DefaultConnection: Host=agologum-db;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD} + - ConnectionStrings__DefaultConnection: Host=agologum-db;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD} + - Jwt__Key: ${JWT_SECRET} ports: - "5000:5000" networks: - agologum-net - environment: - - Jwt__Key=${JWT_SECRET} # this seems to be the right way to pass env variables with secrets networks: agologum-net: From cdc8b5c7a4df2863f3663cfca8a6d0aa7f1a8969 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 00:22:31 -0500 Subject: [PATCH 17/47] export secret --- .gitea/workflows/deploy-api.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/deploy-api.yaml b/.gitea/workflows/deploy-api.yaml index 6dc4943..39d0f84 100644 --- a/.gitea/workflows/deploy-api.yaml +++ b/.gitea/workflows/deploy-api.yaml @@ -39,5 +39,6 @@ jobs: - name: Deploy container run: | export POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} + export JWT_SECRET=${{ secrets.JWT_SECRET }} docker compose -f ./api/docker-compose.prod.yaml pull agologum-api docker compose -f ./api/docker-compose.prod.yaml up -d --force-recreate agologum-api From 817e0b97e621516ad0b8c6efa30b35ed4942f078 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 00:23:30 -0500 Subject: [PATCH 18/47] idk how yaml works apparently --- api/docker-compose.prod.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/docker-compose.prod.yaml b/api/docker-compose.prod.yaml index e6d658b..2c2189c 100644 --- a/api/docker-compose.prod.yaml +++ b/api/docker-compose.prod.yaml @@ -7,8 +7,8 @@ services: container_name: agologum-api restart: always environment: - - ConnectionStrings__DefaultConnection: Host=agologum-db;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD} - - Jwt__Key: ${JWT_SECRET} + ConnectionStrings__DefaultConnection: Host=agologum-db;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD} + Jwt__Key: ${JWT_SECRET} ports: - "5000:5000" networks: From ef4f0c01596f18751e3ecc8e16bbbc1f8c712775 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 14:13:19 -0500 Subject: [PATCH 19/47] fix: dto consistency --- api/docker-compose.prod.yaml | 2 +- api/src/Models/Dto.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/docker-compose.prod.yaml b/api/docker-compose.prod.yaml index 2c2189c..a5e4db6 100644 --- a/api/docker-compose.prod.yaml +++ b/api/docker-compose.prod.yaml @@ -8,7 +8,7 @@ services: restart: always environment: ConnectionStrings__DefaultConnection: Host=agologum-db;Port=5432;Database=agologum;Username=agologum;Password=${POSTGRES_PASSWORD} - Jwt__Key: ${JWT_SECRET} + Jwt__Key: ${JWT_SECRET} # must export the secret as a variable in the ci script ports: - "5000:5000" networks: diff --git a/api/src/Models/Dto.cs b/api/src/Models/Dto.cs index ea508f6..7403c6f 100644 --- a/api/src/Models/Dto.cs +++ b/api/src/Models/Dto.cs @@ -1,7 +1,7 @@ public class RegisterDto { - public string Username { get; set; } = ""; + public string Name { get; set; } = ""; public string Email { get; set; } = ""; public string Password { get; set; } = ""; @@ -9,7 +9,7 @@ public class RegisterDto { public class LoginDto { - public string Username { get; set; } = ""; + public string Name { get; set; } = ""; public string Password { get; set; } = ""; } From 3dd0460209703b796f6c243017b6b620366fe86e Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 15:38:05 -0500 Subject: [PATCH 20/47] migrate to identity for authentication --- ...0321203739_UseIdentityPlatform.Designer.cs | 84 +++++++ .../20260321203739_UseIdentityPlatform.cs | 221 ++++++++++++++++++ api/Migrations/AppDbContextModelSnapshot.cs | 41 +++- api/Program.cs | 12 +- api/agologum-api.csproj | 1 + api/src/Controllers/AuthController.cs | 24 +- api/src/Models/Dto.cs | 4 +- api/src/Models/User.cs | 31 ++- api/src/Services/JwtService.cs | 6 +- api/src/Services/UserService.cs | 2 +- 10 files changed, 397 insertions(+), 29 deletions(-) create mode 100644 api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs create mode 100644 api/Migrations/20260321203739_UseIdentityPlatform.cs diff --git a/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs b/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs new file mode 100644 index 0000000..7c70f29 --- /dev/null +++ b/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs @@ -0,0 +1,84 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260321203739_UseIdentityPlatform")] + partial class UseIdentityPlatform + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260321203739_UseIdentityPlatform.cs b/api/Migrations/20260321203739_UseIdentityPlatform.cs new file mode 100644 index 0000000..6f022b9 --- /dev/null +++ b/api/Migrations/20260321203739_UseIdentityPlatform.cs @@ -0,0 +1,221 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class UseIdentityPlatform : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Name", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Role", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Users", + type: "text", + nullable: false, + oldClrType: typeof(int), + oldType: "integer") + .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + migrationBuilder.AddColumn( + name: "AccessFailedCount", + table: "Users", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ConcurrencyStamp", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "EmailConfirmed", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "LockoutEnabled", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "LockoutEnd", + table: "Users", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "NormalizedEmail", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "NormalizedUserName", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "PhoneNumber", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "PhoneNumberConfirmed", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "SecurityStamp", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "TwoFactorEnabled", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "UserName", + table: "Users", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AccessFailedCount", + table: "Users"); + + migrationBuilder.DropColumn( + name: "ConcurrencyStamp", + table: "Users"); + + migrationBuilder.DropColumn( + name: "EmailConfirmed", + table: "Users"); + + migrationBuilder.DropColumn( + name: "LockoutEnabled", + table: "Users"); + + migrationBuilder.DropColumn( + name: "LockoutEnd", + table: "Users"); + + migrationBuilder.DropColumn( + name: "NormalizedEmail", + table: "Users"); + + migrationBuilder.DropColumn( + name: "NormalizedUserName", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PhoneNumber", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PhoneNumberConfirmed", + table: "Users"); + + migrationBuilder.DropColumn( + name: "SecurityStamp", + table: "Users"); + + migrationBuilder.DropColumn( + name: "TwoFactorEnabled", + table: "Users"); + + migrationBuilder.DropColumn( + name: "UserName", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Users", + type: "integer", + nullable: false, + oldClrType: typeof(string), + oldType: "text") + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + migrationBuilder.AddColumn( + name: "Name", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Role", + table: "Users", + type: "text", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index 472f8c1..b84960f 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -23,29 +23,52 @@ namespace agologum_api.Migrations modelBuilder.Entity("agologumApi.Models.User", b => { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("ConcurrencyStamp") + .HasColumnType("text"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); b.Property("Email") - .IsRequired() .HasColumnType("text"); - b.Property("Name") - .IsRequired() + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") .HasColumnType("text"); b.Property("PasswordHash") - .IsRequired() .HasColumnType("text"); - b.Property("Role") - .IsRequired() + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") .HasColumnType("text"); b.HasKey("Id"); diff --git a/api/Program.cs b/api/Program.cs index a05ead5..fbf5bc5 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -3,8 +3,11 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using System.Text; +using agologumApi.Models; using agologumApi.Services; var builder = WebApplication.CreateBuilder(args); @@ -22,15 +25,20 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); // configuration for jwt authentication +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { - ValidateIssuer = false, - ValidateAudience = false, + ValidateIssuer = true, + ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, + ValidIssuer = "agologum", + ValidAudience = "agologum", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) }; }); diff --git a/api/agologum-api.csproj b/api/agologum-api.csproj index 82bd216..cfe18b4 100644 --- a/api/agologum-api.csproj +++ b/api/agologum-api.csproj @@ -10,6 +10,7 @@ + diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index 6cb3db8..ba2ae21 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using agologumApi.Models; using agologumApi.Services; @@ -9,11 +10,17 @@ using agologumApi.Services; [Route("api/[controller]")] public class AuthController : ControllerBase { + // identity things + private readonly UserManager userManager_; + private readonly SignInManager signInManager_; + private readonly UserService users_; private readonly JwtService jwt_; - public AuthController(UserService users, JwtService jwt) - { + public AuthController(UserManager userManager, SignInManager signInManager, UserService users, JwtService jwt) { + + userManager_ = userManager; + signInManager_ = signInManager; users_ = users; jwt_ = jwt; } @@ -21,10 +28,9 @@ public class AuthController : ControllerBase { [HttpPost("register")] public async Task Register(RegisterDto dto) { var user = new User { - Name = dto.Username, + UserName = dto.UserName, Email = dto.Email, PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password), // TODO: secondary hashing stage in client - Role = "user", CreatedAt = DateTime.UtcNow // yeah why not utc }; @@ -39,11 +45,13 @@ public class AuthController : ControllerBase { [HttpPost("login")] public async Task Login(LoginDto dto) { - var user = await users_.Get(dto.Username); + var user = await users_.Get(dto.UserName); - if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.PasswordHash)) { - return Unauthorized(); - } + if (user == null) return Unauthorized(); + + var result = await signInManager_.CheckPasswordSignInAsync(user, dto.Password, false); + + if(!result.Succeeded) return Unauthorized(); var token = jwt_.GenerateJwt(user); diff --git a/api/src/Models/Dto.cs b/api/src/Models/Dto.cs index 7403c6f..a740896 100644 --- a/api/src/Models/Dto.cs +++ b/api/src/Models/Dto.cs @@ -1,7 +1,7 @@ public class RegisterDto { - public string Name { get; set; } = ""; + public string UserName { get; set; } = ""; public string Email { get; set; } = ""; public string Password { get; set; } = ""; @@ -9,7 +9,7 @@ public class RegisterDto { public class LoginDto { - public string Name { get; set; } = ""; + public string UserName { get; set; } = ""; public string Password { get; set; } = ""; } diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index ab3618a..2488222 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -1,13 +1,32 @@ +using Microsoft.AspNetCore.Identity; + namespace agologumApi.Models; -public class User { +public class User : IdentityUser { - public int Id { get; set; } - public string Name { get; set; } = ""; - public string Email { get; set; } = ""; - public string PasswordHash { get; set; } = ""; - public string Role { get; set; } = ""; public DateTime CreatedAt { get; set; } + // properties inherited from IdentityUser: + /* + AccessFailedCount: Gets or sets the number of failed login attempts for the current user. + Claims: Navigation property for the claims this user possesses. + ConcurrencyStamp: A random value that must change whenever a user is persisted to the store + Email: Gets or sets the email address for this user. + EmailConfirmed: Gets or sets a flag indicating if a user has confirmed their email address. + Id: Gets or sets the primary key for this user. + LockoutEnabled: Gets or sets a flag indicating if the user could be locked out. + LockoutEnd: Gets or sets the date and time, in UTC, when any user lockout ends. + Logins: Navigation property for this users login accounts. + NormalizedEmail: Gets or sets the normalized email address for this user. + NormalizedUserName: Gets or sets the normalized user name for this user. + PasswordHash: Gets or sets a salted and hashed representation of the password for this user. + PhoneNumber: Gets or sets a telephone number for the user. + PhoneNumberConfirmed: Gets or sets a flag indicating if a user has confirmed their telephone address. + Roles: Navigation property for the roles this user belongs to. + SecurityStamp: A random value that must change whenever a users credentials change (password changed, login removed) + TwoFactorEnabled: Gets or sets a flag indicating if two factor authentication is enabled for this user. + UserName: Gets or sets the user name for this user. + https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.entityframeworkcore.identityuser?view=aspnetcore-1.1 + */ }; diff --git a/api/src/Services/JwtService.cs b/api/src/Services/JwtService.cs index 14e0b69..a79188e 100644 --- a/api/src/Services/JwtService.cs +++ b/api/src/Services/JwtService.cs @@ -22,13 +22,17 @@ public class JwtService { var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + if(user.UserName == null) return null; + // not too sure var claims = new[] { - new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; var token = new JwtSecurityToken( + issuer: "agologum", + audience: "agologum", claims: claims, expires: DateTime.UtcNow.AddHours(2), // will add a refresher later signingCredentials: creds diff --git a/api/src/Services/UserService.cs b/api/src/Services/UserService.cs index 1f9f247..1e6c104 100644 --- a/api/src/Services/UserService.cs +++ b/api/src/Services/UserService.cs @@ -22,7 +22,7 @@ public class UserService { } public async Task Get(string username) { - return await db_.Users.FirstOrDefaultAsync(u => u.Name == username); + return await db_.Users.FirstOrDefaultAsync(u => u.UserName == username); } public async Task Create(User user) { From 96456955352cf1e71bde115f88fe96eba9b5f19c Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 15:41:54 -0500 Subject: [PATCH 21/47] require auth for users list page because itll be easier to test with --- client/src/router/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/router/index.ts b/client/src/router/index.ts index dd201b8..5c7d3b3 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -13,7 +13,7 @@ const routes = [ { path: "/", component: index }, { path: "/login", component: LoginForm }, { path: "/register", component: RegisterForm }, - { path: "/users", component: UsersList }, + { path: "/users", component: UsersList, { requiresAuth: true } }, { path: "/user/new", component: UserForm, meta: { requiresAuth: true } }, { path: "/user/:id", component: UserForm, meta: { requiresAuth: true } } ]; // I really like this From efde701ba9924ac42a46c84c3ca52fa6b0aa19bd Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 15:44:35 -0500 Subject: [PATCH 22/47] i should really test lolcally --- client/src/router/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 5c7d3b3..e80ac1c 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -13,7 +13,7 @@ const routes = [ { path: "/", component: index }, { path: "/login", component: LoginForm }, { path: "/register", component: RegisterForm }, - { path: "/users", component: UsersList, { requiresAuth: true } }, + { path: "/users", component: UsersList, meta: { requiresAuth: true } }, { path: "/user/new", component: UserForm, meta: { requiresAuth: true } }, { path: "/user/:id", component: UserForm, meta: { requiresAuth: true } } ]; // I really like this From 27f3be7761ba97ef4696a8e35d6e0be09f7c1b61 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 15:58:33 -0500 Subject: [PATCH 23/47] add another migrtation --- ...20260321205823_ContinuityCheck.Designer.cs | 84 +++++++++++++++++++ .../20260321205823_ContinuityCheck.cs | 22 +++++ 2 files changed, 106 insertions(+) create mode 100644 api/Migrations/20260321205823_ContinuityCheck.Designer.cs create mode 100644 api/Migrations/20260321205823_ContinuityCheck.cs diff --git a/api/Migrations/20260321205823_ContinuityCheck.Designer.cs b/api/Migrations/20260321205823_ContinuityCheck.Designer.cs new file mode 100644 index 0000000..60bf322 --- /dev/null +++ b/api/Migrations/20260321205823_ContinuityCheck.Designer.cs @@ -0,0 +1,84 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260321205823_ContinuityCheck")] + partial class ContinuityCheck + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260321205823_ContinuityCheck.cs b/api/Migrations/20260321205823_ContinuityCheck.cs new file mode 100644 index 0000000..5415049 --- /dev/null +++ b/api/Migrations/20260321205823_ContinuityCheck.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class ContinuityCheck : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} From a3a86d4fde9528e038e94f3a1d5f12a9a5aa1e5b Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 16:10:53 -0500 Subject: [PATCH 24/47] fix entityframework identity dbContext --- api/src/Data/AppDbContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/Data/AppDbContext.cs b/api/src/Data/AppDbContext.cs index 49df799..22d1e54 100644 --- a/api/src/Data/AppDbContext.cs +++ b/api/src/Data/AppDbContext.cs @@ -2,12 +2,12 @@ using agologumApi.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -public class AppDbContext : DbContext { +public class AppDbContext : IdentityDbContext { public AppDbContext(DbContextOptions options) : base(options) { } - public DbSet Users { get; set; } } \ No newline at end of file From 826b654dc9e46969d519f25b59a6fda3caf652be Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 16:12:59 -0500 Subject: [PATCH 25/47] add another identity migration --- ...1211248_AddIdentityToDbContext.Designer.cs | 279 ++++++++++++++++ .../20260321211248_AddIdentityToDbContext.cs | 308 ++++++++++++++++++ api/Migrations/AppDbContextModelSnapshot.cs | 205 +++++++++++- 3 files changed, 787 insertions(+), 5 deletions(-) create mode 100644 api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs create mode 100644 api/Migrations/20260321211248_AddIdentityToDbContext.cs diff --git a/api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs b/api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs new file mode 100644 index 0000000..40311ca --- /dev/null +++ b/api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs @@ -0,0 +1,279 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260321211248_AddIdentityToDbContext")] + partial class AddIdentityToDbContext + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260321211248_AddIdentityToDbContext.cs b/api/Migrations/20260321211248_AddIdentityToDbContext.cs new file mode 100644 index 0000000..d0e4d7e --- /dev/null +++ b/api/Migrations/20260321211248_AddIdentityToDbContext.cs @@ -0,0 +1,308 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class AddIdentityToDbContext : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Users", + table: "Users"); + + migrationBuilder.RenameTable( + name: "Users", + newName: "AspNetUsers"); + + migrationBuilder.AlterColumn( + name: "UserName", + table: "AspNetUsers", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedUserName", + table: "AspNetUsers", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "AspNetUsers", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Email", + table: "AspNetUsers", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_AspNetUsers", + table: "AspNetUsers", + column: "Id"); + + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropPrimaryKey( + name: "PK_AspNetUsers", + table: "AspNetUsers"); + + migrationBuilder.DropIndex( + name: "EmailIndex", + table: "AspNetUsers"); + + migrationBuilder.DropIndex( + name: "UserNameIndex", + table: "AspNetUsers"); + + migrationBuilder.RenameTable( + name: "AspNetUsers", + newName: "Users"); + + migrationBuilder.AlterColumn( + name: "UserName", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedUserName", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_Users", + table: "Users", + column: "Id"); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index b84960f..04564b3 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -21,6 +21,138 @@ namespace agologum_api.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + modelBuilder.Entity("agologumApi.Models.User", b => { b.Property("Id") @@ -30,13 +162,15 @@ namespace agologum_api.Migrations .HasColumnType("integer"); b.Property("ConcurrencyStamp") + .IsConcurrencyToken() .HasColumnType("text"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); b.Property("Email") - .HasColumnType("text"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("EmailConfirmed") .HasColumnType("boolean"); @@ -48,10 +182,12 @@ namespace agologum_api.Migrations .HasColumnType("timestamp with time zone"); b.Property("NormalizedEmail") - .HasColumnType("text"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedUserName") - .HasColumnType("text"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("PasswordHash") .HasColumnType("text"); @@ -69,11 +205,70 @@ namespace agologum_api.Migrations .HasColumnType("boolean"); b.Property("UserName") - .HasColumnType("text"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); - b.ToTable("Users"); + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); #pragma warning restore 612, 618 } From 9b4c2062a7853542034518563d23241366c21876 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 17:16:17 -0500 Subject: [PATCH 26/47] revert database --- .../20260314152859_InitialCreate.Designer.cs | 49 ---- .../20260314152859_InitialCreate.cs | 36 --- ...260317024844_AddUserAuthFields.Designer.cs | 53 ---- .../20260317024844_AddUserAuthFields.cs | 29 -- ...0321010235_CreateExtraUserInfo.Designer.cs | 61 ----- .../20260321010235_CreateExtraUserInfo.cs | 41 --- ...0321203739_UseIdentityPlatform.Designer.cs | 84 ------ .../20260321203739_UseIdentityPlatform.cs | 221 ---------------- ...20260321205823_ContinuityCheck.Designer.cs | 84 ------ .../20260321205823_ContinuityCheck.cs | 22 -- ... 20260321221316_InitialCreate.Designer.cs} | 4 +- ...ext.cs => 20260321221316_InitialCreate.cs} | 248 ++++++------------ scripts/notes.txt | 8 + 13 files changed, 92 insertions(+), 848 deletions(-) delete mode 100644 api/Migrations/20260314152859_InitialCreate.Designer.cs delete mode 100644 api/Migrations/20260314152859_InitialCreate.cs delete mode 100644 api/Migrations/20260317024844_AddUserAuthFields.Designer.cs delete mode 100644 api/Migrations/20260317024844_AddUserAuthFields.cs delete mode 100644 api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs delete mode 100644 api/Migrations/20260321010235_CreateExtraUserInfo.cs delete mode 100644 api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs delete mode 100644 api/Migrations/20260321203739_UseIdentityPlatform.cs delete mode 100644 api/Migrations/20260321205823_ContinuityCheck.Designer.cs delete mode 100644 api/Migrations/20260321205823_ContinuityCheck.cs rename api/Migrations/{20260321211248_AddIdentityToDbContext.Designer.cs => 20260321221316_InitialCreate.Designer.cs} (99%) rename api/Migrations/{20260321211248_AddIdentityToDbContext.cs => 20260321221316_InitialCreate.cs} (67%) create mode 100644 scripts/notes.txt diff --git a/api/Migrations/20260314152859_InitialCreate.Designer.cs b/api/Migrations/20260314152859_InitialCreate.Designer.cs deleted file mode 100644 index 7d957ce..0000000 --- a/api/Migrations/20260314152859_InitialCreate.Designer.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260314152859_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("agologumApi.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/api/Migrations/20260314152859_InitialCreate.cs b/api/Migrations/20260314152859_InitialCreate.cs deleted file mode 100644 index 2e0df2d..0000000 --- a/api/Migrations/20260314152859_InitialCreate.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "text", nullable: false), - Email = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Users"); - } - } -} diff --git a/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs b/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs deleted file mode 100644 index 6a1cde8..0000000 --- a/api/Migrations/20260317024844_AddUserAuthFields.Designer.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260317024844_AddUserAuthFields")] - partial class AddUserAuthFields - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("agologumApi.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/api/Migrations/20260317024844_AddUserAuthFields.cs b/api/Migrations/20260317024844_AddUserAuthFields.cs deleted file mode 100644 index 4c533f6..0000000 --- a/api/Migrations/20260317024844_AddUserAuthFields.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace agologum_api.Migrations -{ - /// - public partial class AddUserAuthFields : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PasswordHash", - table: "Users", - type: "text", - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PasswordHash", - table: "Users"); - } - } -} diff --git a/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs b/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs deleted file mode 100644 index 6689bac..0000000 --- a/api/Migrations/20260321010235_CreateExtraUserInfo.Designer.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260321010235_CreateExtraUserInfo")] - partial class CreateExtraUserInfo - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("agologumApi.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/api/Migrations/20260321010235_CreateExtraUserInfo.cs b/api/Migrations/20260321010235_CreateExtraUserInfo.cs deleted file mode 100644 index 07f3d06..0000000 --- a/api/Migrations/20260321010235_CreateExtraUserInfo.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace agologum_api.Migrations -{ - /// - public partial class CreateExtraUserInfo : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CreatedAt", - table: "Users", - type: "timestamp with time zone", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - - migrationBuilder.AddColumn( - name: "Role", - table: "Users", - type: "text", - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CreatedAt", - table: "Users"); - - migrationBuilder.DropColumn( - name: "Role", - table: "Users"); - } - } -} diff --git a/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs b/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs deleted file mode 100644 index 7c70f29..0000000 --- a/api/Migrations/20260321203739_UseIdentityPlatform.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260321203739_UseIdentityPlatform")] - partial class UseIdentityPlatform - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("agologumApi.Models.User", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasColumnType("text"); - - b.Property("NormalizedUserName") - .HasColumnType("text"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/api/Migrations/20260321203739_UseIdentityPlatform.cs b/api/Migrations/20260321203739_UseIdentityPlatform.cs deleted file mode 100644 index 6f022b9..0000000 --- a/api/Migrations/20260321203739_UseIdentityPlatform.cs +++ /dev/null @@ -1,221 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - /// - public partial class UseIdentityPlatform : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Name", - table: "Users"); - - migrationBuilder.DropColumn( - name: "Role", - table: "Users"); - - migrationBuilder.AlterColumn( - name: "PasswordHash", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Email", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Users", - type: "text", - nullable: false, - oldClrType: typeof(int), - oldType: "integer") - .OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - migrationBuilder.AddColumn( - name: "AccessFailedCount", - table: "Users", - type: "integer", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "ConcurrencyStamp", - table: "Users", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "EmailConfirmed", - table: "Users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "LockoutEnabled", - table: "Users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "LockoutEnd", - table: "Users", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "NormalizedEmail", - table: "Users", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "NormalizedUserName", - table: "Users", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "PhoneNumber", - table: "Users", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "PhoneNumberConfirmed", - table: "Users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "SecurityStamp", - table: "Users", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "TwoFactorEnabled", - table: "Users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "UserName", - table: "Users", - type: "text", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "AccessFailedCount", - table: "Users"); - - migrationBuilder.DropColumn( - name: "ConcurrencyStamp", - table: "Users"); - - migrationBuilder.DropColumn( - name: "EmailConfirmed", - table: "Users"); - - migrationBuilder.DropColumn( - name: "LockoutEnabled", - table: "Users"); - - migrationBuilder.DropColumn( - name: "LockoutEnd", - table: "Users"); - - migrationBuilder.DropColumn( - name: "NormalizedEmail", - table: "Users"); - - migrationBuilder.DropColumn( - name: "NormalizedUserName", - table: "Users"); - - migrationBuilder.DropColumn( - name: "PhoneNumber", - table: "Users"); - - migrationBuilder.DropColumn( - name: "PhoneNumberConfirmed", - table: "Users"); - - migrationBuilder.DropColumn( - name: "SecurityStamp", - table: "Users"); - - migrationBuilder.DropColumn( - name: "TwoFactorEnabled", - table: "Users"); - - migrationBuilder.DropColumn( - name: "UserName", - table: "Users"); - - migrationBuilder.AlterColumn( - name: "PasswordHash", - table: "Users", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Email", - table: "Users", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Users", - type: "integer", - nullable: false, - oldClrType: typeof(string), - oldType: "text") - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - migrationBuilder.AddColumn( - name: "Name", - table: "Users", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "Role", - table: "Users", - type: "text", - nullable: false, - defaultValue: ""); - } - } -} diff --git a/api/Migrations/20260321205823_ContinuityCheck.Designer.cs b/api/Migrations/20260321205823_ContinuityCheck.Designer.cs deleted file mode 100644 index 60bf322..0000000 --- a/api/Migrations/20260321205823_ContinuityCheck.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace agologum_api.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260321205823_ContinuityCheck")] - partial class ContinuityCheck - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("agologumApi.Models.User", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasColumnType("text"); - - b.Property("NormalizedUserName") - .HasColumnType("text"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/api/Migrations/20260321205823_ContinuityCheck.cs b/api/Migrations/20260321205823_ContinuityCheck.cs deleted file mode 100644 index 5415049..0000000 --- a/api/Migrations/20260321205823_ContinuityCheck.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace agologum_api.Migrations -{ - /// - public partial class ContinuityCheck : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs b/api/Migrations/20260321221316_InitialCreate.Designer.cs similarity index 99% rename from api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs rename to api/Migrations/20260321221316_InitialCreate.Designer.cs index 40311ca..1008d5d 100644 --- a/api/Migrations/20260321211248_AddIdentityToDbContext.Designer.cs +++ b/api/Migrations/20260321221316_InitialCreate.Designer.cs @@ -11,8 +11,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace agologum_api.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20260321211248_AddIdentityToDbContext")] - partial class AddIdentityToDbContext + [Migration("20260321221316_InitialCreate")] + partial class InitialCreate { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/api/Migrations/20260321211248_AddIdentityToDbContext.cs b/api/Migrations/20260321221316_InitialCreate.cs similarity index 67% rename from api/Migrations/20260321211248_AddIdentityToDbContext.cs rename to api/Migrations/20260321221316_InitialCreate.cs index d0e4d7e..da54a51 100644 --- a/api/Migrations/20260321211248_AddIdentityToDbContext.cs +++ b/api/Migrations/20260321221316_InitialCreate.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -6,64 +7,11 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace agologum_api.Migrations { /// - public partial class AddIdentityToDbContext : Migration + public partial class InitialCreate : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropPrimaryKey( - name: "PK_Users", - table: "Users"); - - migrationBuilder.RenameTable( - name: "Users", - newName: "AspNetUsers"); - - migrationBuilder.AlterColumn( - name: "UserName", - table: "AspNetUsers", - type: "character varying(256)", - maxLength: 256, - nullable: true, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "NormalizedUserName", - table: "AspNetUsers", - type: "character varying(256)", - maxLength: 256, - nullable: true, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "NormalizedEmail", - table: "AspNetUsers", - type: "character varying(256)", - maxLength: 256, - nullable: true, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Email", - table: "AspNetUsers", - type: "character varying(256)", - maxLength: 256, - nullable: true, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_AspNetUsers", - table: "AspNetUsers", - column: "Id"); - migrationBuilder.CreateTable( name: "AspNetRoles", columns: table => new @@ -78,6 +26,53 @@ namespace agologum_api.Migrations table.PrimaryKey("PK_AspNetRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "AspNetUserClaims", columns: table => new @@ -119,47 +114,6 @@ namespace agologum_api.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "text", nullable: false), - LoginProvider = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - Value = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RoleId = table.Column(type: "text", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "AspNetUserRoles", columns: table => new @@ -184,16 +138,25 @@ namespace agologum_api.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true); + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", @@ -220,6 +183,17 @@ namespace agologum_api.Migrations name: "IX_AspNetUserRoles_RoleId", table: "AspNetUserRoles", column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); } /// @@ -243,66 +217,8 @@ namespace agologum_api.Migrations migrationBuilder.DropTable( name: "AspNetRoles"); - migrationBuilder.DropPrimaryKey( - name: "PK_AspNetUsers", - table: "AspNetUsers"); - - migrationBuilder.DropIndex( - name: "EmailIndex", - table: "AspNetUsers"); - - migrationBuilder.DropIndex( - name: "UserNameIndex", - table: "AspNetUsers"); - - migrationBuilder.RenameTable( - name: "AspNetUsers", - newName: "Users"); - - migrationBuilder.AlterColumn( - name: "UserName", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "NormalizedUserName", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "NormalizedEmail", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Email", - table: "Users", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256, - oldNullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_Users", - table: "Users", - column: "Id"); + migrationBuilder.DropTable( + name: "AspNetUsers"); } } } diff --git a/scripts/notes.txt b/scripts/notes.txt new file mode 100644 index 0000000..8ff218c --- /dev/null +++ b/scripts/notes.txt @@ -0,0 +1,8 @@ + +Resetting the database (for dev): +> set development evironment (specify non-docker network and db password) +> dotnet ef database drop +> dotnet ef migrations remove +> if above errors, dotnet ef database update 0 +> dotnet ef migrations add InitialCreate + From fc064dd01c187fbee7ffbe3d02e90e1369a78729 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 17:26:50 -0500 Subject: [PATCH 27/47] add identity to the register endpoint --- api/src/Controllers/AuthController.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index ba2ae21..05c4efd 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -30,15 +30,15 @@ public class AuthController : ControllerBase { var user = new User { UserName = dto.UserName, Email = dto.Email, - PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password), // TODO: secondary hashing stage in client CreatedAt = DateTime.UtcNow // yeah why not utc }; - var newUser = await users_.Create(user); + var result = await userManager_.CreateAsync(user, dto.Password); + if(!result.Succeeded) return BadRequest(result.Errors); + return CreatedAtAction( nameof(Register), - new { id = newUser.Id }, - user + new { id = user.Id } ); } @@ -63,6 +63,7 @@ public class AuthController : ControllerBase { public ActionResult Logout() { // dummy endpoint // logout happens upon client-side jwt removal + // TODO: expire all refresh tokens return Ok(); } @@ -70,4 +71,5 @@ public class AuthController : ControllerBase { // refresh tokens // email verification // password reset + // oh hell naw 2FA I do not care enough } \ No newline at end of file From 4fe23699c827ec1100fd3bd68defeed78b214268 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 17:41:11 -0500 Subject: [PATCH 28/47] fix client to api dtos again --- api/src/Controllers/AuthController.cs | 2 +- client/src/components/UsersTable.vue | 2 +- client/src/models/User.ts | 6 +++--- client/src/pages/LoginForm.vue | 4 ++-- client/src/pages/RegisterForm.vue | 4 ++-- client/src/pages/UserForm.vue | 4 ++-- client/src/pages/UsersList.vue | 2 +- scripts/DEV_README.md | 24 ++++++++++++++++++++++++ scripts/notes.txt | 8 -------- 9 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 scripts/DEV_README.md delete mode 100644 scripts/notes.txt diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index 05c4efd..6daca44 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -45,7 +45,7 @@ public class AuthController : ControllerBase { [HttpPost("login")] public async Task Login(LoginDto dto) { - var user = await users_.Get(dto.UserName); + var user = await userManager_.FindByNameAsync(dto.UserName); if (user == null) return Unauthorized(); diff --git a/client/src/components/UsersTable.vue b/client/src/components/UsersTable.vue index b9c87e0..76ec2b9 100644 --- a/client/src/components/UsersTable.vue +++ b/client/src/components/UsersTable.vue @@ -21,7 +21,7 @@ onMounted(() => { // register callback for when component is loaded on page - +
{{ user.name }}{{ user.username }} Edit diff --git a/client/src/models/User.ts b/client/src/models/User.ts index 6bc0c95..48d8fcd 100644 --- a/client/src/models/User.ts +++ b/client/src/models/User.ts @@ -4,18 +4,18 @@ export interface User { id: number; - name: string; + username: string; email: string; password: string; } export interface RegisterDto { - name: string; + username: string; email: string; password: string; } export interface LoginDto { - name: string; + username: string; password: string; } diff --git a/client/src/pages/LoginForm.vue b/client/src/pages/LoginForm.vue index 7f79c15..04f7e79 100644 --- a/client/src/pages/LoginForm.vue +++ b/client/src/pages/LoginForm.vue @@ -10,7 +10,7 @@ import * as authApi from "../api/AuthApi"; const router = useRouter(); const user = reactive({ // the template ensures type consistency - name: "", + username: "", password: "", }); @@ -38,7 +38,7 @@ async function login(): Promise {

Login

- + diff --git a/client/src/pages/RegisterForm.vue b/client/src/pages/RegisterForm.vue index 97a904e..28a461c 100644 --- a/client/src/pages/RegisterForm.vue +++ b/client/src/pages/RegisterForm.vue @@ -10,7 +10,7 @@ import * as authApi from "../api/AuthApi"; const router = useRouter(); const user = reactive({ // the template ensures type consistency - name: "", + username: "", email: "", password: "", }); @@ -40,7 +40,7 @@ async function register(): Promise {

Register

- + diff --git a/client/src/pages/UserForm.vue b/client/src/pages/UserForm.vue index 75079bf..7b70f23 100644 --- a/client/src/pages/UserForm.vue +++ b/client/src/pages/UserForm.vue @@ -14,7 +14,7 @@ const router = useRouter(); const user = ref({ id: 0, - name: "", + username: "", email: "", password: "" }); @@ -46,7 +46,7 @@ async function save(): Promise {

{{ id ? "Edit User" : "Create User" }}

- + diff --git a/client/src/pages/UsersList.vue b/client/src/pages/UsersList.vue index 342b4b9..35185e2 100644 --- a/client/src/pages/UsersList.vue +++ b/client/src/pages/UsersList.vue @@ -28,7 +28,7 @@ function logout() { - +
{{ user.name }}{{ user.username }} diff --git a/scripts/DEV_README.md b/scripts/DEV_README.md new file mode 100644 index 0000000..bfee97f --- /dev/null +++ b/scripts/DEV_README.md @@ -0,0 +1,24 @@ + +## These are some notes for development +# contains some helpful tips, commands, and knowledge + +Resetting the database (for dev): +> set development evironment (specify non-docker network and db password) +> dotnet ef database drop +> dotnet ef migrations remove +> if above errors, dotnet ef database update 0 +> dotnet ef migrations add InitialCreate + +To see live logs: +sudo docker logs -f -t agologum-api + +public user: +> username=bard +> password=Public*890 + +chrome dev tools troubleshooting +> response body: Network => url endpoint => Response => expand + +Always test build before committing +> for the client: $ npm run dev +> for the api: $ dotnet build diff --git a/scripts/notes.txt b/scripts/notes.txt deleted file mode 100644 index 8ff218c..0000000 --- a/scripts/notes.txt +++ /dev/null @@ -1,8 +0,0 @@ - -Resetting the database (for dev): -> set development evironment (specify non-docker network and db password) -> dotnet ef database drop -> dotnet ef migrations remove -> if above errors, dotnet ef database update 0 -> dotnet ef migrations add InitialCreate - From cda10dfaa4d174b46c9a91ccb3a9eb88353c5413 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 17:51:02 -0500 Subject: [PATCH 29/47] debug --- client/src/api/AuthApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/api/AuthApi.ts b/client/src/api/AuthApi.ts index 3fac7ec..f57cc48 100644 --- a/client/src/api/AuthApi.ts +++ b/client/src/api/AuthApi.ts @@ -10,6 +10,7 @@ const API_URL: string = "/auth"; export const register = async (user: RegisterDto) => { try { + console.log(user); const response = await api.post(`${API_URL}/register`, user); // TODO: if valid From a9b4d136d5c6d7dd78e5800f4e01c8f8eef5dccc Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 21 Mar 2026 20:20:02 -0500 Subject: [PATCH 30/47] Add items crud to api --- .../20260322011947_AddItems.Designer.cs | 306 ++++++++++++++++++ api/Migrations/20260322011947_AddItems.cs | 39 +++ api/Migrations/AppDbContextModelSnapshot.cs | 27 ++ api/Program.cs | 2 +- api/src/Controllers/AuthController.cs | 4 +- ...{UsersController.cs => ItemsController.cs} | 28 +- api/src/Data/AppDbContext.cs | 3 + api/src/Models/Dto.cs | 7 + api/src/Models/Item.cs | 12 + api/src/Services/ItemService.cs | 51 +++ api/src/Services/UserService.cs | 51 --- client/src/api/AuthApi.ts | 1 - 12 files changed, 461 insertions(+), 70 deletions(-) create mode 100644 api/Migrations/20260322011947_AddItems.Designer.cs create mode 100644 api/Migrations/20260322011947_AddItems.cs rename api/src/Controllers/{UsersController.cs => ItemsController.cs} (54%) create mode 100644 api/src/Models/Item.cs create mode 100644 api/src/Services/ItemService.cs delete mode 100644 api/src/Services/UserService.cs diff --git a/api/Migrations/20260322011947_AddItems.Designer.cs b/api/Migrations/20260322011947_AddItems.Designer.cs new file mode 100644 index 0000000..b40ce57 --- /dev/null +++ b/api/Migrations/20260322011947_AddItems.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260322011947_AddItems")] + partial class AddItems + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("agologumApi.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260322011947_AddItems.cs b/api/Migrations/20260322011947_AddItems.cs new file mode 100644 index 0000000..324af3d --- /dev/null +++ b/api/Migrations/20260322011947_AddItems.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class AddItems : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Items", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + LastEditedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Items", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Items"); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index 04564b3..aa5eef9 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -153,6 +153,33 @@ namespace agologum_api.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("agologumApi.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Items"); + }); + modelBuilder.Entity("agologumApi.Models.User", b => { b.Property("Id") diff --git a/api/Program.cs b/api/Program.cs index fbf5bc5..8f51fb4 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -21,7 +21,7 @@ builder.Services.AddDbContext(options => builder.Services.AddControllers(); // services -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); // configuration for jwt authentication diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index 6daca44..a28fe7e 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -14,14 +14,12 @@ public class AuthController : ControllerBase { private readonly UserManager userManager_; private readonly SignInManager signInManager_; - private readonly UserService users_; private readonly JwtService jwt_; - public AuthController(UserManager userManager, SignInManager signInManager, UserService users, JwtService jwt) { + public AuthController(UserManager userManager, SignInManager signInManager, JwtService jwt) { userManager_ = userManager; signInManager_ = signInManager; - users_ = users; jwt_ = jwt; } diff --git a/api/src/Controllers/UsersController.cs b/api/src/Controllers/ItemsController.cs similarity index 54% rename from api/src/Controllers/UsersController.cs rename to api/src/Controllers/ItemsController.cs index 7962381..86991f5 100644 --- a/api/src/Controllers/UsersController.cs +++ b/api/src/Controllers/ItemsController.cs @@ -7,39 +7,39 @@ using agologumApi.Services; [ApiController] [Route("api/[controller]")] -public class UsersController : ControllerBase { +public class ItemsController : ControllerBase { - private readonly UserService service_; + private readonly ItemService service_; - public UsersController(UserService service) { + public ItemsController(ItemService service) { service_ = service; } [AllowAnonymous] // accessible if not authorized [HttpGet] - public async Task>> getUsers() { + public async Task>> getItemss() { return Ok(await service_.GetAll()); } [AllowAnonymous] [HttpGet("{id:int}")] - public async Task> getUser(int id) { + public async Task> getItem(int id) { - var user = await service_.Get(id); + var item = await service_.Get(id); - if (user == null) return NotFound(); + if (item == null) return NotFound(); - return Ok(user); + return Ok(item); } [Authorize] // testing the authorization [HttpPost] - public async Task> createUser(User user) { + public async Task> createItem(Item item) { - var created = await service_.Create(user); + var created = await service_.Create(item); return CreatedAtAction( - nameof(getUser), + nameof(getItem), new { id = created.Id }, created ); @@ -47,9 +47,9 @@ public class UsersController : ControllerBase { [Authorize] [HttpPut("{id}")] - public async Task> updateUser(int id, User user) { + public async Task> updateItem(int id, Item item) { - var updated = await service_.Update(user); + var updated = await service_.Update(item); if (updated == null) return NotFound(); @@ -58,7 +58,7 @@ public class UsersController : ControllerBase { [Authorize] [HttpDelete("{id}")] - public async Task deleteUser(int id) { + public async Task deleteItem(int id) { var success = await service_.Delete(id); diff --git a/api/src/Data/AppDbContext.cs b/api/src/Data/AppDbContext.cs index 22d1e54..66e7998 100644 --- a/api/src/Data/AppDbContext.cs +++ b/api/src/Data/AppDbContext.cs @@ -10,4 +10,7 @@ public class AppDbContext : IdentityDbContext { } + // Db set for each model besides Users (DbSet