Skip to content

Commit

Permalink
initial project
Browse files Browse the repository at this point in the history
  • Loading branch information
2code-it committed Mar 29, 2024
1 parent c7c97ca commit 24c97fe
Show file tree
Hide file tree
Showing 50 changed files with 8,467 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .azure/pipelines/build-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
trigger:
branches:
include:
- develop
- master
paths:
include:
- /src/*

resources:
repositories:
- repository: devops
type: git
name: pub/devops
ref: master


jobs:
- template: dotnet-build-deploy.yml@devops
parameters:
versionPrefix: '0.1'
projectName: 'Web1.Sse.ChatApi'
hasUnitTests: false
createZip: true
createNugetPackage: false
createGithubRelease: true
25 changes: 25 additions & 0 deletions src/Web1.Sse.Chat.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34701.34
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web1.Sse.ChatApi", "Web1.Sse.ChatApi\Web1.Sse.ChatApi.csproj", "{A0064768-9AD1-46AC-9005-2F5B1501B784}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A0064768-9AD1-46AC-9005-2F5B1501B784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0064768-9AD1-46AC-9005-2F5B1501B784}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0064768-9AD1-46AC-9005-2F5B1501B784}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0064768-9AD1-46AC-9005-2F5B1501B784}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F52A468-AA55-44F7-B83A-320F7DFE70E4}
EndGlobalSection
EndGlobal
9 changes: 9 additions & 0 deletions src/Web1.Sse.ChatApi/Assets/IApiEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Builder;

namespace Web1.Sse.ChatApi.Assets
{
public interface IApiEndPoint
{
void Map(WebApplication app);
}
}
21 changes: 21 additions & 0 deletions src/Web1.Sse.ChatApi/EndPoints/EndPointExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using System.Reflection;
using Web1.Sse.ChatApi.Assets;

namespace Web1.Sse.ChatApi.EndPoints
{
public static class EndPointExtensions
{
public static void UseApiEndPoints(this WebApplication app)
{
var endpointTypes = Assembly.GetExecutingAssembly().ExportedTypes.Where(x => x.GetInterface(nameof(IApiEndPoint)) is not null);
foreach (var endpointType in endpointTypes)
{
IApiEndPoint endPoint = (IApiEndPoint)ActivatorUtilities.CreateInstance(app.Services, endpointType);
endPoint.Map(app);
}
}
}
}
39 changes: 39 additions & 0 deletions src/Web1.Sse.ChatApi/EndPoints/MessagesEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Code2.Web.SseTyped;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using System.Security.Claims;
using Web1.Sse.ChatApi.Assets;
using Web1.Sse.ChatApi.Models;
using Web1.Sse.ChatApi.Services;

namespace Web1.Sse.ChatApi.EndPoints
{
public class MessagesEndPoint : IApiEndPoint
{
public MessagesEndPoint(ISecurityService securityService, ISseService sseService)
{
_securityService = securityService;
_sseService = sseService;
}

private readonly ISseService _sseService;
private readonly ISecurityService _securityService;

public void Map(WebApplication app)
{
app.MapPost("/messages", AddMessage);
}

public IResult AddMessage([FromBody] ChatMessage message, ClaimsPrincipal principal)
{
message.FromUserId = _securityService.GetCurrentUserId(principal);
message.Created = DateTime.Now;
string[] userIds = message.ToUserIds.Select(x => x.ToString()).ToArray();
_sseService.Send(message, (properties) => userIds.Length == 0 || userIds.Contains(properties["userId"]));
return Results.NoContent();
}
}
}
91 changes: 91 additions & 0 deletions src/Web1.Sse.ChatApi/EndPoints/UsersEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Code2.Web.SseTyped;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using System;
using System.Security.Claims;
using Web1.Sse.ChatApi.Assets;
using Web1.Sse.ChatApi.Models;
using Web1.Sse.ChatApi.Services;

namespace Web1.Sse.ChatApi.EndPoints
{
public class UsersEndPoint : IApiEndPoint
{

public UsersEndPoint(IUserRepository userRepository, ISseService sseService, ISecurityService securityService)
{
_userRepository = userRepository;
_sseService = sseService;
_securityService = securityService;
}

private readonly IUserRepository _userRepository;
private readonly ISseService _sseService;
private readonly ISecurityService _securityService;

public void Map(WebApplication app)
{
app.MapPost("/users/login", Login).AllowAnonymous();
app.MapGet("/users", GetUsers);
app.MapPut("/users/{userId}", UpdateUser);
app.MapDelete("/users/{userId}", DeleteUser);
}

public IResult Login(LoginRequest loginRequest)
{
try
{
ChatUser user = new ChatUser { Name = loginRequest.Name };
int userId = _userRepository.Add(user);
_sseService.Send(new SystemMessage(SystemMessage.UserAddedMessageType, user));
return Results.Ok(new LoginResponse { UserId = userId, Token = _securityService.GetToken(userId) });
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}

public IResult GetUsers()
{
return Results.Ok(_userRepository.Get());
}

public IResult UpdateUser(int userId, [FromBody] ChatUser user, ClaimsPrincipal principal)
{
if (!IsCurrentUserId(userId, principal)) return Results.Problem("Invalid operation");
try
{
_userRepository.Update(userId, user);
_sseService.Send(new SystemMessage(SystemMessage.UserModifiedMessageType, user));
return Results.NoContent();
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}

public IResult DeleteUser(int userId, ClaimsPrincipal principal)
{
if (!IsCurrentUserId(userId, principal)) return Results.Problem("Invalid operation");
try
{
_userRepository.Delete(userId);
_sseService.Send(new SystemMessage(SystemMessage.UserRemovedMessageType, new ChatUser { Id = userId }));
return Results.NoContent();
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}

private bool IsCurrentUserId(int userId, ClaimsPrincipal principal)
=> _securityService.GetCurrentUserId(principal) == userId;


}
}
13 changes: 13 additions & 0 deletions src/Web1.Sse.ChatApi/Models/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Web1.Sse.ChatApi.Models
{
public class ChatMessage
{
public int Id { get; set; }
public DateTime? Created { get; set; }
public int FromUserId { get; set; }
public int[] ToUserIds { get; set; } = Array.Empty<int>();
public string Text { get; set; } = string.Empty;
}
}
8 changes: 8 additions & 0 deletions src/Web1.Sse.ChatApi/Models/ChatUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Web1.Sse.ChatApi.Models
{
public class ChatUser
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
}
7 changes: 7 additions & 0 deletions src/Web1.Sse.ChatApi/Models/LoginRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Web1.Sse.ChatApi.Models
{
public class LoginRequest
{
public string Name { get; set; } = string.Empty;
}
}
8 changes: 8 additions & 0 deletions src/Web1.Sse.ChatApi/Models/LoginResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Web1.Sse.ChatApi.Models
{
public class LoginResponse
{
public int UserId { get; set; }
public string Token { get; set; } = string.Empty;
}
}
19 changes: 19 additions & 0 deletions src/Web1.Sse.ChatApi/Models/SystemMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Web1.Sse.ChatApi.Models
{
public class SystemMessage
{
public SystemMessage(string messageType, object data)
{
MessageType = messageType;
Data = data;
}

public const string UserModifiedMessageType = "UserModified";
public const string UserAddedMessageType = "UserAdded";
public const string UserRemovedMessageType = "UserRemoved";

public string MessageType { get; set; }
public object Data { get; private set; }

}
}
31 changes: 31 additions & 0 deletions src/Web1.Sse.ChatApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Code2.Web.SseTyped;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Web1.Sse.ChatApi.EndPoints;
using Web1.Sse.ChatApi.Services;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
ISecurityService securityService = new SecurityService(builder.Configuration);
builder.Services.AddSingleton<IUserRepository, UserRepository>();
builder.Services.AddSingleton<ISecurityService>(securityService);
builder.Services.AddSseTyped();
builder.Services.AddCors();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(securityService.ConfigureJwtBearer);
builder.Services.AddAuthorization(securityService.ConfigureAuthorization);


var app = builder.Build();
// Configure the HTTP request pipeline.

app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();

app.UseApiEndPoints();
app.UseSseTyped(rootPath: "/sse");


app.Run();
31 changes: 31 additions & 0 deletions src/Web1.Sse.ChatApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:13675",
"sslPort": 44315
}
},
"profiles": {
"Web1.Sse.ChatApi": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "index.html",
"applicationUrl": "http://localhost:5180",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "index.html",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
14 changes: 14 additions & 0 deletions src/Web1.Sse.ChatApi/Services/ISecurityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;

namespace Web1.Sse.ChatApi.Services
{
public interface ISecurityService
{
void ConfigureJwtBearer(JwtBearerOptions options);
void ConfigureAuthorization(AuthorizationOptions options);
string GetToken(int userId);
int GetCurrentUserId(ClaimsPrincipal? principal);
}
}
14 changes: 14 additions & 0 deletions src/Web1.Sse.ChatApi/Services/IUserRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using Web1.Sse.ChatApi.Models;

namespace Web1.Sse.ChatApi.Services
{
public interface IUserRepository
{
int Add(ChatUser user);
void Delete(int userId);
ChatUser[] Get();
ChatUser[] Get(Func<ChatUser, bool> filter);
void Update(int userId, ChatUser user);
}
}
Loading

0 comments on commit 24c97fe

Please sign in to comment.