Initial Commit

This commit is contained in:
Alan Moon 2025-06-15 14:24:53 -07:00
commit 16c9aae729
62 changed files with 3892 additions and 0 deletions

30
.dockerignore Normal file
View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

366
.gitignore vendored Normal file
View File

@ -0,0 +1,366 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/qtc-net-server/qtcdev.db
/qtc-net-server/qtcdev.db-shm
/qtc-net-server/qtcdev.db-wal

1
README.md Normal file
View File

@ -0,0 +1 @@
# qtc-net-server

19
docker-compose.dcproj Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<DockerPublishLocally>False</DockerPublishLocally>
<ProjectGuid>b1b3d2c9-92e6-4711-842e-c940a40a9a2e</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>qtc-net-server</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
version: '3.4'
services:
qtc-net-server:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_HTTP_PORTS=8080
ports:
- "8080"

8
docker-compose.yml Normal file
View File

@ -0,0 +1,8 @@
version: '3.4'
services:
qtc-net-server:
image: ${DOCKER_REGISTRY-}qtcnetserver
build:
context: .
dockerfile: qtc-net-server/Dockerfile

11
launchSettings.json Normal file
View File

@ -0,0 +1,11 @@
{
"profiles": {
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"serviceActions": {
"qtc-net-server": "StartDebugging"
}
}
}
}

31
qtc-net-server.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33414.496
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qtc-net-server", "qtc-net-server\qtc-net-server.csproj", "{AE9BEB1A-340C-4EE4-90D1-0B16456DDE6A}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{B1B3D2C9-92E6-4711-842E-C940A40A9A2E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE9BEB1A-340C-4EE4-90D1-0B16456DDE6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE9BEB1A-340C-4EE4-90D1-0B16456DDE6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE9BEB1A-340C-4EE4-90D1-0B16456DDE6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE9BEB1A-340C-4EE4-90D1-0B16456DDE6A}.Release|Any CPU.Build.0 = Release|Any CPU
{B1B3D2C9-92E6-4711-842E-C940A40A9A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1B3D2C9-92E6-4711-842E-C940A40A9A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1B3D2C9-92E6-4711-842E-C940A40A9A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1B3D2C9-92E6-4711-842E-C940A40A9A2E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1B6C7F89-981F-4871-860E-D961CB6A204A}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using qtc_api.Dtos.User;
using System.Runtime.CompilerServices;
using System.Text.Json;
namespace qtc_api.Controllers
{
[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
private readonly ServerConfig serverConfig;
private readonly DataContext dataContext;
public AuthController(IUserService userService, ITokenService tokenService, DataContext dataContext)
{
_userService = userService;
_tokenService = tokenService;
serverConfig = JsonSerializer.Deserialize<ServerConfig>(JsonDocument.Parse(System.IO.File.ReadAllText("./ServerConfig.json")));
this.dataContext = dataContext;
}
[HttpPost("register")]
public async Task<ActionResult<ServiceResponse<User>>> Register(UserDto userDto)
{
if(userDto != null)
{
var response = await _userService.AddUser(userDto);
if(response.Success != false)
{
return Ok(response);
} else
{
return StatusCode(500, response.Message);
}
} else
{
return BadRequest();
}
}
[HttpPost("login")]
public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
{
var dbUser = await _userService.GetUserByEmail(request.Email);
if (dbUser.Data == null)
{
return Ok(new ServiceResponse<string>
{
Message = "User not found.",
Success = false
});
} else if(!BCrypt.Net.BCrypt.Verify(request.Password, dbUser.Data.PasswordHash))
{
return Ok(new ServiceResponse<string>
{
Message = "Incorrect password.",
Success = false
});
}
if (dbUser.Data.Id == serverConfig.AdminUserId && dbUser.Data.Role != "Admin")
{
dbUser.Data.Role = "Admin";
dataContext.SaveChanges();
}
if (dbUser.Data.Status == 1)
{
return Ok(new ServiceResponse<string>
{
Message = "User is already signed in.",
Success = false
});
}
var token = await _tokenService.GenerateAccessTokenAndRefreshToken(dbUser.Data, true, request.RememberMe);
return Ok(token);
}
[HttpPost("refresh")]
public async Task<ActionResult<ServiceResponse<string>>> RefreshLogin(string token)
{
var response = await _tokenService.ValidateRefreshToken(token);
return Ok(response);
}
}
}

View File

@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using qtc_api.Models;
using qtc_api.Services.ContactService;
using System.Security.Claims;
namespace qtc_api.Controllers
{
[Route("api/contacts")]
[ApiController]
public class ContactController : ControllerBase
{
private IContactService _contactService;
public ContactController(IContactService contactService)
{
_contactService = contactService;
}
[HttpPost("add-contact")]
[Authorize]
public async Task<ActionResult<ServiceResponse<Contact>>> AddContact(string userId)
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var ownerId = claims.First().Value;
var result = await _contactService.CreateContact(ownerId, userId);
return Ok(result);
}
return Unauthorized();
}
[HttpPost("approve-contact")]
[Authorize]
public async Task<ActionResult<ServiceResponse<bool>>> ApproveContact(string userId)
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var ownerId = claims.First().Value;
var result = await _contactService.UpdateContactStatus(ownerId, userId, Contact.ContactStatus.Accepted, Contact.ContactStatus.Accepted);
return Ok(result);
}
return Unauthorized();
}
[HttpDelete("remove-contact")]
[Authorize]
public async Task<ActionResult<ServiceResponse<Contact>>> RemoveContact(string userId)
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var ownerId = claims.First().Value;
var response = await _contactService.DeleteContact(ownerId, userId);
return Ok(response);
}
return Unauthorized();
}
[HttpGet("get-user-contacts")]
[Authorize]
public async Task<ActionResult<ServiceResponse<List<Contact>>>> GetUserContacts(User user)
{
var result = await _contactService.GetUserContacts(user);
return Ok(result);
}
}
}

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace qtc_api.Controllers
{
[Route("api/general")]
[ApiController]
public class GeneralController : ControllerBase
{
private readonly ILogger<GeneralController> logger;
public GeneralController(ILogger<GeneralController> logger) => this.logger = logger;
[HttpGet("ping")]
public ActionResult PingAsync()
{
logger.LogInformation($"Ping Received From Client");
return Ok("Pong!");
}
}
}

View File

@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using qtc_api.Services.RoomService;
namespace qtc_api.Controllers
{
[Route("api/rooms")]
[ApiController]
public class RoomsController : ControllerBase
{
public IRoomService _roomService;
public IHubContext<ChatHub> _hubContext;
public RoomsController(IRoomService roomService, IHubContext<ChatHub> hubContext)
{
_roomService = roomService;
_hubContext = hubContext;
}
[HttpPost("create-room")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ServiceResponse<Room>>> CreateRoom(string userId, RoomDto request)
{
var response = await _roomService.AddRoom(userId, request);
await _hubContext.Clients.All.SendAsync("cf", "rul");
return Ok(response);
}
[HttpDelete("delete-room")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ServiceResponse<Room>>> DeleteRoom(string roomId)
{
var response = await _roomService.DeleteRoom(roomId);
await _hubContext.Clients.All.SendAsync("cf", "rul");
return Ok(response);
}
[HttpGet("get-all-rooms")]
[Authorize]
public async Task<ActionResult<ServiceResponse<List<Room>>>> GetAllRooms()
{
var rooms = await _roomService.GetAllRooms();
return Ok(rooms);
}
}
}

View File

@ -0,0 +1,156 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using qtc_api.Dtos.User;
using System.Net.Mime;
using System.Security.Claims;
using System.Text.Json;
namespace qtc_api.Controllers
{
[Route("api/users")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IConfiguration _configuration;
public UsersController(IUserService userService, IConfiguration configuration)
{
_userService = userService;
_configuration = configuration;
}
[HttpGet("all")]
[Authorize]
public async Task<ActionResult<ServiceResponse<List<UserInformationDto>>>> GetAllUsers()
{
var users = await _userService.GetAllUsers();
return Ok(users);
}
[HttpGet("user-info")]
[Authorize]
public async Task<ActionResult<ServiceResponse<UserInformationDto>>> GetUserInformation(string id)
{
var user = await _userService.GetUserInformationById(id);
return Ok(user);
}
[HttpGet("user-authorized")]
[Authorize]
public async Task<ActionResult<ServiceResponse<User>>> UserFromAuthorizeHead()
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if(identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var id = claims.First().Value;
if(id != null)
{
var user = await _userService.GetUserById(id);
return Ok(user);
} else
{
return BadRequest("Token did not contain an ID.");
}
} else
{
return BadRequest("Header not found.");
}
}
[HttpGet("users-online")]
[Authorize]
public async Task<ActionResult<ServiceResponse<List<UserInformationDto>>>> GetAllOnlineUsers()
{
var users = await _userService.GetAllOnlineUsers();
return Ok(users);
}
[HttpPut("update")]
[Authorize]
public async Task<ActionResult<ServiceResponse<UserInformationDto>>> UpdateUserInformation(UserUpdateInformationDto user)
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if(identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var id = claims.First().Value;
if(id != null && id == user.Id)
{
var updatedUser = await _userService.UpdateUserInfo(user);
return Ok(updatedUser);
} else
{
return Unauthorized("You are not authorized to edit that user.");
}
} else
{
return BadRequest("Session Expired.");
}
}
[HttpPost("upload-profile-pic")]
[Authorize]
public async Task<ActionResult<ServiceResponse<string>>> UploadOrUpdateProfilePic(string userId, IFormFile file)
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if(identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var id = claims.First().Value;
if(id != null && id == userId)
{
if (file.Length > 3000000)
{
return BadRequest("File Is Above Limit.");
}
var response = await _userService.UpdateUserPic(userId, file);
return Ok(response);
} else
{
return BadRequest("You are not permitted to edit that user.");
}
} else
{
return BadRequest("No Identity.");
}
}
[HttpGet("profile-pic/{userId}")]
[Authorize]
public async Task<ActionResult> GetUserProfilePicture(string userId)
{
var result = await _userService.GetUserPic(userId);
if (result != null && result.Success != false)
{
return result.Data!;
} else if (result!.Message == "User Does Not Have A Profile Picture." || result!.Message == "User Content Folder Does Not Exist Yet.")
{
return BadRequest("User has no profile picture.");
} else
{
return BadRequest("Failed To Get Profile Picture.");
}
}
[HttpDelete("delete-user")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<ServiceResponse<User>>> DeleteUserById(string id)
{
var result = await _userService.DeleteUser(id);
return Ok(result);
}
}
}

View File

@ -0,0 +1,42 @@
namespace qtc_api.Data
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Room> Rooms { get; set; }
public DbSet<RefreshToken> ValidRefreshTokens { get; set; }
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// Users
builder.Entity<User>().HasMany(e => e.ContactsList);
builder.Entity<User>().HasMany(e => e.ContactsMade);
// Rooms (no relations)
builder.Entity<Room>();
// Refresh Tokens
builder.Entity<RefreshToken>().HasOne(e => e.User)
.WithMany(e => e.RefreshTokens)
.HasForeignKey(e => e.UserID);
// Contacts
builder.Entity<Contact>().HasOne(e => e.Owner)
.WithMany(e => e.ContactsMade)
.HasForeignKey(e => e.OwnerId);
builder.Entity<Contact>().HasOne(e => e.User)
.WithMany(e => e.ContactsList)
.HasForeignKey(e => e.UserId);
}
}
}

21
qtc-net-server/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["qtc-net-server/qtc-net-server.csproj", "qtc-net-server/"]
RUN dotnet restore "qtc-net-server/qtc-net-server.csproj"
COPY . .
WORKDIR "/src/qtc-net-server"
RUN dotnet build "qtc-net-server.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "qtc-net-server.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "qtc-net-server.dll"]

View File

@ -0,0 +1,8 @@
namespace qtc_api.Dtos.Room
{
public class RoomDto
{
public string Name { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,8 @@
namespace qtc_api.Dtos.User
{
public class UserConnectionDto
{
public Models.User? ConnectedUser { get; set; }
public string? ConnectionId { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace qtc_api.Dtos.User
{
public class UserDto
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,14 @@
namespace qtc_api.Dtos.User
{
public class UserInformationDto
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string ProfilePicture { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
public DateTime CreatedAt { get; set; } = new DateTime();
public int Status { get; set; } = 0;
}
}

View File

@ -0,0 +1,9 @@
namespace qtc_api.Dtos.User
{
public class UserLoginDto
{
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool RememberMe { get; set; } = false;
}
}

View File

@ -0,0 +1,8 @@
namespace qtc_api.Dtos.User
{
public class UserStatusDto
{
public string Id { get; set; } = string.Empty;
public int Status { get; set; } = 0;
}
}

View File

@ -0,0 +1,10 @@
namespace qtc_api.Dtos.User
{
public class UserUpdateInformationDto
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,167 @@
using System.Text.Json;
namespace qtc_gateway.Hubs
{
[Authorize]
public class ChatHub : Hub
{
private IUserService _userService;
private ILogger<ChatHub> _logger;
private static List<UserConnectionDto> ConnectedUsers = new List<UserConnectionDto>();
private static List<User> OnlineUsers = new List<User>();
public ChatHub(IUserService userService, ILogger<ChatHub> logger)
{
_userService = userService;
_logger = logger;
}
public override async Task OnDisconnectedAsync(Exception? ex)
{
Log("Client Disconnected.");
var connection = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
if (connection != null)
{
var user = OnlineUsers.FirstOrDefault(x => x.Id == connection!.ConnectedUser!.Id);
if (user != null)
{
await LogoutAsync(user!);
}
}
}
[HubMethodName("l")]
public async Task LoginAsync(User user)
{
await Clients.All.SendAsync("rm", $"[SERVER] User {user.Username} Is Now Online");
Log($"User {user.Username} Has Logged In");
var statusDto = new UserStatusDto { Id = user.Id, Status = 1 };
await _userService.UpdateStatus(statusDto);
ConnectedUsers.Add(new UserConnectionDto() { ConnectedUser = user, ConnectionId = Context.ConnectionId });
OnlineUsers.Add(user);
ServerConfig serverConfig = JsonSerializer.Deserialize<ServerConfig>(JsonDocument.Parse(File.ReadAllText("./ServerConfig.json")));
await Clients.Client(ConnectedUsers.FirstOrDefault(e => e.ConnectionId == Context.ConnectionId)!.ConnectionId!).SendAsync("rc", serverConfig);
await Clients.All.SendAsync("cf", "rul");
}
[HubMethodName("us")]
public async Task UpdateStatusAsync(User user, int status)
{
var statusDto = new UserStatusDto { Id = user.Id, Status = status };
await _userService.UpdateStatus(statusDto);
await Clients.All.SendAsync("cf", "rul");
}
[HubMethodName("jl")]
public async Task JoinHubAsync(User user)
{
await Groups.AddToGroupAsync(Context.ConnectionId, "LOBBY");
await Clients.Group("LOBBY").SendAsync("rm", $"[SERVER] User {user.Username} Has Joined The Lobby");
Log($"User {user.Username} Has Joined The Lobby");
}
[HubMethodName("ll")]
public async Task LeaveLobbyAsync(User user)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "LOBBY");
await Clients.Group("LOBBY").SendAsync("rm", $"[SERVER] User {user.Username} Has Left The Lobby");
Log($"User {user.Username} Has Left The Lobby");
}
[HubMethodName("jr")]
public async Task JoinRoomAsync(User user, Room room)
{
await Groups.AddToGroupAsync(Context.ConnectionId, room.Id);
await Clients.Group(room.Id).SendAsync("rm", $"[SERVER] User {user.Username} Has Joined The Room");
Log($"User {user.Username} Has Joined {room.Name}");
}
[HubMethodName("lr")]
public async Task LeaveRoomAsync(User user, Room room)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.Id);
await Clients.Group(room.Id).SendAsync("rm", $"[SERVER] User {user.Username} Has Left The Room");
Log($"User {user.Username} Has Left {room.Name}");
}
[HubMethodName("hdr")]
public async Task HandleDeletedRoomAsync(Room room)
{
await Clients.Group(room.Id).SendAsync("rm", $"[SERVER] This Room Has Been Deleted By An Administrator.");
await Clients.Group(room.Id).SendAsync("cf", "rtl");
await Clients.All.SendAsync("cf", "rr");
}
[HubMethodName("rcl")]
public async Task RefreshContactsListForUser(UserInformationDto user, User execUser)
{
var connection = ConnectedUsers.FirstOrDefault(e => e.ConnectedUser.Id == user.Id);
var connection2 = ConnectedUsers.FirstOrDefault(e => e.ConnectedUser.Id == execUser.Id);
if (connection != null && connection2 != null)
{
await Clients.Client(connection.ConnectionId).SendAsync("cf", "rcl");
await Clients.Client(connection2.ConnectionId).SendAsync("cf", "rcl");
return;
}
}
[HubMethodName("s")]
public async Task SendMessageAsync(User user, Message message, bool IsLobbyMsg, Room room = null!)
{
if(IsLobbyMsg == true) { await Clients.Group("LOBBY").SendAsync("rm", $"[{user.Username}] {message.Content}"); return; }
await Clients.Group(room.Id).SendAsync("rm", $"[{user.Username}] {message.Content}");
}
[HubMethodName("sdm")]
public async Task SendDirectMessageAsync(User user, UserInformationDto userToMsg, Message message)
{
// send direct message directly to connected user
var connection = ConnectedUsers.FirstOrDefault(e => e.ConnectedUser.Id == userToMsg.Id);
if (connection != null)
{
UserInformationDto userInformationDto = new UserInformationDto { Id = user.Id, Username = user.Username, Bio = user.Bio, Role = user.Role, Status = user.Status, CreatedAt = user.CreatedAt, DateOfBirth = user.DateOfBirth, ProfilePicture = user.ProfilePicture };
await Clients.Client(connection.ConnectionId).SendAsync("rdm", message, userInformationDto);
return;
}
}
private async Task LogoutAsync(User user)
{
await Clients.All.SendAsync("rm", $"[SERVER] User {user.Username} Is Now Offline");
Log($"User {user.Username} Has Logged Out");
var statusDto = new UserStatusDto { Id = user.Id, Status = 0 };
await _userService.UpdateStatus(statusDto);
Log($"Set User {user.Username} To Offline");
await Clients.All.SendAsync("cf", "rul");
OnlineUsers.Remove(user);
var connection = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
ConnectedUsers.Remove(connection!);
}
private void Log(string message)
{
_logger.LogInformation(message);
}
}
}

View File

@ -0,0 +1,133 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250605232504_InitialData")]
partial class InitialData
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Contacts");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("ID");
b.ToTable("ValidRefreshTokens");
});
modelBuilder.Entity("qtc_api.Models.Room", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatorId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Rooms");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace qtc_api.Migrations
{
/// <inheritdoc />
public partial class InitialData : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Contacts",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false),
OwnerId = table.Column<string>(type: "longtext", nullable: false),
UserId = table.Column<string>(type: "longtext", nullable: false),
Status = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Contacts", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Rooms",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
CreatorId = table.Column<string>(type: "longtext", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Rooms", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false),
Username = table.Column<string>(type: "longtext", nullable: false),
ProfilePicture = table.Column<string>(type: "longtext", nullable: false),
Bio = table.Column<string>(type: "longtext", nullable: false),
Role = table.Column<string>(type: "longtext", nullable: false),
PasswordHash = table.Column<string>(type: "longtext", nullable: false),
Email = table.Column<string>(type: "longtext", nullable: false),
DateOfBirth = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "ValidRefreshTokens",
columns: table => new
{
ID = table.Column<string>(type: "varchar(255)", nullable: false),
UserID = table.Column<string>(type: "longtext", nullable: false),
Token = table.Column<string>(type: "longtext", nullable: false),
Expires = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ValidRefreshTokens", x => x.ID);
})
.Annotation("MySQL:Charset", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Contacts");
migrationBuilder.DropTable(
name: "Rooms");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "ValidRefreshTokens");
}
}
}

View File

@ -0,0 +1,136 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250606000415_AddExtraStatus")]
partial class AddExtraStatus
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerStatus")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("UserStatus")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Contacts");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("ID");
b.ToTable("ValidRefreshTokens");
});
modelBuilder.Entity("qtc_api.Models.Room", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatorId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Rooms");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace qtc_api.Migrations
{
/// <inheritdoc />
public partial class AddExtraStatus : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Status",
table: "Contacts",
newName: "UserStatus");
migrationBuilder.AddColumn<int>(
name: "OwnerStatus",
table: "Contacts",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OwnerStatus",
table: "Contacts");
migrationBuilder.RenameColumn(
name: "UserStatus",
table: "Contacts",
newName: "Status");
}
}
}

View File

@ -0,0 +1,160 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250608203855_RefreshTokensForeignKey")]
partial class RefreshTokensForeignKey
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("OwnerStatus")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("UserStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("Contact");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("ID");
b.HasIndex("UserID");
b.ToTable("RefreshToken");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("User");
});
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.HasOne("qtc_api.Models.User", "Owner")
.WithMany("ContactsMade")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("qtc_api.Models.User", "User")
.WithMany("ContactsList")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.HasOne("qtc_api.Models.User", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Navigation("ContactsList");
b.Navigation("ContactsMade");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,226 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace qtc_api.Migrations
{
/// <inheritdoc />
public partial class RefreshTokensForeignKey : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Rooms");
migrationBuilder.DropPrimaryKey(
name: "PK_ValidRefreshTokens",
table: "ValidRefreshTokens");
migrationBuilder.DropPrimaryKey(
name: "PK_Users",
table: "Users");
migrationBuilder.DropPrimaryKey(
name: "PK_Contacts",
table: "Contacts");
migrationBuilder.RenameTable(
name: "ValidRefreshTokens",
newName: "RefreshToken");
migrationBuilder.RenameTable(
name: "Users",
newName: "User");
migrationBuilder.RenameTable(
name: "Contacts",
newName: "Contact");
migrationBuilder.AlterColumn<string>(
name: "UserID",
table: "RefreshToken",
type: "varchar(255)",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext");
migrationBuilder.AlterColumn<string>(
name: "UserId",
table: "Contact",
type: "varchar(255)",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext");
migrationBuilder.AlterColumn<string>(
name: "OwnerId",
table: "Contact",
type: "varchar(255)",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext");
migrationBuilder.AddPrimaryKey(
name: "PK_RefreshToken",
table: "RefreshToken",
column: "ID");
migrationBuilder.AddPrimaryKey(
name: "PK_User",
table: "User",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_Contact",
table: "Contact",
column: "Id");
migrationBuilder.CreateIndex(
name: "IX_RefreshToken_UserID",
table: "RefreshToken",
column: "UserID");
migrationBuilder.CreateIndex(
name: "IX_Contact_OwnerId",
table: "Contact",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_Contact_UserId",
table: "Contact",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_Contact_User_OwnerId",
table: "Contact",
column: "OwnerId",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Contact_User_UserId",
table: "Contact",
column: "UserId",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RefreshToken_User_UserID",
table: "RefreshToken",
column: "UserID",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Contact_User_OwnerId",
table: "Contact");
migrationBuilder.DropForeignKey(
name: "FK_Contact_User_UserId",
table: "Contact");
migrationBuilder.DropForeignKey(
name: "FK_RefreshToken_User_UserID",
table: "RefreshToken");
migrationBuilder.DropPrimaryKey(
name: "PK_User",
table: "User");
migrationBuilder.DropPrimaryKey(
name: "PK_RefreshToken",
table: "RefreshToken");
migrationBuilder.DropIndex(
name: "IX_RefreshToken_UserID",
table: "RefreshToken");
migrationBuilder.DropPrimaryKey(
name: "PK_Contact",
table: "Contact");
migrationBuilder.DropIndex(
name: "IX_Contact_OwnerId",
table: "Contact");
migrationBuilder.DropIndex(
name: "IX_Contact_UserId",
table: "Contact");
migrationBuilder.RenameTable(
name: "User",
newName: "Users");
migrationBuilder.RenameTable(
name: "RefreshToken",
newName: "ValidRefreshTokens");
migrationBuilder.RenameTable(
name: "Contact",
newName: "Contacts");
migrationBuilder.AlterColumn<string>(
name: "UserID",
table: "ValidRefreshTokens",
type: "longtext",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255)");
migrationBuilder.AlterColumn<string>(
name: "UserId",
table: "Contacts",
type: "longtext",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255)");
migrationBuilder.AlterColumn<string>(
name: "OwnerId",
table: "Contacts",
type: "longtext",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255)");
migrationBuilder.AddPrimaryKey(
name: "PK_Users",
table: "Users",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_ValidRefreshTokens",
table: "ValidRefreshTokens",
column: "ID");
migrationBuilder.AddPrimaryKey(
name: "PK_Contacts",
table: "Contacts",
column: "Id");
migrationBuilder.CreateTable(
name: "Rooms",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatorId = table.Column<string>(type: "longtext", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Rooms", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
}
}
}

View File

@ -0,0 +1,181 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250608204114_AddRemainingRelations")]
partial class AddRemainingRelations
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("OwnerStatus")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("UserStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("Contact");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("ID");
b.HasIndex("UserID");
b.ToTable("RefreshToken");
});
modelBuilder.Entity("qtc_api.Models.Room", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatorId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Room");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("User");
});
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.HasOne("qtc_api.Models.User", "Owner")
.WithMany("ContactsMade")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("qtc_api.Models.User", "User")
.WithMany("ContactsList")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.HasOne("qtc_api.Models.User", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Navigation("ContactsList");
b.Navigation("ContactsMade");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace qtc_api.Migrations
{
/// <inheritdoc />
public partial class AddRemainingRelations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Room",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false),
Name = table.Column<string>(type: "longtext", nullable: false),
CreatorId = table.Column<string>(type: "longtext", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Room", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Room");
}
}
}

View File

@ -0,0 +1,181 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250608204625_ValidRefreshTokens")]
partial class ValidRefreshTokens
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("OwnerStatus")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("UserStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("Contacts");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("ID");
b.HasIndex("UserID");
b.ToTable("ValidRefreshTokens");
});
modelBuilder.Entity("qtc_api.Models.Room", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatorId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Rooms");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.HasOne("qtc_api.Models.User", "Owner")
.WithMany("ContactsMade")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("qtc_api.Models.User", "User")
.WithMany("ContactsList")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.HasOne("qtc_api.Models.User", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Navigation("ContactsList");
b.Navigation("ContactsMade");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,224 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace qtc_api.Migrations
{
/// <inheritdoc />
public partial class ValidRefreshTokens : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Contact_User_OwnerId",
table: "Contact");
migrationBuilder.DropForeignKey(
name: "FK_Contact_User_UserId",
table: "Contact");
migrationBuilder.DropForeignKey(
name: "FK_RefreshToken_User_UserID",
table: "RefreshToken");
migrationBuilder.DropPrimaryKey(
name: "PK_User",
table: "User");
migrationBuilder.DropPrimaryKey(
name: "PK_Room",
table: "Room");
migrationBuilder.DropPrimaryKey(
name: "PK_RefreshToken",
table: "RefreshToken");
migrationBuilder.DropPrimaryKey(
name: "PK_Contact",
table: "Contact");
migrationBuilder.RenameTable(
name: "User",
newName: "Users");
migrationBuilder.RenameTable(
name: "Room",
newName: "Rooms");
migrationBuilder.RenameTable(
name: "RefreshToken",
newName: "ValidRefreshTokens");
migrationBuilder.RenameTable(
name: "Contact",
newName: "Contacts");
migrationBuilder.RenameIndex(
name: "IX_RefreshToken_UserID",
table: "ValidRefreshTokens",
newName: "IX_ValidRefreshTokens_UserID");
migrationBuilder.RenameIndex(
name: "IX_Contact_UserId",
table: "Contacts",
newName: "IX_Contacts_UserId");
migrationBuilder.RenameIndex(
name: "IX_Contact_OwnerId",
table: "Contacts",
newName: "IX_Contacts_OwnerId");
migrationBuilder.AddPrimaryKey(
name: "PK_Users",
table: "Users",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_Rooms",
table: "Rooms",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_ValidRefreshTokens",
table: "ValidRefreshTokens",
column: "ID");
migrationBuilder.AddPrimaryKey(
name: "PK_Contacts",
table: "Contacts",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Contacts_Users_OwnerId",
table: "Contacts",
column: "OwnerId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Contacts_Users_UserId",
table: "Contacts",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ValidRefreshTokens_Users_UserID",
table: "ValidRefreshTokens",
column: "UserID",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Contacts_Users_OwnerId",
table: "Contacts");
migrationBuilder.DropForeignKey(
name: "FK_Contacts_Users_UserId",
table: "Contacts");
migrationBuilder.DropForeignKey(
name: "FK_ValidRefreshTokens_Users_UserID",
table: "ValidRefreshTokens");
migrationBuilder.DropPrimaryKey(
name: "PK_ValidRefreshTokens",
table: "ValidRefreshTokens");
migrationBuilder.DropPrimaryKey(
name: "PK_Users",
table: "Users");
migrationBuilder.DropPrimaryKey(
name: "PK_Rooms",
table: "Rooms");
migrationBuilder.DropPrimaryKey(
name: "PK_Contacts",
table: "Contacts");
migrationBuilder.RenameTable(
name: "ValidRefreshTokens",
newName: "RefreshToken");
migrationBuilder.RenameTable(
name: "Users",
newName: "User");
migrationBuilder.RenameTable(
name: "Rooms",
newName: "Room");
migrationBuilder.RenameTable(
name: "Contacts",
newName: "Contact");
migrationBuilder.RenameIndex(
name: "IX_ValidRefreshTokens_UserID",
table: "RefreshToken",
newName: "IX_RefreshToken_UserID");
migrationBuilder.RenameIndex(
name: "IX_Contacts_UserId",
table: "Contact",
newName: "IX_Contact_UserId");
migrationBuilder.RenameIndex(
name: "IX_Contacts_OwnerId",
table: "Contact",
newName: "IX_Contact_OwnerId");
migrationBuilder.AddPrimaryKey(
name: "PK_RefreshToken",
table: "RefreshToken",
column: "ID");
migrationBuilder.AddPrimaryKey(
name: "PK_User",
table: "User",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_Room",
table: "Room",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_Contact",
table: "Contact",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Contact_User_OwnerId",
table: "Contact",
column: "OwnerId",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Contact_User_UserId",
table: "Contact",
column: "UserId",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RefreshToken_User_UserID",
table: "RefreshToken",
column: "UserID",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -0,0 +1,178 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using qtc_api.Data;
#nullable disable
namespace qtc_api.Migrations
{
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("OwnerId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("OwnerStatus")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<int>("UserStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("UserId");
b.ToTable("Contacts");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.Property<string>("ID")
.HasColumnType("varchar(255)");
b.Property<DateTime>("Expires")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserID")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("ID");
b.HasIndex("UserID");
b.ToTable("ValidRefreshTokens");
});
modelBuilder.Entity("qtc_api.Models.Room", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatorId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Rooms");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("Bio")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProfilePicture")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("qtc_api.Models.Contact", b =>
{
b.HasOne("qtc_api.Models.User", "Owner")
.WithMany("ContactsMade")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("qtc_api.Models.User", "User")
.WithMany("ContactsList")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.RefreshToken", b =>
{
b.HasOne("qtc_api.Models.User", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("qtc_api.Models.User", b =>
{
b.Navigation("ContactsList");
b.Navigation("ContactsMade");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,16 @@
namespace qtc_api.Models
{
public class Contact
{
public string Id { get; set; } = string.Empty;
public string OwnerId { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public ContactStatus OwnerStatus { get; set; } = ContactStatus.AwaitingApprovalFromOther;
public ContactStatus UserStatus { get; set; } = ContactStatus.AwaitingApprovalFromSelf;
public virtual User? Owner { get; }
public virtual User? User { get; }
public enum ContactStatus { AwaitingApprovalFromOther = 0, AwaitingApprovalFromSelf = 1, Accepted = 2 }
}
}

View File

@ -0,0 +1,10 @@
namespace qtc_api.Models
{
public class Message
{
public string Id { get; set; } = string.Empty;
public string AuthorId { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,12 @@
namespace qtc_api.Models
{
public class RefreshToken
{
public string ID { get; set; } = string.Empty;
public string UserID { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
public DateTime Expires { get; set; }
public virtual User User { get; } = null!;
}
}

View File

@ -0,0 +1,10 @@
namespace qtc_api.Models
{
public class Room
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string CreatorId { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,11 @@
namespace qtc_api.Models
{
public class ServerConfig
{
public string? Name { get; set; }
public string? Description { get; set; }
public string? AdminUserId { get; set; }
public bool IsDown { get; set; }
public string? IsDownMessage { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace qtc_api.Models
{
public class ServiceResponse<T>
{
public T? Data { get; set; }
public bool Success { get; set; } = false;
public string Message { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,20 @@
namespace qtc_api.Models
{
public class User
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string ProfilePicture { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; }
public DateTime CreatedAt { get; set; }
public int Status { get; set; } = 0;
public virtual IEnumerable<RefreshToken>? RefreshTokens { get; }
public virtual IEnumerable<Contact>? ContactsMade { get; }
public virtual IEnumerable<Contact>? ContactsList { get; }
}
}

63
qtc-net-server/Program.cs Normal file
View File

@ -0,0 +1,63 @@
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.AspNetCore.SignalR;
global using Microsoft.AspNetCore.Authorization;
global using qtc_api.Models;
global using qtc_api.Data;
global using qtc_api.Dtos.User;
global using qtc_api.Dtos.Room;
global using qtc_api.Services.UserService;
global using Microsoft.IdentityModel.Tokens;
global using System.Text;
global using qtc_api.Services.TokenService;
global using qtc_gateway.Hubs;
using qtc_api.Services.RoomService;
using qtc_api.Services.ContactService;
using Microsoft.EntityFrameworkCore.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddDbContext<DataContext>(options =>
{
if (builder.Environment.IsProduction()) options.UseMySQL(builder.Configuration.GetConnectionString("DefaultConnection"));
else options.UseSqlite(builder.Configuration.GetConnectionString("DevelopmentConnection"));
// ignore pending model changes warning
options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
});
builder.Services.AddSignalR();
builder.Services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
});
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IRoomService, RoomService>();
builder.Services.AddScoped<IContactService, ContactService>();
var app = builder.Build();
using var scope = app.Services.CreateScope();
await scope.ServiceProvider.GetRequiredService<DataContext>().Database.EnsureCreatedAsync();
app.UseAuthorization();
app.MapControllers();
app.MapHub<ChatHub>("/chat");
app.Run();

View File

@ -0,0 +1,37 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5268"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:26136",
"sslPort": 0
}
}
}

View File

@ -0,0 +1,7 @@
{
"Name": "QtC.NET Server",
"Description": "This is a QtC.NET Server.",
"AdminUserId": "523736357658921388",
"IsDown": false,
"IsDownMessage": "This server is currently down. Please try again later."
}

View File

@ -0,0 +1,118 @@
namespace qtc_api.Services.ContactService
{
public class ContactService : IContactService
{
private readonly DataContext _dataContext;
public ContactService(DataContext dataContext)
{
_dataContext = dataContext;
}
public async Task<ServiceResponse<Contact>> CreateContact(string ownerId, string userId)
{
var serviceResponse = new ServiceResponse<Contact>();
var rnd = LongRandom(1, 900000000000000000, new Random());
var contact = new Contact()
{
Id = rnd.ToString(),
OwnerId = ownerId,
UserId = userId,
OwnerStatus = Contact.ContactStatus.AwaitingApprovalFromOther,
UserStatus = Contact.ContactStatus.AwaitingApprovalFromSelf,
};
await _dataContext.Contacts.AddAsync(contact);
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = contact;
return serviceResponse;
}
public async Task<ServiceResponse<bool>> UpdateContactStatus(string ownerId, string userId, Contact.ContactStatus ownerStatus, Contact.ContactStatus userStatus)
{
var serviceResponse = new ServiceResponse<bool>();
var contact = await _dataContext.Contacts.FirstOrDefaultAsync(e => (e.OwnerId == ownerId || e.UserId == userId) || (e.OwnerId == userId || e.UserId == ownerId));
if(contact != null)
{
contact.OwnerStatus = ownerStatus;
contact.UserStatus = userStatus;
serviceResponse.Success = true;
}
else serviceResponse.Success = false;
await _dataContext.SaveChangesAsync();
return serviceResponse;
}
public async Task<ServiceResponse<Contact>> DeleteContact(string ownerId, string userId)
{
var serviceResponse = new ServiceResponse<Contact>();
var contact = await _dataContext.Contacts.FirstOrDefaultAsync(x => (x.OwnerId == ownerId || x.UserId == userId) || (x.OwnerId == userId || x.UserId == ownerId));
if (contact != null)
{
var result = _dataContext.Contacts.Remove(contact);
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = result.Entity;
return serviceResponse;
}
else
{
serviceResponse.Success = false;
serviceResponse.Data = null;
serviceResponse.Message = "Contact Not Found";
return serviceResponse;
}
}
public async Task<ServiceResponse<Contact>> GetContactById(string id)
{
var serviceResponse = new ServiceResponse<Contact>();
var contact = await _dataContext.Contacts.FirstOrDefaultAsync(x => x.Id == id);
if (contact == null) { serviceResponse.Success = false; serviceResponse.Message = "Contact Does Not Exist."; }
else { serviceResponse.Success = true; serviceResponse.Data = contact; }
return serviceResponse;
}
public async Task<ServiceResponse<List<Contact>>> GetUserContacts(User user)
{
var serviceResponse = new ServiceResponse<List<Contact>>();
var contactsMade = await _dataContext.Contacts.Where(x => x.OwnerId == user.Id).ToListAsync();
var contactsList = await _dataContext.Contacts.Where(x => x.UserId == user.Id).ToListAsync();
var contactsCombined = new List<Contact>();
foreach (var contact in contactsMade)
contactsCombined.Add(contact); // all contacts the user has made
foreach (var contact in contactsList)
contactsCombined.Add(contact); // all contacts the user has received
if (contactsCombined.Count == 0) { serviceResponse.Success = true; serviceResponse.Message = "User Has No Contacts."; }
else { serviceResponse.Success = true; serviceResponse.Data = contactsCombined; }
return serviceResponse;
}
private long LongRandom(long min, long max, Random rnd)
{
long result = rnd.Next((int)(min >> 32), (int)(max >> 32));
result = result << 32;
result = result | (long)rnd.Next((int)min, (int)max);
return result;
}
}
}

View File

@ -0,0 +1,11 @@
namespace qtc_api.Services.ContactService
{
public interface IContactService
{
public Task<ServiceResponse<Contact>> CreateContact(string ownerId, string userId);
public Task<ServiceResponse<bool>> UpdateContactStatus(string ownerId, string userId, Contact.ContactStatus ownerStatus, Contact.ContactStatus userStatus);
public Task<ServiceResponse<List<Contact>>> GetUserContacts(User user);
public Task<ServiceResponse<Contact>> GetContactById(string id);
public Task<ServiceResponse<Contact>> DeleteContact(string ownerId, string userId);
}
}

View File

@ -0,0 +1,11 @@
namespace qtc_api.Services.RoomService
{
public interface IRoomService
{
public Task<ServiceResponse<Room>> GetRoom(string id);
public Task<ServiceResponse<List<Room>>> GetAllRooms();
public Task<ServiceResponse<Room>> AddRoom(string userId, RoomDto room);
public Task<ServiceResponse<Room>> UpdateRoom(Room room);
public Task<ServiceResponse<Room>> DeleteRoom(string id);
}
}

View File

@ -0,0 +1,107 @@
namespace qtc_api.Services.RoomService
{
public class RoomService : IRoomService
{
private readonly DataContext _dataContext;
private long idMax = 900000000000000000;
public RoomService(DataContext dataContext)
{
_dataContext = dataContext;
}
public async Task<ServiceResponse<Room>> AddRoom(string userId, RoomDto room)
{
var serviceResponse = new ServiceResponse<Room>();
var roomList = await _dataContext.Rooms.ToListAsync();
var roomToAdd = new Room();
Random rnd = new Random();
roomToAdd.Id = LongRandom(1, idMax, rnd).ToString();
roomToAdd.Name = room.Name;
roomToAdd.CreatorId = userId;
roomToAdd.CreatedAt = DateTime.UtcNow;
var cRoom = await _dataContext.Rooms.FirstOrDefaultAsync(x => x.Name == roomToAdd.Name);
if (cRoom == null)
{
await _dataContext.Rooms.AddAsync(roomToAdd);
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = roomToAdd;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Room already exists.";
}
return serviceResponse;
}
public async Task<ServiceResponse<Room>> DeleteRoom(string id)
{
var serviceResponse = new ServiceResponse<Room>();
var room = await _dataContext.Rooms.FirstOrDefaultAsync(x => x.Id == id);
if (room != null)
{
_dataContext.Rooms.Remove(room);
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = room;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Room not found.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<Room>>> GetAllRooms()
{
var serviceResponse = new ServiceResponse<List<Room>>();
var rooms = await _dataContext.Rooms.ToListAsync();
serviceResponse.Success = true;
serviceResponse.Data = rooms;
return serviceResponse;
}
public async Task<ServiceResponse<Room>> GetRoom(string id)
{
var serviceResponse = new ServiceResponse<Room>();
var room = await _dataContext.Rooms.FirstOrDefaultAsync(x => x.Id == id);
if (room != null)
{
serviceResponse.Success = true;
serviceResponse.Data = room;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Room not found.";
}
return serviceResponse;
}
public Task<ServiceResponse<Room>> UpdateRoom(Room room)
{
throw new NotImplementedException();
}
private long LongRandom(long min, long max, Random rnd)
{
long result = rnd.Next((int)(min >> 32), (int)(max >> 32));
result = result << 32;
result = result | (long)rnd.Next((int)min, (int)max);
return result;
}
}
}

View File

@ -0,0 +1,10 @@
namespace qtc_api.Services.TokenService
{
public interface ITokenService
{
public Task<ServiceResponse<string>> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken = true, bool remember = false);
public Task<ServiceResponse<bool>> ValidateAccessToken(string accessToken);
public Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken);
public ServiceResponse<TokenValidationParameters> GetValidationParams();
}
}

View File

@ -0,0 +1,176 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace qtc_api.Services.TokenService
{
public class TokenService : ITokenService
{
private readonly IConfiguration _configuration;
private readonly DataContext _dataContext;
public TokenService(IConfiguration configuration, DataContext dataContext)
{
_configuration = configuration;
_dataContext = dataContext;
}
public async Task<ServiceResponse<string>> GenerateAccessTokenAndRefreshToken(User user, bool generateRefToken, bool remember)
{
var serviceResponse = new ServiceResponse<string>();
// Generate JWT Access Token
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.Hash, user.Id),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value!));
var issuer = _configuration["Jwt:Issuer"];
var audience = _configuration["Jwt:Audience"];
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds
);
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
serviceResponse.Data = jwt;
// Generate and Store Refresh Token
if (generateRefToken)
{
var random = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(random);
}
RefreshToken refToken = new RefreshToken()
{
ID = LongRandom(1, 900000000000000000, new Random()).ToString(),
UserID = user.Id,
Token = Convert.ToBase64String(random)
};
if (remember) refToken.Expires = DateTime.UtcNow.AddDays(7);
else refToken.Expires = DateTime.UtcNow.AddDays(1);
_dataContext.ValidRefreshTokens.Add(refToken);
await _dataContext.SaveChangesAsync();
serviceResponse.Message = refToken.Token;
}
serviceResponse.Success = true;
return serviceResponse;
}
public async Task<ServiceResponse<string>> ValidateRefreshToken(string refreshToken)
{
var serviceResponse = new ServiceResponse<string>();
var dbRefresh = await _dataContext.ValidRefreshTokens.FirstOrDefaultAsync(x => x.Token == refreshToken);
if (dbRefresh != null)
{
if (dbRefresh.Expires < DateTime.UtcNow)
{
serviceResponse.Success = false;
serviceResponse.Message = "Refresh Token Expired.";
// Handle Expired Refresh Token
_dataContext.ValidRefreshTokens.Remove(dbRefresh);
await _dataContext.SaveChangesAsync();
return serviceResponse;
}
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == dbRefresh.UserID);
if (user != null && dbRefresh.UserID == user.Id)
{
var token = await GenerateAccessTokenAndRefreshToken(user, false, false);
if (token != null)
{
serviceResponse.Success = true;
serviceResponse.Data = token.Data;
}
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Requesting User ID and the associated Refresh Token's User ID does not match.";
}
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "Invalid Refresh Token.";
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> ValidateAccessToken(string accessToken)
{
var serviceResponse = new ServiceResponse<bool>();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParams = GetValidationParams();
TokenValidationResult result = await tokenHandler.ValidateTokenAsync(accessToken, validationParams.Data);
if (result.IsValid)
{
serviceResponse.Success = true;
serviceResponse.Data = true;
return serviceResponse;
}
else
{
serviceResponse.Success = true;
serviceResponse.Data = false;
return serviceResponse;
}
}
public ServiceResponse<TokenValidationParameters> GetValidationParams()
{
var serviceResponse = new ServiceResponse<TokenValidationParameters>();
serviceResponse.Data = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!))
};
return serviceResponse;
}
private long LongRandom(long min, long max, Random rnd)
{
long result = rnd.Next((int)(min >> 32), (int)(max >> 32));
result = result << 32;
result = result | (long)rnd.Next((int)min, (int)max);
return result;
}
}
}

View File

@ -0,0 +1,19 @@
using qtc_api.Dtos.User;
namespace qtc_api.Services.UserService
{
public interface IUserService
{
public Task<ServiceResponse<User>> GetUserById(string id);
public Task<ServiceResponse<UserInformationDto>> GetUserInformationById(string id);
public Task<ServiceResponse<User>> GetUserByEmail(string email);
public Task<ServiceResponse<List<UserInformationDto>>> GetAllUsers();
public Task<ServiceResponse<List<UserInformationDto>>> GetAllOnlineUsers();
public Task<ServiceResponse<User>> AddUser(UserDto userReq);
public Task<ServiceResponse<UserInformationDto>> UpdateUserInfo(UserUpdateInformationDto request);
public Task<ServiceResponse<string>> UpdateUserPic(string userId, IFormFile file);
public Task<ServiceResponse<FileContentResult>> GetUserPic(string userId);
public Task<ServiceResponse<UserStatusDto>> UpdateStatus(UserStatusDto request);
public Task<ServiceResponse<User>> DeleteUser(string id);
}
}

View File

@ -0,0 +1,340 @@
namespace qtc_api.Services.UserService
{
public class UserService : IUserService
{
private readonly IConfiguration _configuration;
private readonly DataContext _dataContext;
private long idMax = 900000000000000000;
public UserService(IConfiguration configuration, DataContext dataContext)
{
_configuration = configuration;
_dataContext = dataContext;
}
public async Task<ServiceResponse<User>> AddUser(UserDto userReq)
{
var serviceResponse = new ServiceResponse<User>();
var userList = await _dataContext.Users.ToListAsync();
var user = new User();
Random rnd = new Random();
var id = LongRandom(1, idMax, rnd);
user.Id = id.ToString();
user.Username = userReq.Username;
user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(userReq.Password);
user.Email = userReq.Email;
user.DateOfBirth = userReq.DateOfBirth;
user.Role = _configuration["Jwt:DefaultUserRole"]!;
user.CreatedAt = DateTime.UtcNow;
try
{
var cUser = userList.FirstOrDefault(x => x.Email == user.Email);
if (cUser != null)
{
serviceResponse.Success = false;
serviceResponse.Message = "User with that email already exists.";
return serviceResponse;
}
else
{
await _dataContext.Users.AddAsync(user);
await _dataContext.SaveChangesAsync();
userList = await _dataContext.Users.ToListAsync();
if (userList.Contains(user))
{
var createdUser = await _dataContext.Users.FindAsync(user.Id);
serviceResponse.Success = true;
serviceResponse.Data = createdUser;
return serviceResponse;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "User not created.";
return serviceResponse;
}
}
} catch (Exception ex)
{
serviceResponse.Success = false;
serviceResponse.Message = ex.Message;
return serviceResponse;
}
}
public async Task<ServiceResponse<User>> DeleteUser(string id)
{
var serviceResponse = new ServiceResponse<User>();
var user = _dataContext.Users.FirstOrDefault(x => x.Id == id)!;
if(user != null)
{
_dataContext.Users.Remove(user);
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = user;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "User not found.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetAllUsers()
{
var serviceResponse = new ServiceResponse<List<UserInformationDto>>();
var userList = await _dataContext.Users.ToListAsync();
var userInfoList = new List<UserInformationDto>();
foreach(var user in userList)
{
var x = new UserInformationDto();
x.Id = user.Id;
x.Username = user.Username;
x.ProfilePicture = user.ProfilePicture;
x.Role = user.Role;
x.Bio = user.Bio;
x.DateOfBirth = user.DateOfBirth;
userInfoList.Add(x);
}
serviceResponse.Success = true;
serviceResponse.Data = userInfoList;
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetAllOnlineUsers()
{
var serviceResponse = new ServiceResponse<List<UserInformationDto>>();
var onlineUsers = new List<UserInformationDto>();
await _dataContext.Users.ForEachAsync(user =>
{
if (user.Status == 1 || user.Status == 2 || user.Status == 3)
{
var x = new UserInformationDto();
x.Id = user.Id;
x.Username = user.Username;
x.ProfilePicture = user.ProfilePicture;
x.Role = user.Role;
x.Bio = user.Bio;
x.Status = user.Status;
x.DateOfBirth = user.DateOfBirth;
x.CreatedAt = user.CreatedAt;
onlineUsers.Add(x);
}
});
serviceResponse.Success = true;
serviceResponse.Data = onlineUsers;
return serviceResponse;
}
public async Task<ServiceResponse<User>> GetUserById(string id)
{
var serviceResponse = new ServiceResponse<User>();
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == id);
serviceResponse.Success = true;
serviceResponse.Data = user;
return serviceResponse;
}
public async Task<ServiceResponse<UserInformationDto>> GetUserInformationById(string id)
{
var serviceResponse = new ServiceResponse<UserInformationDto>();
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == id);
if(user != null)
{
var dto = new UserInformationDto();
dto.Id = user.Id;
dto.Username = user.Username;
dto.ProfilePicture = user.ProfilePicture;
dto.Role = user.Role;
dto.Bio = user.Bio;
dto.DateOfBirth = user.DateOfBirth;
dto.CreatedAt = user.CreatedAt;
dto.Status = user.Status;
serviceResponse.Success = true;
serviceResponse.Data = dto;
return serviceResponse;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "User not found.";
return serviceResponse;
}
}
public async Task<ServiceResponse<User>> GetUserByEmail(string email)
{
var serviceResponse = new ServiceResponse<User>();
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Email == email);
serviceResponse.Success = true;
serviceResponse.Data = user;
return serviceResponse;
}
public async Task<ServiceResponse<UserInformationDto>> UpdateUserInfo(UserUpdateInformationDto request)
{
var serviceResponse = new ServiceResponse<UserInformationDto>();
var dbUser = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == request.Id);
if(dbUser != null)
{
dbUser.Username = request.Username;
dbUser.Bio = request.Bio;
dbUser.DateOfBirth = request.DateOfBirth;
await _dataContext.SaveChangesAsync();
var infoDto = new UserInformationDto();
infoDto.Id = dbUser.Id;
infoDto.Username = request.Username;
infoDto.ProfilePicture = dbUser.ProfilePicture;
infoDto.Bio = request.Bio;
infoDto.Role = dbUser.Role;
infoDto.DateOfBirth = request.DateOfBirth;
infoDto.CreatedAt = dbUser.CreatedAt;
infoDto.Status = dbUser.Status;
serviceResponse.Success = true;
serviceResponse.Data = infoDto;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "User not found.";
}
return serviceResponse;
}
public async Task<ServiceResponse<string>> UpdateUserPic(string userId, IFormFile file)
{
var serviceResponse = new ServiceResponse<string>();
var userToUpdate = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == userId);
var cdnPath = _configuration["GeneralConfig:CDNPath"];
if (userToUpdate == null)
{
serviceResponse.Success = false;
serviceResponse.Message = "User Not Found.";
return serviceResponse;
}
if (file != null && file.Length > 0)
{
if (!Directory.Exists(cdnPath)) Directory.CreateDirectory(cdnPath!);
if (!Directory.Exists($"{cdnPath}/{userId}")) Directory.CreateDirectory($"{cdnPath}/{userId}");
var fileName = $"{userId}.{file.FileName.Split('.')[1]}";
var filePath = Path.Combine(cdnPath!, userId, fileName);
using (var stream = File.Create(filePath))
{
await file.CopyToAsync(stream);
}
userToUpdate.ProfilePicture = fileName;
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = fileName;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "Empty File.";
}
return serviceResponse;
}
public async Task<ServiceResponse<FileContentResult>> GetUserPic(string userId)
{
var serviceResponse = new ServiceResponse<FileContentResult>();
var user = await _dataContext.Users.FirstOrDefaultAsync(x => x.Id == userId);
var cdnPath = _configuration["GeneralConfig:CDNPath"];
if (user != null)
{
if (user.ProfilePicture != null)
{
if (!Directory.Exists(cdnPath))
{
serviceResponse.Success = false;
serviceResponse.Message = "User Content Folder Does Not Exist Yet.";
return serviceResponse;
}
var pic = Path.Combine(cdnPath, userId, user.ProfilePicture);
if (!File.Exists(pic))
{
serviceResponse.Success = false;
serviceResponse.Message = "User Does Not Have A Profile Picture.";
return serviceResponse;
}
serviceResponse.Success = true;
serviceResponse.Message = user.ProfilePicture;
serviceResponse.Data = new FileContentResult(File.ReadAllBytes(pic), $"image/{Path.GetExtension(pic)}");
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "User Does Not Have A Profile Picture.";
}
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "User Not Found.";
}
return serviceResponse;
}
public async Task<ServiceResponse<UserStatusDto>> UpdateStatus(UserStatusDto request)
{
var serviceResponse = new ServiceResponse<UserStatusDto>();
var user = _dataContext.Users.FirstOrDefault(x => x.Id == request.Id)!;
user.Status = request.Status;
await _dataContext.SaveChangesAsync();
serviceResponse.Success = true;
serviceResponse.Data = request;
return serviceResponse;
}
private long LongRandom(long min, long max, Random rnd)
{
long result = rnd.Next((int)(min >> 32), (int)(max >> 32));
result = result << 32;
result = result | (long)rnd.Next((int)min, (int)max);
return result;
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Debug"
}
}
}

View File

@ -0,0 +1,22 @@
{
"Jwt": {
"Key": "bgpLLhY2L2UeZN3sj6WwSzScFmY3JgWfs33ZEJNcaPzC2TEnfZz",
"Issuer": "http://localhost",
"Audience": "http://localhost",
"DefaultUserRole": "User"
},
"ConnectionStrings": {
"DefaultConnection": "Server=db;Database=qtcdb;Uid=root;Pwd=EuK3pXkaPCR9cW",
"DevelopmentConnection": "Data Source=qtcdev.db"
},
"GeneralConfig": {
"CDNPath": "./user-content"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,5 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>qtc_api</RootNamespace>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.5" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.10.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
</ItemGroup>
<ItemGroup>
<Content Update="ServerConfig.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
@ECHO OFF
dotnet run --project qtc-net-server.csproj -lp http -e ASPNETCORE_ENVIRONMENT="Development"

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB