From 4a7810f571487112ebfa61b4597f4d7569495757 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Thu, 10 Jul 2025 23:58:12 +0300 Subject: [PATCH] feat: implemented return of user info and eula accept logic --- .gitignore | 1 + JOBot.Backend/DAL/Context/AppDbContext.cs | 8 +- JOBot.Backend/DAL/Models/User.cs | 27 +++++- ....cs => 20250710203327_Initial.Designer.cs} | 26 ++++-- ...8_Initial.cs => 20250710203327_Initial.cs} | 11 ++- .../Migrations/AppDbContextModelSnapshot.cs | 24 +++-- JOBot.Backend/Dockerfile | 1 + JOBot.Backend/JOBot.Backend.csproj | 5 ++ JOBot.Backend/Services/gRPC/UserService.cs | 89 +++++++++++++++---- JOBot.Backend/Startup.cs | 5 +- JOBot.Backend/appsettings.Development.json | 3 + JOBot.Backend/appsettings.Staging.json | 2 +- Proto/user.proto | 16 +++- compose.dev.yml | 37 ++++++++ compose.yml | 2 - 15 files changed, 209 insertions(+), 48 deletions(-) rename JOBot.Backend/Data/Migrations/{20250710125338_Initial.Designer.cs => 20250710203327_Initial.Designer.cs} (63%) rename JOBot.Backend/Data/Migrations/{20250710125338_Initial.cs => 20250710203327_Initial.cs} (61%) create mode 100644 compose.dev.yml diff --git a/.gitignore b/.gitignore index 2508034..0c4ec6d 100644 --- a/.gitignore +++ b/.gitignore @@ -406,3 +406,4 @@ FodyWeavers.xsd # Secrets JOBot.TClient/appsettings.json /.idea/.idea.JOBot/Docker/compose.generated.override.yml +/.idea/.idea.JOBot/Docker/compose.dev.generated.override.yml diff --git a/JOBot.Backend/DAL/Context/AppDbContext.cs b/JOBot.Backend/DAL/Context/AppDbContext.cs index 730c478..9e91d4e 100644 --- a/JOBot.Backend/DAL/Context/AppDbContext.cs +++ b/JOBot.Backend/DAL/Context/AppDbContext.cs @@ -1,19 +1,17 @@ namespace JOBot.Backend.DAL.Context; -using JOBot.Backend.DAL.Models; +using Models; using Microsoft.EntityFrameworkCore; -public class AppDbContext : DbContext +public class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet Users { get; set; } - public AppDbContext(DbContextOptions options) : base(options) { } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity() - .HasAlternateKey(b => b.TelegramId); + .HasAlternateKey(b => b.UserId); } } \ No newline at end of file diff --git a/JOBot.Backend/DAL/Models/User.cs b/JOBot.Backend/DAL/Models/User.cs index 7d8e786..82ec05c 100644 --- a/JOBot.Backend/DAL/Models/User.cs +++ b/JOBot.Backend/DAL/Models/User.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using JOBot.Proto; using Microsoft.EntityFrameworkCore; namespace JOBot.Backend.DAL.Models; @@ -9,10 +10,30 @@ public class User public Guid Id { get; set; } [Key] - public required long TelegramId { get; set; } - [MaxLength(50)] + public required long UserId { get; set; } + [MaxLength(255)] public string? Username { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public string? HeadHunterResumeUrl { get; set; } + [MaxLength(255)] public string? AccessToken { get; set; } = null; + [MaxLength(255)] public string? RefreshToken { get; set; } = null; + + public bool Eula { get; set; } = false; + [MaxLength(255)] public string? CvUrl { get; set; } = null; +} + +//TODO: Негоже это маппинги в DAL ложить +public static class UserMap +{ + public static GetUserResponse MapToResponse(this User user) + { + return new GetUserResponse + { + UserId = user.UserId, + Username = user.Username, + Eula = user.Eula, + IsLogged = user.RefreshToken != null, + CVUrl = user.CvUrl + }; + } } \ No newline at end of file diff --git a/JOBot.Backend/Data/Migrations/20250710125338_Initial.Designer.cs b/JOBot.Backend/Data/Migrations/20250710203327_Initial.Designer.cs similarity index 63% rename from JOBot.Backend/Data/Migrations/20250710125338_Initial.Designer.cs rename to JOBot.Backend/Data/Migrations/20250710203327_Initial.Designer.cs index 24df0f4..56191cc 100644 --- a/JOBot.Backend/Data/Migrations/20250710125338_Initial.Designer.cs +++ b/JOBot.Backend/Data/Migrations/20250710203327_Initial.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace JOBot.Backend.Data.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20250710125338_Initial")] + [Migration("20250710203327_Initial")] partial class Initial { /// @@ -31,22 +31,34 @@ namespace JOBot.Backend.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("AccessToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("HeadHunterResumeUrl") - .HasColumnType("text"); + b.Property("CvUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); - b.Property("TelegramId") + b.Property("Eula") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") .HasColumnType("bigint"); b.Property("Username") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); + .HasMaxLength(255) + .HasColumnType("character varying(255)"); b.HasKey("Id"); - b.HasAlternateKey("TelegramId"); + b.HasAlternateKey("UserId"); b.ToTable("Users"); }); diff --git a/JOBot.Backend/Data/Migrations/20250710125338_Initial.cs b/JOBot.Backend/Data/Migrations/20250710203327_Initial.cs similarity index 61% rename from JOBot.Backend/Data/Migrations/20250710125338_Initial.cs rename to JOBot.Backend/Data/Migrations/20250710203327_Initial.cs index f868d53..0db10c7 100644 --- a/JOBot.Backend/Data/Migrations/20250710125338_Initial.cs +++ b/JOBot.Backend/Data/Migrations/20250710203327_Initial.cs @@ -16,15 +16,18 @@ namespace JOBot.Backend.Data.Migrations columns: table => new { Id = table.Column(type: "uuid", nullable: false), - TelegramId = table.Column(type: "bigint", nullable: false), - Username = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + UserId = table.Column(type: "bigint", nullable: false), + Username = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - HeadHunterResumeUrl = table.Column(type: "text", nullable: true) + AccessToken = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + RefreshToken = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Eula = table.Column(type: "boolean", nullable: false), + CvUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) }, constraints: table => { table.PrimaryKey("PK_Users", x => x.Id); - table.UniqueConstraint("AK_Users_TelegramId", x => x.TelegramId); + table.UniqueConstraint("AK_Users_UserId", x => x.UserId); }); } diff --git a/JOBot.Backend/Data/Migrations/AppDbContextModelSnapshot.cs b/JOBot.Backend/Data/Migrations/AppDbContextModelSnapshot.cs index 24a6ae6..ada6829 100644 --- a/JOBot.Backend/Data/Migrations/AppDbContextModelSnapshot.cs +++ b/JOBot.Backend/Data/Migrations/AppDbContextModelSnapshot.cs @@ -28,22 +28,34 @@ namespace JOBot.Backend.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("AccessToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("HeadHunterResumeUrl") - .HasColumnType("text"); + b.Property("CvUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); - b.Property("TelegramId") + b.Property("Eula") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") .HasColumnType("bigint"); b.Property("Username") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); + .HasMaxLength(255) + .HasColumnType("character varying(255)"); b.HasKey("Id"); - b.HasAlternateKey("TelegramId"); + b.HasAlternateKey("UserId"); b.ToTable("Users"); }); diff --git a/JOBot.Backend/Dockerfile b/JOBot.Backend/Dockerfile index da0d968..6073e57 100644 --- a/JOBot.Backend/Dockerfile +++ b/JOBot.Backend/Dockerfile @@ -10,4 +10,5 @@ WORKDIR /app COPY --from=build /app . EXPOSE 5001 EXPOSE 5000 +ENV ASPNETCORE_ENVIRONMENT Staging ENTRYPOINT ["dotnet", "JOBot.Backend.dll"] \ No newline at end of file diff --git a/JOBot.Backend/JOBot.Backend.csproj b/JOBot.Backend/JOBot.Backend.csproj index 5850b0b..2c4b3c8 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -9,6 +9,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,4 +25,8 @@ + + + + \ No newline at end of file diff --git a/JOBot.Backend/Services/gRPC/UserService.cs b/JOBot.Backend/Services/gRPC/UserService.cs index b2b18e4..d2f3b60 100644 --- a/JOBot.Backend/Services/gRPC/UserService.cs +++ b/JOBot.Backend/Services/gRPC/UserService.cs @@ -1,43 +1,96 @@ using Grpc.Core; -using JOBot.Proto; using JOBot.Backend.DAL.Context; - -using Models = JOBot.Backend.DAL.Models; +using JOBot.Backend.DAL.Models; +using JOBot.Proto; +using Microsoft.EntityFrameworkCore; +using User = JOBot.Backend.DAL.Models.User; namespace JOBot.Backend.Services.gRPC; -public class UserService(AppDbContext dbContext) : User.UserBase +public class UserService(AppDbContext dbContext) : Proto.User.UserBase { - public override Task Register( + /// + /// Create user + /// + /// + /// + /// Status of operation (fail if user exists) + public override async Task Register( RegisterRequest request, - ServerCallContext context) + ServerCallContext _) { - if(!dbContext.Users - .Any(x => x.TelegramId == request.UserId)) + if(!await dbContext.Users + .AnyAsync(x => x.UserId == request.UserId)) { - dbContext.Users.Add(new Models.User + dbContext.Users.Add(new User { - TelegramId = request.UserId, + UserId = request.UserId, Username = !string.IsNullOrEmpty(request.Username) ? request.Username : null }); - dbContext.SaveChanges(); - return Task.FromResult(new RegisterResponse + await dbContext.SaveChangesAsync(); + return new RegisterResponse { Success = true - }); + }; } - return Task.FromResult(new RegisterResponse + return new RegisterResponse { Success = false - }); + }; } - public override Task GetUser( + /// + /// Get user for client + /// + /// + /// + /// User, or throws RPC exception if not found + public override async Task GetUser( GetUserRequest request, - ServerCallContext context) + ServerCallContext _) { - return null; + var user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == request.UserId); + ValidateUserFound(user); + + return user!.MapToResponse(); + } + + /// + /// Accept EULA for user + /// + /// + /// + /// Status of operation + public override async Task AcceptEula(AcceptEulaRequest request, ServerCallContext _) + { + var user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == request.UserId); + if (user == null) + { + return new AcceptEulaResponse + { + Success = false + }; + } + + user.Eula = request.EulaAccepted; + await dbContext.SaveChangesAsync(); + + return new AcceptEulaResponse + { + Success = true + }; + } + + /// + /// Throw RPCException if user not found + /// + /// + /// + private void ValidateUserFound(User? user) + { + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "User not found")); } } \ No newline at end of file diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 3a2c585..0186b3b 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -15,13 +15,16 @@ public class Startup public void ConfigureServices(IServiceCollection services) { - services.AddGrpc(); + services.AddGrpc(); + services.AddGrpcReflection(); + services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); } public void Configure(WebApplication app, IWebHostEnvironment env) { + app.MapGrpcReflectionService().AllowAnonymous(); app.MapGrpcService(); } } \ No newline at end of file diff --git a/JOBot.Backend/appsettings.Development.json b/JOBot.Backend/appsettings.Development.json index 0c208ae..955448b 100644 --- a/JOBot.Backend/appsettings.Development.json +++ b/JOBot.Backend/appsettings.Development.json @@ -4,5 +4,8 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "PostgreSQL": "Host=localhost;Port=5432;Database=jobot;Username=postgres;Password=LocalDbPass" } } diff --git a/JOBot.Backend/appsettings.Staging.json b/JOBot.Backend/appsettings.Staging.json index 2a11f60..f8b140b 100644 --- a/JOBot.Backend/appsettings.Staging.json +++ b/JOBot.Backend/appsettings.Staging.json @@ -7,6 +7,6 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "PostgreSQL": "Host=localhost;Port=5432;Database=jobot_test;Username=postgres;Password=LocalDbPass" + "PostgreSQL": "Host=postgres;Port=5432;Database=jobot_test;Username=postgres;Password=LocalDbPass" } } \ No newline at end of file diff --git a/Proto/user.proto b/Proto/user.proto index 600e9ec..98072c3 100644 --- a/Proto/user.proto +++ b/Proto/user.proto @@ -4,6 +4,7 @@ option csharp_namespace = "JOBot.Proto"; service User { rpc Register (RegisterRequest) returns (RegisterResponse); rpc GetUser (GetUserRequest) returns (GetUserResponse); + rpc AcceptEula (AcceptEulaRequest) returns (AcceptEulaResponse); } import "google/protobuf/wrappers.proto"; @@ -22,5 +23,18 @@ message GetUserRequest { } message GetUserResponse { - bool logged_to_hh = 1; + int64 user_id = 1; + google.protobuf.StringValue username = 2; + bool eula = 3; + bool is_logged = 4; + google.protobuf.StringValue CVUrl = 5; +} + +message AcceptEulaRequest { + int64 user_id = 1; + bool eula_accepted = 2; +} + +message AcceptEulaResponse{ + bool success = 1; } \ No newline at end of file diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 0000000..4bb2897 --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,37 @@ +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_PASSWORD: LocalDbPass + POSTGRES_DB: jobot + ports: + - "5432:5432" + volumes: + - ./.docker/postgres_data:/var/lib/postgresql/data + networks: + - jobot + + backend: + build: + context: . + dockerfile: JOBot.Backend/Dockerfile + depends_on: + - postgres + ports: + - "5001:5001" + networks: + - jobot + + bot: + build: + context: . + dockerfile: JOBot.TClient/Dockerfile + depends_on: + - backend + networks: + - jobot + +networks: + jobot: \ No newline at end of file diff --git a/compose.yml b/compose.yml index b0fe59b..da54fa4 100644 --- a/compose.yml +++ b/compose.yml @@ -6,8 +6,6 @@ services: environment: POSTGRES_PASSWORD: LocalDbPass POSTGRES_DB: jobot - ports: - - "5432:5432" volumes: - ./.docker/postgres_data:/var/lib/postgresql/data networks: