Compare commits

..
34 Commits
Author SHA1 Message Date
kham 1a9316ed6f put in new function call 2026-09-26 09:23:54 +10:00
kham f3b1d901a8 put in vieww 2026-09-24 22:28:34 +10:00
kham df7e8827be change to v22 and ui optimus ng 2026-09-20 23:07:32 +10:00
kham 2e477ca023 change to person id to photo id 2026-09-20 17:38:15 +10:00
kham 5292b99af6 fixed download if no image return empty 2026-09-20 15:30:22 +10:00
kham 3d83e95613 put in new ui and backend store image to table 2026-09-20 14:32:59 +10:00
kham 75b49be476 change all save to database no folder anymore 2026-09-19 22:37:43 +10:00
kham ba4976fce4 add clear log provide 2026-08-23 18:15:40 +10:00
khamandand logout too 4eafa2fa8d add in cookie in now
Co-authored-by: and logout too
2026-08-14 17:59:47 +10:00
kham 70083f1230 commit first 2026-08-14 17:52:42 +10:00
kham 9070bebb54 put in two launch.json and tasks.json 2026-08-05 10:49:18 +10:00
kham 5d2e989ee9 fixed the program.cs and run in VC code 2026-07-30 17:23:48 +10:00
kham ce2b3ba8c9 add toolbar layout on the left when mobile on below image and text 2026-05-17 13:07:11 +10:00
kham c9228950d5 add check db is not there create db not there same as table 2026-05-16 23:22:42 +10:00
kham 2e7f9e7a27 add menu on left 2026-05-16 19:16:47 +10:00
kham d656229ab3 remove hard code password and username 2026-05-15 22:07:13 +10:00
kham fa3b289a77 update config file and sample graphql 2026-05-15 21:34:45 +10:00
kham 09ac6c6bb1 add nginx in arch linux style 2026-05-14 21:59:52 +10:00
kham 8228ff27b8 adding config for arch linux and nginx 2026-05-14 21:59:19 +10:00
kham c0bc89083d put in fixed error in appsetting.cs does not have other key 2026-05-13 18:22:17 +10:00
kham fb751ecbc3 add file deploy in linux with nginx configure file 2026-05-12 21:39:26 +10:00
kham d4543b5169 add new folder wsl 2026-05-12 21:02:00 +10:00
kham adf62c8dcc change namme and project 2026-05-12 20:59:47 +10:00
kham 936094a4bf remove ensure mirgration
and scope.serviceProvider.getService
2026-05-10 21:49:38 +10:00
kham 4c19ec3e6a fixed the merge 2026-04-16 19:45:31 +10:00
kham 8eccd04332 update angular v21.1 2026-04-16 19:35:20 +10:00
kham a449028e90 delete share ui 2026-04-10 19:40:36 +10:00
kham 2e81082615 check in the new button name 2026-04-05 20:27:35 +10:00
kham 7744dc3680 put in new console why is not save attachment person id < 0 2026-04-05 15:34:23 +10:00
kham 1681f48bbf put try and continue 2026-04-04 23:42:18 +11:00
kham d57751eefa put in try and catch just continue 2026-04-04 23:40:26 +11:00
kham 817eee7d3e put in new version 2026-04-04 22:58:19 +11:00
kham 9173d551f4 put in the .net 10 2026-04-04 22:50:27 +11:00
kham 3ba26bee33 fixed photo is not save the correct if new person and partner also the change name 2026-03-28 16:54:41 +11:00
301 changed files with 11553 additions and 24199 deletions
+54
View File
@@ -0,0 +1,54 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C#: FamilyTreeAPI",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/FamilyTreeAPI/bin/Debug/net10.0/FamilyTreeAPI.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:5019"
},
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)",
"uriFormat": "%s/swagger/index.html"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
},
},
{
"name": ".NET Core Launch (web) - Release",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "publish",
"program": "${workspaceFolder}/FamilyTreeAPI/bin/Release/net10.0/linux-x64/FamilyTreeAPI.dll",
"serverReadyAction": {
"action": "openExternally",
"pattern": "listening on:\\s+(https?://\\S+)",
"uriFormat": "%s/swagger/index.html"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_URLS": "https://localhost:5019"
},
"cwd": "${workspaceFolder}"
}
]
}
+31
View File
@@ -0,0 +1,31 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"${workspaceFolder}/FamilyTreeAPI/FamilyTreeAPI.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/FamilyTreeAPI/FamilyTreeAPI.csproj",
"-c",
"Release"
],
"problemMatcher": "$msCompile"
},
]
}
+1 -2
View File
@@ -1,5 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34202.233
MinimumVisualStudioVersion = 10.0.40219.1
@@ -3,6 +3,7 @@ using FamilyTreeAPI.Repository;
namespace FamilyTreeAPI.Controllers;
using DocumentFormat.OpenXml.Drawing.Charts;
using FamilyTreeAPI.Interface;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
@@ -110,14 +111,44 @@ public class FileUploadController : ControllerBase
return Ok(ret);
}
[HttpPost("[action]")]
public async Task<IActionResult> DownloadPersonPhotoById(DeleteCriteria<int> criteria)
{
var result = await _personPhoto.DownloadPersonPhotoById(criteria.Id);
if (result.StatusCode > 0)
return File(result.Data.Content, result.Data.ContentType, result.Data.FileName);
else
return NotFound("not found image");
}
[HttpPost("[action]")]
public async Task<IActionResult> DownloadPersonPhoto(DownloadFileCriteria criteria)
{
var result = await _personPhoto.DownloadPersonPhoto(criteria.Id, criteria.FileName);
var result = await _personPhoto.DownloadPersonPhotoPerson(criteria.Id,criteria.PhotoType ,criteria.FileName);
if (result.StatusCode > 0)
return File(result.Data.Content, result.Data.ContentType, result.Data.FileName);
else
return NotFound("not found image");
}
[HttpPost("[action]")]
public async Task<IActionResult> DownloadPersonPhoto64(DownloadFileCriteria criteria)
{
return File(result.Data.Content, result.Data.ContentType, result.Data.FileName);
var result = await _personPhoto.DownloadPersonPhotoPerson(criteria.Id,criteria.PhotoType ,criteria.FileName);
if (result.StatusCode > 0)
{
byte[] file64 = result.Data.Content;
string rval = Convert.ToBase64String(file64);
ResultModel<string> retval = new() {
Data = rval,
StatusCode = 1,
Message = ""
};
return Ok(retval);
}
else
return NotFound("not found image");
}
[HttpPost("[action]")]
public async Task<ActionResult> DeletePersonPhoto(DeleteFileCriteria criteria)
@@ -127,8 +158,18 @@ public class FileUploadController : ControllerBase
if ( criteria.Id > 0)
{
ret = await _personPhoto.DeletePersonPhoto(criteria.Id);
ret = await _personPhoto.DeletePersonPhoto(criteria.Id, criteria.PhotoType);
}
return Ok(ret);
}
[HttpPost("[action]")]
public async Task<ActionResult> DeletePersonPhotoById(DeleteCriteria<int> criteria)
{
ResultModel<int> ret = new();
ret = await _personPhoto.DeletePersonPhotoById(criteria.Id);
return Ok(ret);
}
}
@@ -14,6 +14,7 @@ namespace FamilyTreeAPI.Controllers
{
_repo = repo;
}
[HttpPost("[action]")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
@@ -37,8 +37,25 @@ public class UsersController : ControllerBase
public async Task<IActionResult> LoginApiAD([FromBody] AuthenticateRequest model)
{
string remoteIpAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
var response = await _userService.LoginApiAD(model, remoteIpAddress);
return Ok(response);
var rval = await _userService.LoginApiAD(model, remoteIpAddress);
if (rval.Data != null && rval.Data.Token != null)
{
// Configure cookie options
var cookieOptions = new CookieOptions
{
HttpOnly = true, // Blocks client-side JavaScript reading/theft
Secure = false, // Requires HTTPS connections
SameSite = SameSiteMode.Unspecified, // Prevents CSRF attacks
Expires = DateTimeOffset.UtcNow.AddDays(1), // Match your token lifespan
Path = "/" // Accessible across entire domain structure
};
// Write cookie into response headers
Response.Cookies.Append("token", rval.Data.Token, cookieOptions);
}
return Ok(rval);
}
[HttpPost("[action]")]
@@ -51,6 +68,14 @@ public class UsersController : ControllerBase
string token = Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
string remoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
var response = await _userService.Logout(token, currentUser, remoteIpAddress);
Response.Cookies.Delete("token", new CookieOptions
{
HttpOnly = true,
Secure = false,
SameSite = SameSiteMode.Unspecified,
Path = "/"
});
return Ok(response);
}
+2 -2
View File
@@ -3,7 +3,7 @@
# This stage is used when running from VS in fast mode (Default for Debug configuration)
#FROM mcr.microsoft.com/dotnet/aspnet:9::.0 AS base
ARG BUILD_CONFIGURATION=Release
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["FamilyTreeAPI/FamilyTreeAPI.csproj", "FamilyTreeAPI/"]
RUN dotnet restore "./FamilyTreeAPI/FamilyTreeAPI.csproj"
@@ -31,7 +31,7 @@ FROM build AS publish
RUN dotnet publish "./FamilyTreeAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
+2 -2
View File
@@ -4,9 +4,9 @@ public class AppSettings
{
public string Secret { get; set; }
public string SQLConnectionString { get; set; }
public string LoginWebAPI { get; set; }
public string ImageFolder { get; set; }
public string ClientURL { get; set; }
public string ImportFolder { get; set; }
}
/*
+4 -1
View File
@@ -15,16 +15,19 @@ public class DownloadFileCriteria
{
public string FileName { get; set; }
public int Id { get; set; }
public int PhotoType {get; set;}
}
public class UploadCriteria
{
public string FileName { get; set; }
public int PersonId { get; set; }
public int PhotoType {get; set;}
public IFormFile File { get; set; }
}
public class DeleteFileCriteria
{
public int Id { get; set; }
public string Filename { get; set; }
public int PhotoType {get; set;}
}
@@ -1,5 +1,12 @@
namespace FamilyTreeAPI.Entities;
//photo Type string number
public enum enumPhotoType
{
Profile = 1,
Other = 2
}
public class PersonPhotoDto
{
public int Id { get; set; }
+22 -19
View File
@@ -1,38 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<PublishSingleFile>true</PublishSingleFile>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Models_old\**" />
<Content Remove="Models_old\**" />
<EmbeddedResource Remove="Models_old\**" />
<None Remove="Models_old\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.6" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.6" />
<PackageReference Include="HotChocolate.Pagination.EntityFramework" Version="14.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<PackageReference Include="HotChocolate.AspNetCore" Version="16.6.1" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="16.6.1" />
<PackageReference Include="HotChocolate.Pagination.EntityFramework" Version="14.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.11">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<PackageReference Include="SpreadsheetLight" Version="3.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
<PackageReference Include="System.Drawing.Common" Version="10.0.11" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+6
View File
@@ -2,6 +2,12 @@
{
public class Helpers
{
public static string GetFileExtension(string fileName)
{
string result = System.IO.Path.GetExtension(fileName);
return result;
}
public static string GetFileContent(string extension)
{
string contentType = "";
+7
View File
@@ -71,6 +71,10 @@ $ sudo nano /etc/nginx/sites-avaible/default
After adding the content, save it. Then let’s enable and start the service:
sudo systemctl enable kestrel-app.service
sudo systemctl start kestrel-app.service
put familytreeapp.conf
put in /etc/nginx/sites-availalble/familytreeapp.conf
$) sudo ln -s /etc/nginx/sites-available/familytreeapp.conf /etc/nginx/sites-enabled/
postgresql.conf:
@@ -105,3 +109,6 @@ $ sudo nano /etc/nginx/sites-avaible/default
docker run -p 5432:5432 -e POSTGRES_PASSWORD=123456789 \
-d postgres:9.3.6 \
-c config_file=/path/to/postgresql.conf
+4 -3
View File
@@ -5,8 +5,9 @@ namespace FamilyTreeAPI.Interface;
public interface IPersonPhoto
{
Task<ResultModel<int>> SaveAsync(UploadCriteria criteria);
Task<ResultModel<FileContent>> DownloadPersonPhoto(int id, string? filename);
Task<ResultModel<int>> DeletePersonPhoto(int id);
Task<ResultModel<FileContent>> DownloadPersonPhotoPerson(int personId, int photoType, string filename);
Task<ResultModel<FileContent>> DownloadPersonPhotoById(int id);
Task<ResultModel<int>> DeletePersonPhoto(int id, int photoType);
Task<ResultModel<int>> DeletePersonPhotoById(int id);
Task<ResultModel<List<PersonPhotoDto>>> LoadPersonPhoto(int personId);
}
+88 -17
View File
@@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using DocumentFormat.OpenXml.Office2010.Drawing.Charts;
using FamilyTreeAPI.Authorization;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.GraphQL.Query;
@@ -14,6 +13,15 @@ using FamilyTreeAPI.Repository;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
// Explicitly register the base paths for Linux container execution
builder.Configuration
.SetBasePath(AppContext.BaseDirectory) // <-- Pulls from wherever the binary sits
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
// Add services to the container.
#region Services
var services = builder.Services;
@@ -22,11 +30,26 @@ services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
string skey = "Super Secret SIGN AND VERIFY JWT TOKENS";
var appSettingsSection = builder.Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
if (appSettingsSection.Exists())
{
services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
if ( appSettings != null)
skey = appSettings.Secret;
else
{
string tkey = builder.Configuration.GetValue<string>("AppSettings:Secret") ?? string.Empty;
if (!string.IsNullOrEmpty(tkey))
skey = tkey;
}
}
var key = Encoding.ASCII.GetBytes(skey);
services.AddAuthentication(x =>
{
@@ -103,10 +126,7 @@ using (var scope = app.Services.CreateScope())
var db = context.Database;
if (db != null)
{
db.Migrate();
db.EnsureCreated();
var staff = context.GetService<Seed>();
var staff = scope.ServiceProvider.GetService<Seed>();
int id = staff.InsertOneUser();
if (id < 0)
{
@@ -114,23 +134,34 @@ using (var scope = app.Services.CreateScope())
if (databaseCreator != null)
{
//if (!databaseCreator.Exists())
databaseCreator.CreateTables();
try
{
// Check if database exists
if (!databaseCreator.Exists())
{
databaseCreator.Create();
}
if (!databaseCreator.HasTables())
{
databaseCreator.CreateTables();
}
}
catch
{
//continue
}
}
id = staff.InsertOneUser();
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
app.UseMiddleware<JwtMiddleware>();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())//
@@ -156,6 +187,17 @@ app.Run();
#endregion
/*
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
https://www.youtube.com/watch?v=WQFx2m5Ub9M
@@ -171,14 +213,43 @@ PM> Scaffold-DbContext "Host=postgresdb;Port=5432;database=FamilyTreeDB;user id=
https://www.youtube.com/watch?v=HnXA8RI7Tvc
query {
family {
persons {
nodes{
id
firstname
lastname
firstName
sex
email
}
}
}
dotnet --list-sdks
In the VS Code Explorer, click and open your <ProjectName>.csproj file.
Locate the <TargetFramework> tag inside the <PropertyGroup> block.
Change the value to your desired version (e.g., change net11.0 or net9.0 to net10.0
now run to see out of date
dotnet list package --outdated
dotnet tool uninstall --global dotnet-outdated
dotnet tool install --global dotnet-outdated-tool
//update the outdate
dotnet outdated -u
update the package
dotnet add package <Package_Name>
Open the .vscode folder in your workspace and select launch.json.
Look for lines containing your old framework directory string (e.g., /bin/Debug/net8.0/).
Update those paths to reflect your new version (e.g., /bin/Debug/net9.0/).
clear and rebuild
dotnet clean
dotnet restore
dotnet build
*/
@@ -8,35 +8,17 @@
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5016",
"applicationUrl1": "http://192.168.8.188:5015"
"applicationUrl": "http://localhost:5016"
},
"IIS Express": {
"commandName": "IISExpress",
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
"applicationUrl": "http://localhost:5000"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": false
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:4559",
"sslPort": 0
}
}
}
@@ -35,7 +35,7 @@ namespace FamilyTreeAPI.Repository
string error = "";
try
{
Lookup model = null!;
FamilyTreeAPI.Models.Lookup model = null!;
bool ok = !checkDescription(desc, lookup.Type, lookup.Id);
if (ok)
{
@@ -52,7 +52,7 @@ namespace FamilyTreeAPI.Repository
}
else
{
Lookup? model1 = await _context.Lookups.FindAsync(lookup.Id);
FamilyTreeAPI.Models.Lookup? model1 = await _context.Lookups.FindAsync(lookup.Id);
if (model1 != null)
{
model1.Description = desc;
@@ -3,6 +3,7 @@ using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Helper;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using HotChocolate.CostAnalysis.Types;
using Microsoft.EntityFrameworkCore;
namespace FamilyTreeAPI.Repository
@@ -31,12 +32,12 @@ namespace FamilyTreeAPI.Repository
dto.PhotoType = model.PhotoType;
return dto;
}
private PersonPhoto FillModel(PersonPhoto model, int personId, string filename)
private PersonPhoto FillModel(PersonPhoto model, int personId, int photoType, string filename)
{
model.PersonId = personId;
model.Photo = filename;
model.PhotoType = System.IO.Path.GetExtension(filename);
model.PhotoType = photoType.ToString(); //System.IO.Path.GetExtension(filename);
return model;
}
@@ -44,8 +45,10 @@ namespace FamilyTreeAPI.Repository
{
int result = default(int);
int statuscode = 0;
string error = "";
string error = "";//Other
string photoType = "2"; //
if (criteria.PhotoType > 0)
photoType = criteria.PhotoType.ToString();
HttpContext? httpContext = _httpContext.HttpContext;
string loginName = "";
if (httpContext != null)
@@ -63,7 +66,7 @@ namespace FamilyTreeAPI.Repository
model = new();
model = FillModel(model,criteria.PersonId, formfile.FileName);
model = FillModel(model,criteria.PersonId, criteria.PhotoType,formfile.FileName);
using (var stream = new MemoryStream())
{
@@ -72,6 +75,7 @@ namespace FamilyTreeAPI.Repository
}
_context.PersonPhotos.Add(model);
await _context.SaveChangesAsync();
result = model.Id;
statuscode = 1;
@@ -89,15 +93,50 @@ namespace FamilyTreeAPI.Repository
Message = error
};
}
public async Task<ResultModel<FileContent>> DownloadPersonPhoto(int id, string? filename)
public async Task<ResultModel<FileContent>> DownloadPersonPhotoById(int Id)
{
int statusCode = -1;
string error = "";
string extension = "";
PersonPhoto? model = null;
FileContent fileContent = new();
if (id < 1)
try {
model = await _context.PersonPhotos.FindAsync(Id);
if (model != null)
{
extension = Helpers.GetFileExtension(model.Photo);
fileContent.ContentType = Helpers.GetFileContent(extension);
fileContent.Content = (byte[])model.ImagePhoto;
fileContent.FileName = model.Photo;
statusCode = 1;
}
}
catch (Exception e)
{
error = e.ToString();
statusCode = -1;
}
return new ResultModel<FileContent>()
{
Data = fileContent,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<FileContent>> DownloadPersonPhotoPerson(int personId, int photoType, string filename)
{
int statusCode = -1;
string error = "";
string extension = "";
PersonPhoto? model = null;
FileContent fileContent = new();
if (personId < 1)
{
var rlist = await _context.PersonPhotos.Where(x => x.Photo == filename).ToListAsync();
if (rlist != null)
@@ -108,19 +147,26 @@ namespace FamilyTreeAPI.Repository
}
}
}
else
{
model = await _context.PersonPhotos.FindAsync(id);
var rlist = await _context.PersonPhotos.Where (x => x.PersonId == personId && photoType.ToString() == x.PhotoType).ToListAsync();
for ( int i = 0; i < rlist.Count; i++)
{
model = rlist[i];
break;
}
}
if (model != null)
{
fileContent.ContentType = Helpers.GetFileContent(model.PhotoType);
extension = Helpers.GetFileExtension(model.Photo);
fileContent.ContentType = Helpers.GetFileContent(extension);
fileContent.Content = (byte[])model.ImagePhoto;
fileContent.FileName = model.Photo;
statusCode = 1;
}
return new ResultModel<FileContent>()
{
Data = fileContent,
@@ -129,20 +175,69 @@ namespace FamilyTreeAPI.Repository
};
}
public async Task<ResultModel<int>> DeletePersonPhoto(int id)
public async Task<ResultModel<int>> DeletePersonPhoto(int personId, int photoType)
{
int result = default(int);
int statuscode = 0;
string error = "";
PersonPhoto? model;
try
{
PersonPhoto? model = await _context.PersonPhotos.FindAsync(id);
if (model != null)
if (photoType < 1)
{
_context.PersonPhotos.Remove(model);
await _context.SaveChangesAsync();
statuscode = 1;
List<PersonPhoto> list = await _context.PersonPhotos.Where ( x => x.PersonId == personId).ToListAsync();
for (int i = 0; i< list.Count; i++)
{
model = list[i];
_context.PersonPhotos.Remove(model);
await _context.SaveChangesAsync();
statuscode = 1;
}
}
else
{
List<PersonPhoto> list = await _context.PersonPhotos.Where ( x => x.PersonId == personId && x.PhotoType == photoType.ToString()).ToListAsync();
for (int i = 0; i< list.Count; i++)
{
model = list[i];
_context.PersonPhotos.Remove(model);
await _context.SaveChangesAsync();
statuscode = 1;
}
}
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
//var dto = await Task.Run(() => result);
return new ResultModel<int>()
{
Data = result,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<int>> DeletePersonPhotoById(int id)
{
int result = default(int);
int statuscode = 0;
string error = "";
try
{
PersonPhoto? model = await _context.PersonPhotos.FindAsync(id);
if (model != null)
{
_context.PersonPhotos.Remove(model);
await _context.SaveChangesAsync();
statuscode = 1;
}
}
catch (Exception ex)
{
@@ -283,7 +283,7 @@ public partial class PersonRepository : IPerson
node.Key = item.Id.ToString();
node.Type = type;
if (item.Image != null)
node.Data.Image = await LoadPersonImage(item.Image);
node.Data.Image = await LoadPersonImage(item.Id, (int) enumPhotoType.Profile, item.Image);
node.Icon = "T";
node.StyleClass = getStyleClass(node.Icon);
@@ -319,8 +319,8 @@ public partial class PersonRepository : IPerson
TreeNode<TreeData> child;
PersonDto dto;
Person pe;
int fatherId, motherId;
fatherId = motherId = 0;
int fatherId, motherId, personId;
personId = fatherId = motherId = 0;
string data = person.Sex;
TreeNode<TreeData> citem;
string pName = "";
@@ -371,6 +371,7 @@ public partial class PersonRepository : IPerson
pName = pe.FirstName;
image = pe.Image;
key = motherId.ToString();
personId = motherId;
data = pe.Sex;
}
}
@@ -382,6 +383,7 @@ public partial class PersonRepository : IPerson
pName = pe.FirstName;
image = pe.Image;
key = fatherId.ToString();
personId = fatherId;
data = pe.Sex;
}
}
@@ -390,7 +392,7 @@ public partial class PersonRepository : IPerson
citem.Data = new();
citem.Label = pName;
if (image != null)
citem.Data.Image = await LoadPersonImage(image);
citem.Data.Image = await LoadPersonImage(personId, (int) enumPhotoType.Profile, image);
citem.Icon = "P";
@@ -411,7 +413,7 @@ public partial class PersonRepository : IPerson
child.Type = type;
image = dto.Image;
if (image != null)
child.Data.Image = await LoadPersonImage(image);
child.Data.Image = await LoadPersonImage(dto.Id,(int) enumPhotoType.Profile, image);
child.Icon = "C";
child.StyleClass = getStyleClass(child.Icon);
child.Label = dto.FirstName;
@@ -480,12 +482,12 @@ public partial class PersonRepository : IPerson
/// <param name="fileName"></param>
/// <param name="familyId"></param>
/// <param name="base64"></param>
private string SaveFile(string fileName, int familyId, string base64)
private async Task<string> SaveFile(string fileName, int personId, string base64)
{
string path = "";
int statusCode = 0;
string error = "";
// string error = "";
string sdate = GetCurrentDateTime();
string filename, extention;
filename = extention = "";
@@ -494,14 +496,33 @@ public partial class PersonRepository : IPerson
string rbase = base64.Substring(first, last - first);
byte[] newBytes = Convert.FromBase64String(rbase);
extention = System.IO.Path.GetExtension(fileName);
filename = personId + "_" + sdate + extention;
// Wrap the byte array in a MemoryStream
var stream = new MemoryStream(newBytes);
// Create the IFormFile instance
IFormFile file = new FormFile(stream, 0, newBytes.Length, "Profile", filename)
{
Headers = new HeaderDictionary(),
ContentType = "application/octet-stream" // Change to your specific MIME type if known (e.g., "image/jpeg", "application/pdf")
};
//save to database.
UploadCriteria criteria = new();
criteria.FileName = filename;
criteria.PersonId = personId;
criteria.PhotoType = (int) enumPhotoType.Profile;
criteria.File = file;
var rval = await _personPhoto.SaveAsync(criteria);
/*
path = _config["AppSettings:ImageFolder"];
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
extention = System.IO.Path.GetExtension(fileName);
filename = familyId + "_" + sdate + extention;
string fullpath = System.IO.Path.Combine(path, filename);
/*
using (var fileStream = new FileStream(fullpath, FileMode.Create))
@@ -520,10 +541,12 @@ public partial class PersonRepository : IPerson
}
*/
/*
using (FileStream fileStream = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.None))
{
fileStream.Write(newBytes, 0, newBytes.Length);
}
*/
return filename;
}
@@ -538,7 +561,7 @@ public partial class PersonRepository : IPerson
string loginName = "";
if (httpContext != null)
{
UserDto? user = (UserDto?)httpContext.Items["User"];
UserDto? user = (UserDto?)httpContext.Items["User"];
if (user != null)
loginName = user.FirstName + " " + user.LastName;
}
@@ -571,7 +594,7 @@ public partial class PersonRepository : IPerson
if (!string.IsNullOrEmpty(container.FileName))
{
string image = SaveFile(container.FileName, result, container.FormData);
string image = await SaveFile(container.FileName, result, container.FormData);
// update the image in table to new
model.Image = image;
await _context.SaveChangesAsync();
@@ -587,7 +610,7 @@ public partial class PersonRepository : IPerson
result = item.Id;
if (!string.IsNullOrEmpty(container.FileName))
{
string image = SaveFile(container.FileName, result, container.FormData);
string image = await SaveFile(container.FileName, result, container.FormData);
// update the image in table to new
model1.Image = image;
}
@@ -714,17 +737,37 @@ public partial class PersonRepository : IPerson
};
}
private async Task<string> LoadPersonImage(string filename)
private async Task<string> LoadPersonImage_old(string filename)
{
string result = "";
DownloadFileCriteria criteria = new() { FileName = filename };
var resultModel = await _importPersonRepository.DownloadFile(criteria);
if (!string.IsNullOrEmpty((resultModel.Data)))
if (!string.IsNullOrEmpty(resultModel.Data))
result = "data:image/png;base64," + resultModel.Data;
return result;
}
private async Task<string> LoadPersonImage(int personId, int photoType, string filename)
{
string result = "";
// DownloadFileCriteria criteria = new() { FileName = filename };
var resultModel = await _personPhoto.DownloadPersonPhotoPerson(personId, photoType , filename);
if (resultModel.StatusCode > 0)
{
string base64String = Convert.ToBase64String(resultModel.Data.Content);
if (!string.IsNullOrEmpty(base64String))
result = "data:image/png;base64," + base64String;
}
return result;
}
public ResultModel<int> DeleteUploadFile(DeleteFileCriteria criteria)
{
int result = -1;
-1
View File
@@ -66,7 +66,6 @@ public class Seed
var conn = _context.Database.GetDbConnection();
try
{
conn.Open();
var command = conn.CreateCommand();
command.CommandText = sptext;
@@ -146,7 +146,7 @@ public class UserServiceRepository : IUserService
AuthenticateResponse retval = null;
string error = "user name is not in DB or user Name profile is not generic";
int statuscode = 0;
string webAPIUrl = _appSettings.LoginWebAPI;
string webAPIUrl = "_appSettings.LoginWebAPI";
//KCO, D204KCO
//now check the adminuser table
//ward clerk
+9 -8
View File
@@ -1,12 +1,12 @@
{
"AppSettings": {
"Secret": "Nepean Blue Mountain Super Secret SIGN AND VERIFY JWT TOKENS, BEARER TOKEN USE WHEN CALLING THIS API.",
"SQLConnectionString": "host=localhost;port=5432;database=FamilyTreeDB;username=postgres;password=Positive~1;",
"SQLConnectionString_o": "host=192.168.1.123;port=5432;database=FamilyTreeDB;username=postgres;password=Positive~1;",
"Secret": "Super Secret SIGN AND VERIFY JWT TOKENS, BEARER TOKEN USE WHEN CALLING THIS API.",
"iSQLConnectionString_i": "host=localhost;port=5432;database=familytreedb;username=postgres;password=Positive~1;",
"SQLConnectionString": "host=127.0.0.1;port=5432;database=familytreedb;username=postgres;password=Positive~1;",
"ImageFolder": "c:\\temp\\Family",
"ImportFolder": "c:\\temp"
"ImageFolder": "/temp/Family",
"ImportFolder": "/temp"
},
"Logging": {
@@ -17,11 +17,12 @@
}
},
"AllowedHosts": "*",
"Kestrel_not_use": {
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5015"
"Url": "http://localhost:5016"
}
}
}
}
}
+7 -5
View File
@@ -1,15 +1,17 @@
[Unit]
Description=ASP.NET Core Web App running on Ubuntu
Description=ASP.NET Core Web App running on arch linux
[Service]
WorkingDirectory=/var/www/familytree/api
ExecStart=/usr/bin/dotnet /var/www/fadmilytree/api/FamilyTreeAPI.dll
ExecStart=/usr/bin/dotnet /var/www/familytree/api/FamilyTreeAPI.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
# Restart service after 20 seconds if the dotnet service crashes:
RestartSec=20
KillSignal=SIGINT
SyslogIdentifier=dotnet-web-familytree-api
# This user should exist on the server and have ownership of the deployment directory
User=www-data
#User=www-data this is other ubuntu
#arch linux has user http
User=http
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
+44
View File
@@ -0,0 +1,44 @@
#make sure in nginx.conf has include module.d/*.conf inside http block one.
server {
listen 80; #make nginx out side listen 82;
location /familytreeapi/ {
#proxy_pass http://127.0.0.1:5015;
proxy_pass http://192.168.1.219:5015/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
return 301 /familytreeui/; #if same one redirect to /familytreeui/
}
location /familytreeui {
return 301 /familytreeui/; #if same one redirect to /familytreeui/
}
location /familytreeui/ {
#root /var/www/familytree/; #path is real must exist like /var/www/familytree/familytreeui and /var/www/familytree/api folder.
alias /var/www/familytree/ui/; #location does not to match. but angular index baseurl must same as location.
#index index.html;
try_files $uri $uri/ /familytreeui/index.html /index.html;
}
# Optional: Cache static assets for better performance
#location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
# expires 30d;
# add_header Cache-Control "public";
#}
# error_page 404 /index.html;
# put in /etc/nginx/sites-availalble/familytreeapp.conf
# link to enable
# $) sudo ln -s /etc/nginx/sites-availalble/familytreeapp.conf /etc/nginx/sites-enabled/
}
+123
View File
@@ -0,0 +1,123 @@
user http;
worker_processes auto;
worker_cpu_affinity auto;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
# Load all installed modules
#include modules.d/*.conf;
events {
worker_connections 1024;
}
http {
charset utf-8;
include mime.types;
default_type application/octet-stream;
types_hash_max_size 4096;
client_max_body_size 16M;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
include modules.d/*.conf;
server {
listen 81;
server_name localhost;
#access_log logs/host.access.log main;
#kham comment out first
#location / {
# root /usr/share/nginx/html;
# index index.html index.htm;
# }
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
+3162 -4312
View File
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -23,35 +23,35 @@
"private": true,
"packageManager": "npm@11.6.4",
"dependencies": {
"@angular/common": "^21.0.0",
"@angular/compiler": "^21.0.0",
"@angular/core": "^21.0.0",
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/router": "^21.0.0",
"@ngrx/effects": "^21.0.1",
"@ngrx/store": "^21.0.1",
"@ngrx/store-devtools": "^21.0.1",
"@primeuix/themes": "^2.0.2",
"@angular/common": "^22.0.0",
"@angular/compiler": "^22.0.0",
"@angular/core": "^22.0.0",
"@angular/forms": "^22.0.0",
"@angular/platform-browser": "^22.0.0",
"@angular/router": "^22.0.0",
"@ngrx/effects": "^22.0.0",
"@ngrx/store": "^22.0.0",
"@ngrx/store-devtools": "^22.0.0",
"@openng/optimus-ui": "^2.0.2",
"@tailwindcss/postcss": "^4.1.17",
"file-saver": "^2.0.5",
"moment": "^2.30.1",
"postcss": "^8.5.6",
"primeicons": "^7.0.0",
"primeng": "^21.0.1",
"rxjs": "~7.8.0",
"tailwindcss": "^4.1.17",
"tailwindcss-primeui": "^0.6.1",
"tslib": "^2.3.0",
"xlsx": "^0.18.5"
"xlsx": "^0.18.5",
"@openng/icons": "^1.0.0",
"@openng/optimus-ui-themes": "^2.0.2",
"@openng/optimus-ui-tailwindcss": "^2.0.2"
},
"devDependencies": {
"@angular/build": "^21.0.2",
"@angular/cli": "^21.0.2",
"@angular/compiler-cli": "^21.0.0",
"@angular/build": "^22.0.0",
"@angular/cli": "^22.0.0",
"@angular/compiler-cli": "^22.0.0",
"@types/file-saver": "^2.0.7",
"jsdom": "^27.1.0",
"typescript": "~5.9.2",
"typescript": "~6.0.0",
"vitest": "^4.0.8"
}
}
+3 -1
View File
@@ -2,5 +2,7 @@
"baseUrl1": "http://192.168.8.188:5015",
"baseUrl": "http://localhost:5016",
"baseUrl_docker": "http://localhost:8080",
"attachment": "http://localhost/document/family"
"attachment": "http://localhost/document/family",
"username": "kham.vilaythong@gmail.com",
"prefill_ps": "passwor"
}
BIN
View File
Binary file not shown.
+8 -6
View File
@@ -1,9 +1,9 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners ,
provideAppInitializer} from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { providePrimeNG } from 'primeng/config';
import { provideOptimus } from '@openng/optimus-ui/config';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ConfirmationService, MessageService } from '@openng/optimus-ui/api';
import { routes } from './app.routes';
import MyPreset from './mythem';
import { JwtInterceptor, ErrorInterceptor, initializeApp } from './shares';
@@ -29,13 +29,13 @@ export const appConfig: ApplicationConfig = {
provideAppInitializer(initializeApp()),
provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])),
provideRouter(routes, withComponentInputBinding()),
providePrimeNG({
provideOptimus({
theme: {
preset: MyPreset,
options: {
cssLayer: {
name: 'primeng',
order: 'theme, base, primeng'
name: 'optimus',
order: 'theme, base, optimus'
}
}
}
@@ -44,7 +44,9 @@ export const appConfig: ApplicationConfig = {
]
};
/*
$) ng build --base-href "/familytreeui/" -c production
*/
/*
+2 -2
View File
@@ -2,8 +2,8 @@
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ToolbarComponent } from './toolbar/toolbar.component';
import { ToastModule } from 'primeng/toast';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { ToastModule } from '@openng/optimus-ui/toast';
import { ConfirmDialogModule } from '@openng/optimus-ui/confirmdialog';
@Component({
selector: 'app-root',
imports: [RouterOutlet,ToastModule,ToolbarComponent,ConfirmDialogModule],
+1 -1
View File
@@ -10,7 +10,7 @@
<div class="flex items-end justify-end mt-4">
@if (files)
{
<button pButton pRipple type="button" icon="pi pi-upload" label="Add Photo" class="p-button-sm mr-2"
<button pButton pRipple type="button" icon="pi pi-upload" label="OK" class="p-button-sm mr-2"
(click)="upload($event)"></button>
}
<button pButton pRipple type="button" icon="pi pi-times" label="Cancel" class="p-button-sm"
+14 -7
View File
@@ -1,15 +1,15 @@
import { ChangeDetectorRef, Component, inject, Inject, OnDestroy, OnInit, signal } from '@angular/core';
import { DynamicDialogModule, DynamicDialogRef ,DynamicDialogConfig} from 'primeng/dynamicdialog';
import { DynamicDialogModule, DynamicDialogRef ,DynamicDialogConfig} from '@openng/optimus-ui/dynamicdialog';
import { LookupEdit } from '../models';
import { UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { Subject, Subscription } from 'rxjs';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ConfirmationService, MessageService } from '@openng/optimus-ui/api';
import { LookupService } from '../shares';
import { CommonModule } from '@angular/common';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from '@openng/optimus-ui/checkbox';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { ButtonModule } from '@openng/optimus-ui/button';
@Component({
selector: 'add-photo',
imports: [CommonModule, ReactiveFormsModule,
@@ -67,9 +67,9 @@ export class AddPhoto implements OnInit, OnDestroy {
});
}
else {
//new cleaner
//new photo
const ward:LookupEdit = {
id: -1, description: '', codeId: maxcodeId, type: this.selType, active: true,
id, description: '', codeId: maxcodeId, type: this.selType, active: true,
};
this.assignValue(ward);
@@ -157,7 +157,14 @@ export class AddPhoto implements OnInit, OnDestroy {
}
upload(e: Event): void {
e.preventDefault();
if (this.files.length > 0)
{
this.ref.close(this.files);
}
else
{
this.messageService.add({severity: 'error', summary: 'No File to upload file', detail: 'select file first then upload file'});
}
}
cancel(e: Event): void {
+2 -1
View File
@@ -16,7 +16,7 @@
<th>Id</th>
<th pSortableColumn="description">Description<p-sortIcon field="description"></p-sortIcon>
</th>
<th>Photo Type</th>
<th>Action</th>
</tr>
@@ -25,6 +25,7 @@
<tr>
<td>{{item.id}}</td>
<td >{{item.photo}}</td>
<td>{{item.photoType}}</td>
<td>
<button pButton type="button" icon="pi pi-times"
+9 -9
View File
@@ -2,16 +2,16 @@
import { Component, OnInit, OnDestroy, inject, ChangeDetectorRef, signal, output } from '@angular/core';
import { catchError, EMPTY, finalize, Subscription } from 'rxjs';
import { CommonUtilities, HttpUtility, LookupService } from '../shares';
import { DialogService ,DynamicDialogConfig,DynamicDialogModule, DynamicDialogRef} from 'primeng/dynamicdialog';
import { ConfirmationService, MessageService } from 'primeng/api';
import { DialogService ,DynamicDialogConfig,DynamicDialogModule, DynamicDialogRef} from '@openng/optimus-ui/dynamicdialog';
import { ConfirmationService, MessageService } from '@openng/optimus-ui/api';
import { AddPhoto } from './add.photo';
import { PersonPhotoDto } from '../models';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TableModule } from 'primeng/table';
import { ButtonModule } from 'primeng/button';
import { TooltipModule } from 'primeng/tooltip';
import { TableModule } from '@openng/optimus-ui/table';
import { ButtonModule } from '@openng/optimus-ui/button';
import { TooltipModule } from '@openng/optimus-ui/tooltip';
import { AuthenticationService } from '../user-services';
import { HttpResponse } from '@angular/common/http';
import { PersonService } from '../person';
@@ -69,11 +69,11 @@ export class PhotoList implements OnInit, OnDestroy {
downloadAttachment(id: number): void {
//GetReportFile
let typeofCall = "personId_" + id.toString();
let typeofCall = "personPhotoId_" + id.toString();
let criteria:any ={id, fileName:''};
let criteria: any = { id };
this.downloadFile.set(false);
this.http.getFileResponse("api/FileUpload/downloadPersonPhoto", criteria).pipe(
this.http.getFileResponse("api/FileUpload/DownloadPersonPhotoById", criteria).pipe(
catchError(err => {
console.error(err.message);
return EMPTY;
@@ -128,7 +128,7 @@ doRemove(id: number): void {
}
else
{
const deletepersonPhoto$ = this.personService.deletePersonPhotoFile(id);
const deletepersonPhoto$ = this.personService.deletePersonPhotoFileById(id);
this.subscription.add(deletepersonPhoto$.subscribe(
{
next: x => {
+5 -5
View File
@@ -1,9 +1,9 @@
import { Component, inject, OnInit } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { FileUploadEvent, FileUploadModule } from 'primeng/fileupload';
import { MessageService } from 'primeng/api';
import { FileUpload } from 'primeng/fileupload';
import { ToastModule } from 'primeng/toast';
import { ButtonModule } from '@openng/optimus-ui/button';
import { FileUploadEvent, FileUploadModule } from '@openng/optimus-ui/fileupload';
import { MessageService } from '@openng/optimus-ui/api';
import { FileUpload } from '@openng/optimus-ui/fileupload';
import { ToastModule } from '@openng/optimus-ui/toast';
import { AppSettingService } from '../shares';
/*
interface UploadEvent {
+10 -8
View File
@@ -5,15 +5,15 @@ import { first } from 'rxjs/operators';
import { AuthenticationService } from '../user-services';
import { ConfirmationService } from 'primeng/api';
import { ConfirmationService } from '@openng/optimus-ui/api';
import { Subject, Subscription } from 'rxjs';
import { CommonModule } from '@angular/common';
import { CardModule } from 'primeng/card';
import { CardModule } from '@openng/optimus-ui/card';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from '@openng/optimus-ui/button';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { Utils } from '../shares';
import { AppSettingService, Utils } from '../shares';
@Component({
templateUrl: './login.html',
styleUrl: './login.css',
@@ -29,6 +29,7 @@ export class Login implements OnInit, OnDestroy {
router = inject(Router);
authenticationService = inject(AuthenticationService);
formBuilder = inject(FormBuilder);
private appSetting =inject(AppSettingService);
loading = false;
homeUrl = "/person";
submitted = false;
@@ -40,10 +41,11 @@ export class Login implements OnInit, OnDestroy {
private subscription: Subscription = new Subscription();
constructor() {
const default_us = this.appSetting.appSetting.username;
const pass = this.appSetting.appSetting.prefill_ps;
this.loginForm = this.formBuilder.group({
username: ['kham.vilaythong@gmail.com', Validators.required],
password: ['password', Validators.required],
username: [default_us, Validators.required],
password: [pass, Validators.required],
});
}
+4 -1
View File
@@ -15,7 +15,10 @@ export enum mState {
Modified = 1,
New =2,
};
export enum enumPhoto {
Profile = 1,
Other = 2
}
export enum userRole {
Normal = 1,
Admin = 2,
+29 -38
View File
@@ -1,51 +1,41 @@
//mypreset.ts
import { definePreset } from '@primeuix/themes';
import Aura from '@primeuix/themes/aura';
import { primitive } from '@primeuix/themes/aura/base';
import { definePreset } from '@openng/optimus-ui-themes';
import Aura from '@openng/optimus-ui-themes/aura';
const MyPreset = definePreset(Aura, {
semantic: {
colorScheme: {
primitive: {
cyan: {
50: '{cyan.50}',
100: '{cyan.100}',
200: '{cyan.200}',
300: '{cyan.300}',
400: '{cyan.400}',
500: '{cyan.500}',
}
},
primary: {
50: '{zinc.50}',
100: '{zinc.100}',
200: '{zinc.200}',
300: '{zinc.300}',
400: '{zinc.400}',
500: '{zinc.500}',
600: '{zinc.600}',
700: '{zinc.700}',
800: '{zinc.800}',
900: '{zinc.900}',
950: '{zinc.950}'
primary: {
50: '{blue.50}',
100: '{blue.100}',
200: '{blue.200}',
300: '{blue.300}',
400: '{blue.400}',
500: '{blue.500}',
600: '{blue.600}',
700: '{blue.700}',
800: '{blue.800}',
900: '{blue.900}',
950: '{blue.950}'
},
colorScheme: {
light: {
surface: {
0: '#ffffff',
50: '{zinc.50}',
100: '{zinc.100}',
200: '{zinc.200}',
300: '{zinc.300}',
400: '{zinc.400}',
500: '{zinc.500}',
600: '{zinc.600}',
700: '{zinc.700}',
800: '{zinc.800}',
900: '{zinc.900}',
950: '{zinc.950}'
50: '{blue.50}',
100: '{blue.100}',
200: '{blue.200}',
300: '{blue.300}',
400: '{blue.400}',
500: '{blue.500}',
600: '{blue.600}',
700: '{blue.700}',
800: '{blue.800}',
900: '{blue.900}',
950: '{blue.950}'
}
},
dark: {
@@ -65,6 +55,7 @@ const MyPreset = definePreset(Aura, {
}
}
}
}
});
+10 -10
View File
@@ -1,23 +1,23 @@
import { Component, OnInit, OnDestroy, inject, ChangeDetectorRef, signal} from '@angular/core';
import { StaffView ,StaffSearch, Person, RelationShip } from '../models';
import { OrganizationChartModule } from 'primeng/organizationchart';
import { OrganizationChartModule } from '@openng/optimus-ui/organizationchart';
import { take } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { PersonService } from './person.service';
import { TableModule } from 'primeng/table';
import { TableModule } from '@openng/optimus-ui/table';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { TreeNode } from 'primeng/api';
import { InputTextModule } from 'primeng/inputtext';
import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ButtonModule } from '@openng/optimus-ui/button';
import { TreeNode } from '@openng/optimus-ui/api';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { DialogService, DynamicDialogConfig, DynamicDialogRef } from '@openng/optimus-ui/dynamicdialog';
import { PersonEdit } from './person.edit';
import { MessageService } from 'primeng/api';
import { TreeModule, TreeNodeDoubleClickEvent, TreeNodeSelectEvent } from 'primeng/tree';
import { MessageService } from '@openng/optimus-ui/api';
import { TreeModule, TreeNodeDoubleClickEvent, TreeNodeSelectEvent } from '@openng/optimus-ui/tree';
import { Utils } from '../shares';
import { PopoverModule } from 'primeng/popover';
import { PopoverModule } from '@openng/optimus-ui/popover';
@Component({
selector: 'family-orga',
@@ -64,7 +64,7 @@ export class FamilyOrga implements OnInit, OnDestroy{
{
expanded: true,
type: 'person',
styleClass: 'bg-indigo-500 text-white',
styleClass: 'bg-blue-500 text-white',
data: {
image: 'https://primefaces.org/cdn/primeng/images/demo/avatar/amyelsner.png',
name: 'Amy Elsner',
+9 -9
View File
@@ -1,24 +1,24 @@
import { Component, OnInit, OnDestroy, inject, ChangeDetectorRef, signal} from '@angular/core';
import { StaffView ,StaffSearch, Person } from '../models';
import { OrganizationChartModule } from 'primeng/organizationchart';
import { OrganizationChartModule } from '@openng/optimus-ui/organizationchart';
import { take } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { Router } from '@angular/router';
import { PersonService } from './person.service';
import { AuthenticationService } from '../user-services';
import { TableModule } from 'primeng/table';
import { TableModule } from '@openng/optimus-ui/table';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { TreeNode } from 'primeng/api';
import { InputTextModule } from 'primeng/inputtext';
import { DialogService } from 'primeng/dynamicdialog';
import { ButtonModule } from '@openng/optimus-ui/button';
import { TreeNode } from '@openng/optimus-ui/api';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { DialogService } from '@openng/optimus-ui/dynamicdialog';
import { PersonEdit } from './person.edit';
import { MessageService } from 'primeng/api';
import { CheckboxModule } from 'primeng/checkbox';
import { TreeModule, TreeNodeDoubleClickEvent, TreeNodeSelectEvent } from 'primeng/tree';
import { MessageService } from '@openng/optimus-ui/api';
import { CheckboxModule } from '@openng/optimus-ui/checkbox';
import { TreeModule, TreeNodeDoubleClickEvent, TreeNodeSelectEvent } from '@openng/optimus-ui/tree';
import { Utils } from '../shares';
@Component({
+8 -8
View File
@@ -8,17 +8,17 @@ import { Subscription } from 'rxjs';
import { Router } from '@angular/router';
import { PersonService } from './person.service';
import { AuthenticationService } from '../user-services';
import { Table, TableModule } from 'primeng/table';
import { Table, TableModule } from '@openng/optimus-ui/table';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { DialogService } from 'primeng/dynamicdialog';
import { ButtonModule } from '@openng/optimus-ui/button';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { DialogService } from '@openng/optimus-ui/dynamicdialog';
import { PersonEdit } from './person.edit';
import { ConfirmationService, MenuItem, MessageService } from 'primeng/api';
import { IconFieldModule } from 'primeng/iconfield';
import { InputIconModule } from 'primeng/inputicon';
import { Menu, MenuModule } from 'primeng/menu';
import { ConfirmationService, MenuItem, MessageService } from '@openng/optimus-ui/api';
import { IconFieldModule } from '@openng/optimus-ui/iconfield';
import { InputIconModule } from '@openng/optimus-ui/inputicon';
import { Menu, MenuModule } from '@openng/optimus-ui/menu';
import { FamilyOrga } from './family.orga';
import { saveAs } from 'file-saver';
+35 -29
View File
@@ -1,27 +1,28 @@
import { ChangeDetectorRef, Component, inject, OnDestroy, OnInit, signal, ViewChild } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
import {DynamicDialogConfig} from 'primeng/dynamicdialog';
import {DialogService, DynamicDialogRef} from '@openng/optimus-ui/dynamicdialog';
import {DynamicDialogConfig} from '@openng/optimus-ui/dynamicdialog';
import { FormControl, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { Subject, Subscription} from 'rxjs';
import { ConfirmationService, MessageService } from 'primeng/api';
import { DatePickerModule } from 'primeng/datepicker';
import { Code, RelationShipView, Person, mState, RelationShip, PersonPhotoDto} from '../models';
import { ConfirmationService, MessageService } from '@openng/optimus-ui/api';
import { DatePickerModule } from '@openng/optimus-ui/datepicker';
import { Code, RelationShipView, Person, mState, RelationShip, PersonPhotoDto, enumPhoto } from '../models';
import { AppSettingService, LookupService, Utils } from '../shares';
import { PersonService } from './person.service';
import { ButtonModule } from 'primeng/button';
import { SelectModule } from 'primeng/select';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from '@openng/optimus-ui/button';
import { SelectModule } from '@openng/optimus-ui/select';
import { CheckboxModule } from '@openng/optimus-ui/checkbox';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import moment from 'moment';
import { HttpEvent } from '@angular/common/http';
import { TableModule } from 'primeng/table';
import { TableModule } from '@openng/optimus-ui/table';
import { Pickperson } from '../pickperson/pickperson';
import { ImageDisplayComponent } from '../pickperson/image.display';
import { TooltipModule } from 'primeng/tooltip';
import { AutoFocusModule } from 'primeng/autofocus';
import { TooltipModule } from '@openng/optimus-ui/tooltip';
import { AutoFocusModule } from '@openng/optimus-ui/autofocus';
import { DomSanitizer } from '@angular/platform-browser';
import { PhotoList } from '../attachphoto/photolist';
import { InputNumberDesignTokens } from '@openng/optimus-ui-themes/types/inputnumber';
export interface FileUploadEvent {
originalEvent: HttpEvent<any>;
@@ -190,9 +191,9 @@ doDeleteFile(): void {
this.profileFile = null;
}
doDeleteImage(fileName:string) : void {
const familyId = this.adminuserForm.value.id;
const data = { fileName, familyId };
const delete$ = this.personService.deleteUploadFile(data);
const personId = this.adminuserForm.value.id;
const photoType = enumPhoto.Profile;
const delete$ = this.personService.deletePersonPhotoFile(personId, photoType);
this.subscription.add(delete$.subscribe(
{
next: x => {
@@ -306,7 +307,9 @@ get isFieldsChange() {
doViewImage(imageName:string): void {
const ref = this.dialogService.open(ImageDisplayComponent, {
data: {
imageName,
imageName,
personId: this._id,
photyType: enumPhoto.Profile
},
header: 'View Image',
draggable: true,
@@ -358,7 +361,7 @@ assignValue(item:Person): void {
}
if (item.image && item.image.length > 0)
{
this.loadImage(item.image);
this.loadImage(item.image, item.id, enumPhoto.Profile);
}
// disable use false//true for not disable.
this.subChanged$.next(true);
@@ -367,8 +370,10 @@ assignValue(item:Person): void {
// convenience getter for easy access in form fields
get f() { return this.adminuserForm.controls;}
loadImage(fileName: string| null): void {
const download = this.personService.downloadFile(fileName!);
loadImage(fileName: string | null,
personId: number, photoType: number): void {
if (fileName) {
const download = this.personService.downloadPersonPhoto64(fileName, personId, photoType);
this.subscription.add(download.subscribe({
next: x => {
if (x.statusCode == 1)
@@ -384,8 +389,10 @@ assignValue(item:Person): void {
},
error: e => console.error("error in download image", e)
}));
}
displayIamge64(baseImage: string): void
}
}
displayIamge64(baseImage: string): void
{
const changeUrl = (this.sanitizer.bypassSecurityTrustResourceUrl(baseImage) as any).changingThisBreaksApplicationSecurity;
console.log('this is bypassSecurityTrustResourceUrl', changeUrl);
@@ -493,10 +500,12 @@ assignValue(item:Person): void {
if (x.statusCode >= 1)
{
item.id = x.data;
this._id = item.id;
// this.messageService.add({severity:'success', summary: 'Save user', detail: item.firstName + " " + item.lastName });
//this.router.navigate([this.returnUrl]);
console.log("the person after save", item);
const list = this.getPhotoListforSave();
console.log("the person after save photo, person", list, item);
if (list.length > 0)
{
this.savePhotoList(list, item);
@@ -715,7 +724,7 @@ getPhotoListforSave(): PersonPhotoDto[] {
let result: PersonPhotoDto[] =[];
for (let i = 0; i < this.photoList.length; i++)
{
if (this.photoList[i].id < 0)
if (this.photoList[i].id < 1)
{
result.push(this.photoList[i]);
}
@@ -732,23 +741,20 @@ savePhotoList(list: PersonPhotoDto[], item:Person): void {
if (file)
formData.append("files", file, file.name);
}
formData.append('personId', this._id.toString());
formData.append('personId', item.id.toString());
console.log("savephotolist person id", item.id, item);
const personPhoto$ = this.personService.savePersonPhotoList(formData);
this.subscription.add(personPhoto$.subscribe(
{
next: x => {
if (x.statusCode == 1) {
//const filename = x.data;
//this.adminuserForm.patchValue({ image: filename });
this.ref.close(item);
//this.cdr.detectChanges();
}
else
this.messageService.add({ severity: 'error', summary: 'Error save attach photos files', detail: 'Fail to photos file: ' + x.message });
},
error: e =>
this.messageService.add({ severity: 'error', summary: 'Error attach photos file', detail: 'Fail to photos file: ' })
this.messageService.add({ severity: 'error', summary: 'Error attach photos file', detail: 'Fail to photos file: ' })
}
));
+37 -10
View File
@@ -4,7 +4,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Staff,StaffSearch, StaffView, ResultModel,ConfigureUrl,ResetPassword, Person, PersonContainer, RelationShip } from '../models';
import { AppSettingService } from '../shares';
import { TreeNode } from 'primeng/api';
import { TreeNode } from '@openng/optimus-ui/api';
@Injectable({ providedIn: 'root' })
export class PersonService {
@@ -77,33 +77,60 @@ export class PersonService {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.personUrl + "/UploadImage";
return this.http.post<ResultModel<string>>(baseUrl, data);
}
downloadFile(imageName: string): Observable<ResultModel<string>> {
downloadFile_dontusethis(imageName: string, personId: number,
photoType: number): Observable<ResultModel<string>> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DownloadFile";
const data = {
fileName: imageName
fileName: imageName,
id: personId,
photoType
};
return this.http.post<ResultModel<string>>(baseUrl, data);
}
deleteUploadFile(data: any): Observable<ResultModel<number>> {
deleteUploadFile_dotnotuse(data: any): Observable<ResultModel<number>> {
//data ={filename};
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.personUrl + "/DeleteUploadFile";
return this.http.post<ResultModel<number>>(baseUrl, data);
}
deletePersonPhotoFile(id: number): Observable<ResultModel<number>> {
const data ={id, fileName:''};
deletePersonPhotoFile(personId: number, photoType: number): Observable<ResultModel<number>> {
const data = {
id: personId,
fileName: '',
photoType
};
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DeletePersonPhoto";
return this.http.post<ResultModel<number>>(baseUrl, data);
}
deletePersonPhotoFileById(id: number): Observable<ResultModel<number>> {
const data = {
id
};
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DeletePersonPhotoById";
return this.http.post<ResultModel<number>>(baseUrl, data);
}
savePersonPhotoList(data:FormData): Observable<ResultModel<number>> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/SavePersonPhoto";
return this.http.post<ResultModel<number>>(baseUrl, data);
}
downloadPersonPhoto(id: number): Observable<ResultModel<string>> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DownloadPersonPhoto";
downloadPersonPhoto64(imageName: string, personId: number,
photoType: number): Observable<ResultModel<string>> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DownloadPersonPhoto64";
const data = {
id,
fileName: ''
id: personId,
photoType,
fileName: imageName
};
return this.http.post<ResultModel<string>>(baseUrl, data);
}
downloadPersonPhoto(imageName: string, personId: number,
photoType: number): Observable<any> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DownloadPersonPhoto";
const data = {
id: personId,
photoType,
fileName: imageName
};
return this.http.post<any>(baseUrl, data);
}
}
+8 -6
View File
@@ -5,8 +5,8 @@ import { PersonService } from '../person';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ButtonModule } from '@openng/optimus-ui/button';
import { DynamicDialogConfig, DynamicDialogRef } from '@openng/optimus-ui/dynamicdialog';
@Component({
selector: 'app-image-display',
@@ -27,12 +27,14 @@ export class ImageDisplayComponent implements OnInit, OnDestroy {
ngOnInit(): void {
const imageName = this.config.data.imageName;
this.loadImage(imageName);
const imageName = this.config.data.imageName;
const personId = this.config.data.personId;
const photoType = this.config.data.photyType;
this.loadImage(imageName, personId, photoType);
}
loadImage(fileName: string| null): void {
const download = this.personService.downloadFile(fileName!);
loadImage(fileName: string | null, personId: number, photoType: number): void {
const download = this.personService.downloadPersonPhoto64(fileName!, personId, photoType);
this.subscription.add(download.subscribe({
next: x => {
if (x.statusCode == 1)
+6 -6
View File
@@ -1,13 +1,13 @@
import { CommonModule } from '@angular/common';
import { Component, OnDestroy, OnInit, signal, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Table, TableModule } from 'primeng/table';
import { Table, TableModule } from '@openng/optimus-ui/table';
import { Person } from '../models';
import { IconFieldModule } from 'primeng/iconfield';
import { InputIconModule } from 'primeng/inputicon';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { IconFieldModule } from '@openng/optimus-ui/iconfield';
import { InputIconModule } from '@openng/optimus-ui/inputicon';
import { DynamicDialogConfig, DynamicDialogRef } from '@openng/optimus-ui/dynamicdialog';
import { ButtonModule } from '@openng/optimus-ui/button';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
@Component({
selector: 'app-pickperson',
+1 -1
View File
@@ -1,6 +1,6 @@
import moment from "moment";
import { Person, User, userRole } from "../models"
import { TreeNode } from "primeng/api";
import { TreeNode } from "@openng/optimus-ui/api";
type MyProName = "fatherId" | "motherId";
+3 -3
View File
@@ -9,11 +9,11 @@ import { Subscription } from 'rxjs';
import { Router } from '@angular/router';
import { StaffService } from './staff.service';
import { AuthenticationService } from '../user-services';
import { TableModule } from 'primeng/table';
import { TableModule } from '@openng/optimus-ui/table';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from '@openng/optimus-ui/button';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
import { selectStaffLoaded,selectStaff } from '../state/staff.selectors';
@Component({
+5 -5
View File
@@ -2,15 +2,15 @@ import { Component, inject, input, numberAttribute, OnDestroy, OnInit } from '@a
import { Router, ActivatedRoute } from '@angular/router';
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { Subject, Subscription} from 'rxjs';
import { MessageService } from 'primeng/api';
import { MessageService } from '@openng/optimus-ui/api';
import {Staff, Code, userRole} from '../models';
import { LookupService, Utils } from '../shares';
import { StaffService } from './staff.service';
import { ButtonModule } from 'primeng/button';
import { SelectModule } from 'primeng/select';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from '@openng/optimus-ui/button';
import { SelectModule } from '@openng/optimus-ui/select';
import { CheckboxModule } from '@openng/optimus-ui/checkbox';
import { InputTextModule } from '@openng/optimus-ui/inputtext';
@Component({
templateUrl: 'staff.edit.component.html',
+2 -2
View File
@@ -2,13 +2,13 @@ import { Component, inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { Subject, Subscription} from 'rxjs';
import { MessageService } from 'primeng/api';
import { MessageService } from '@openng/optimus-ui/api';
import {Staff, Code, userRole, ResetPassword} from '../models';
import { LookupService, Utils } from '../shares';
import { StaffService } from './staff.service';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { ButtonModule } from '@openng/optimus-ui/button';
@Component({
templateUrl: 'staff.pass.component.html',
+8 -5
View File
@@ -1,17 +1,15 @@
@if (currentUserS() && isAuth)
@if (currentUserS() && isAuth == true)
{
<div class="flex flex-column justify-between md:flex-row rounded" style="background-color:transparent">
<div style="margin-top:5px;padding-right:35px; cursor: pointer;width:400px;">
<img (click)="onHome()" src="images/application_image.png" alt="Banner" style="height:80px;width:100%" />
</div>
<div class="flex justify-end items-end flex-row">
<div class="flex-column">
<label class=" flex justify-end items-end mr-1 mb-2 myname">{{loginUser}}</label>
<div>
<span class="md:hidden flex justify-end"> <!--hidden on md screen show on small-->
<button type="button" pButton icon="pi pi-align-justify" (click)="onemenu.toggle($event);"></button>
<p-menu #onemenu [popup]="true" [model]="oneMenu"></p-menu>
</span>
<span class="hidden md:inline-flex"> <!--hidden when small screen-->
<p-buttonGroup >
<button type="button" pButton icon="pi pi-slack" label="Home" (click)="onHome()"
@@ -38,4 +36,9 @@
</div>
</div>
</div>
<!--put in the left new row if in mobile not much room-->
<span class="md:hidden flex justify-start"> <!--hidden on md screen show on small-->
<button type="button" pButton icon="pi pi-align-justify" (click)="onemenu.toggle($event);"></button>
<p-menu #onemenu [popup]="true" [model]="oneMenu"></p-menu>
</span>
}
+4 -4
View File
@@ -1,10 +1,10 @@
import { Component, OnInit, OnDestroy, effect, computed, Signal, } from '@angular/core';
import { Router } from '@angular/router';
import { CommonModule } from '@angular/common';
import { MenuItem } from 'primeng/api';
import { MenuModule } from 'primeng/menu';
import { ButtonModule} from 'primeng/button';
import { ButtonGroupModule } from 'primeng/buttongroup';
import { MenuItem } from '@openng/optimus-ui/api';
import { MenuModule } from '@openng/optimus-ui/menu';
import { ButtonModule} from '@openng/optimus-ui/button';
import { ButtonGroupModule } from '@openng/optimus-ui/buttongroup';
import { Subscription } from 'rxjs';
import { AuthenticationService } from '../user-services';
import { Utils } from '../shares';
+2 -2
View File
@@ -1,7 +1,7 @@
/* You can add global styles to this file, and also import other style files */
@import "tailwindcss";
@plugin "tailwindcss-primeui";
@import "primeicons/primeicons.css";
@plugin "@openng/optimus-ui-tailwindcss";
@import "@openng/icons/openng-icons.css";
html,
body {
height: 100%;
-1
View File
@@ -10,7 +10,6 @@
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
+38
View File
@@ -0,0 +1,38 @@
import { HttpInterceptorFn } from '@angular/common/http';
export const credentialsInterceptor: HttpInterceptorFn = (req, next) => {
// Clone the request to add the withCredentials property
const clonedRequest = req.clone({
withCredentials: true
});
return next(clonedRequest);
};
typescriptimport { provideHttpClient, withInterceptors } from '@angular/common/http';
import { credentialsInterceptor } from './credentials.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([credentialsInterceptor]) // Register it here
)
]
};
2. Individual Request ConfigurationIf you only want to send cookies to specific endpoints,
pass the withCredentials option directly inside your service.
typescriptimport { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ApiService {
private http = inject(HttpClient);
getData() {
return this.http.get('https://your-api-domain.com', {
withCredentials: true // Enables cookies for this request only
});
}
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh -e
docker-compose up
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh -e
docker-compose down
+40
View File
@@ -0,0 +1,40 @@
networks:
dev:
driver: bridge
services:
postgresdb:
image: postgres
container_name: ${DOCKER_REGISTRY-}postgresql-db
ports:
- "5432:5432"
environment:
- POSTGRES_DB=FamilyTreeDB
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=Positive~1
- PGDATA=/var/lib/postgresql/data/pgdata
restart: always
volumes:
- postgresdata:/var/lib/postgresql/data
networks:
- dev
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin4_container
restart: always
depends_on:
- "postgresdb"
volumes:
- pgadmin-data:/var/lib/pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: postgres@domain.com
PGADMIN_DEFAULT_PASSWORD: Positive~1
ports:
- "5050:5050"
networks:
- dev
volumes:
pgadmin-data:
external: true
postgresdata:
external: true
Binary file not shown.
-30
View File
@@ -1,30 +0,0 @@
**/.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
View File
@@ -1,63 +0,0 @@
###############################################################################
# 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
-363
View File
@@ -1,363 +0,0 @@
## 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
-31
View File
@@ -1,31 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34202.233
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FamilyTreeAPI", "FamilyTreeAPI\FamilyTreeAPI.csproj", "{9B95839F-8AE3-4725-BCC1-DC73D1899CC6}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{81DDED9D-158B-E303-5F62-77A2896D2A5A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9B95839F-8AE3-4725-BCC1-DC73D1899CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B95839F-8AE3-4725-BCC1-DC73D1899CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B95839F-8AE3-4725-BCC1-DC73D1899CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B95839F-8AE3-4725-BCC1-DC73D1899CC6}.Release|Any CPU.Build.0 = Release|Any CPU
{81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C7DF2338-DFC8-47CE-9AA7-C05C3C2ABEF7}
EndGlobalSection
EndGlobal
@@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.8",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
@@ -1,7 +0,0 @@
using System;
namespace FamilyTreeAPI.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }
@@ -1,36 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FamilyTreeAPI.Authorization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
// private readonly IList<AdminRole> _roles;
/*
public AuthorizeAttribute(params AdminRole[] roles)
{
_roles = roles ?? new AdminRole[] { };
}
*/
public void OnAuthorization(AuthorizationFilterContext context)
{
// skip authorization if action is decorated with [AllowAnonymous] attribute
var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
if (allowAnonymous)
return;
// authorization
// var user = (User)context.HttpContext.Items["User"];
// if (user == null || (_roles.Any() && !_roles.Contains(user.Role)))
// {
// not logged in or role not authorized
// context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
//}
}
}
@@ -1,38 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Linq;
using System.Threading.Tasks;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Authorization;
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly AppSettings _appSettings;
public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
{
_next = next;
_appSettings = appSettings.Value;
}
public async Task Invoke(HttpContext context, IJwtUtils jwtUtils)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
{
var user = jwtUtils.ValidateJwtToken(token);
if (user != null)
{
// attach user to context on successful jwt validation
//here to put in the real user
//TODO if you want to add information for User
context.Items["User"] = user;
}
}
await _next(context);
}
}
@@ -1,111 +0,0 @@
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
namespace FamilyTreeAPI.Authorization;
public class JwtUtils : IJwtUtils
{
private readonly AppSettings _appSettings;
public JwtUtils(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public string GenerateJwtToken(UserDto user)
{
// generate token that is valid for 2 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] {
new Claim("id", user.Id.ToString()),
new Claim("subject", user.Username),
new Claim("firstName", user.FirstName + " " + user.LastName)
{
}
//add more if we need it
}
),
Expires = DateTime.UtcNow.AddDays(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
// get back id and username. in subject
public UserDto? ValidateJwtToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
// var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); //old
int userId = 0;
string username = "";
string firstName = "";
Claim claim;
for (int i = 0; i< jwtToken.Claims.Count(); i++)
{
claim = jwtToken.Claims.ElementAt(i);
if (claim.Type == "id")
{
userId = int.Parse(claim.Value);
}
else if (claim.Type == "subject")
{
username = claim.Value;
}
else if (claim.Type == "firstName")
{
firstName = claim.Value;
}
if (!string.IsNullOrEmpty(username)
&& !string.IsNullOrEmpty(firstName)
&& userId > 0)
{
break;
}
}
UserDto user = new UserDto();
user.Id = userId;
user.Username = username;
user.FirstName = firstName;
// return user id from JWT token if validation successful
return user;
}
catch
{
// return null if validation fails
return null;
}
}
}
@@ -1,82 +0,0 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Repository;
namespace FamilyTreeAPI.Controllers;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;
[ApiController]
[Route("api/[controller]")]
public class FileUploadController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly ImportPersonRepository _importPersonRepository;
private readonly IConfiguration _config;
public FileUploadController(IWebHostEnvironment hostingEnvironment, ImportPersonRepository importPersonRepository, IConfiguration config)
{
_hostingEnvironment = hostingEnvironment;
_importPersonRepository = importPersonRepository;
_config = config;
}
[HttpPost("[action]")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
List<CodeDto<string>> output = new ();
if (file == null || file.Length == 0)
{
return BadRequest("No file uploaded.");
}
try
{
// Define the upload directory
// var uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
string importFolder = _config.GetValue<string>("AppSettings:ImportFolder");
var uploadsFolder = Path.Combine(_hostingEnvironment.ContentRootPath, importFolder);
if (!Directory.Exists(uploadsFolder))
{
Directory.CreateDirectory(uploadsFolder);
}
// Create a unique file name to avoid overwriting
var uniqueFileName = Path.GetFileNameWithoutExtension(file.FileName)
+ "_" + System.Guid.NewGuid().ToString()
+ Path.GetExtension(file.FileName);
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
// Save the file to the server
using (var stream = new FileStream(filePath, FileMode.Create))
{
// await file.CopyToAsync(stream);
await file.CopyToAsync(stream);
// output = await _importPersonRepository.ImportPerson(stream, "Sheet1");
}
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
output = await _importPersonRepository.ImportPerson(stream, "Sheet1");
}
//return Ok(new { FileName = uniqueFileName, FilePath = filePath });
return Ok(output);
}
catch (System.Exception ex)
{
return StatusCode(500, $"Internal server error: {ex.Message}");
}
}
[HttpPost("[action]")]
public async Task<IActionResult> DownloadFile(DownloadFileCriteria criteria)
{
var rev = await _importPersonRepository.DownloadFile(criteria);
return Ok(rev);
}
}
@@ -1,68 +0,0 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using Microsoft.AspNetCore.Mvc;
namespace FamilyTreeAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LookupController : ControllerBase
{
private readonly ILookup _repo;
public LookupController(ILookup repo)
{
_repo = repo;
}
[HttpGet("[action]")]
public async Task<IActionResult> LoadLookup(string type)
{
var list = await _repo.GetLookupAsync(type);
return Ok(list);
}
[HttpGet("[action]")]
public async Task<IActionResult> LoadLookupEdit(string type)
{
var list = await _repo.GetLookupEditAsync(type);
return Ok(list);
}
[HttpGet("[action]")]
public async Task<IActionResult> GetPersons()
{
var list = await _repo.GetPersonsAsync();
return Ok(list);
}
[HttpGet("[action]")]
public async Task<IActionResult> GetStaffs()
{
var list = await _repo.GetStaffAsync();
return Ok(list);
}
[HttpPost]
public async Task<IActionResult> Lookup([FromBody] LookupEditDto model)
{
//var currentUser = (User?)(HttpContext.Items["User"]);
//if (null == currentUser)
// return Unauthorized(new { message = "Unauthorized" });
var response = await _repo.SaveLookupAsync(model);
return Ok(response);
}
[HttpGet]
public async Task<IActionResult> Lookup(int id, string type)
{
//lookup/id?type='abc'
/*
// only admins can access other user records
var currentUser = (User)HttpContext.Items["User"];
if (id != currentUser.Id && currentUser.Role != Role.Admin)
return Unauthorized(new { message = "Unauthorized" });
*/
var retval = await _repo.GetLookupEditByIdAsync(id, type);
return Ok(retval);
}
}
}
@@ -1,110 +0,0 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace FamilyTreeAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PersonController : ControllerBase
{
private readonly IPerson _repo;
public PersonController(IPerson repo)
{
_repo = repo;
}
[HttpPost("[action]")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
var keys = Request.Form;
var files = Request.Form.Files;
ResultModel<string> ret = new();
if (files.Count > 0)
{
StringValues familyId = "";
keys.TryGetValue("familyId", out familyId);
UploadCriteria criteria = new();
criteria.File = file;
criteria.FamilyId = familyId;
criteria.FileName = file.FileName;
ret = await _repo.UploadImage(criteria);
}
return Ok(ret);
}
//DeleteUploadFile
[HttpPost("[action]")]
public ActionResult DeleteUploadFile(DeleteFileCriteria criteria)
{
ResultModel<int> ret = new();
if (!string.IsNullOrEmpty(criteria.Filename))
{
ret = _repo.DeleteUploadFile(criteria);
}
return Ok(ret);
}
[HttpPost("[action]")]
public async Task<IActionResult> SearchPerson(PersonCriteria criteria)
{
var list = await _repo.GetPerson(criteria);
return Ok(list);
}
[HttpPost("[action]")]
public async Task<IActionResult> GetChildress(ChildCriteria criteria)
{
var list = await _repo.GetChildren(criteria);
return Ok(list);
}
[HttpPost("[action]")]
public async Task<IActionResult> GetFamilyTreeBy(FamilyCriteria criteria)
{
var list = await _repo.GetFamilyTreeBy(criteria);
return Ok(list);
}
[HttpGet("[action]/{id}")]
public async Task<IActionResult> GetByPersonFamily(int id)
{
var list = await _repo.GetByFamilyAsync(id);
return Ok(list);
}
[HttpGet("[action]/{id}")]
public async Task<IActionResult> GetById(int id)
{
var list = await _repo.GetByIdAsync(id);
return Ok(list);
}
[HttpPost("[action]")]
public async Task<IActionResult> DeleteById(DeleteCriteria<int> criteria)
{
var list = await _repo.DeleteAsync(criteria.Id);
return Ok(list);
}
[HttpPost("[action]")]
public async Task<IActionResult> SavePerson([FromBody] PersonForSave model)
{
//var currentUser = (User?)(HttpContext.Items["User"]);
//if (null == currentUser)
// return Unauthorized(new { message = "Unauthorized" });
var response = await _repo.SaveAsync(model);
return Ok(response);
}
}
}
@@ -1,47 +0,0 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace FamilyTreeAPI.Controllers;
[Route("api/[controller]")]
[ApiController]
public class RelationShipController : ControllerBase
{
private readonly IRelationShipd _repo;
public RelationShipController(IRelationShipd repo)
{
_repo = repo;
}
[HttpGet("[action]/{id}")]
public async Task<IActionResult> GetById(int id)
{
var list = await _repo.GetByIdAsync(id);
return Ok(list);
}
[HttpGet("[action]/{id}")]
public async Task<IActionResult> GetByPersonId(int id)
{
var list = await _repo.GetByPersonIdAsync(id);
return Ok(list);
}
[HttpPost("[action]/{id}")]
public async Task<IActionResult> DeleteById(int id)
{
var list = await _repo.GetByIdAsync(id);
return Ok(list);
}
[HttpPost("[action]")]
public async Task<IActionResult> SaveRelationShip([FromBody] RelationShiftContainer list)
{
//var currentUser = (User?)(HttpContext.Items["User"]);
//if (null == currentUser)
// return Unauthorized(new { message = "Unauthorized" });
var response = await _repo.SaveAsync(list.relationShips);
return Ok(response);
}
}
@@ -1,71 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using System.Threading.Tasks;
namespace FamilyTreeAPI.Controllers;
// [Authorize]
[ApiController]
[Route("api/[controller]")]
public class StaffController : ControllerBase
{
private readonly IStaff _staff;
public StaffController(IStaff staff)
{
_staff = staff;
}
[HttpPost("[action]")]
public async Task<IActionResult> SaveStaff([FromBody] StaffDto model)
{
//var currentUser = (User?)HttpContext.Items["User"];
//if (null == currentUser)
// return Unauthorized(new { message = "Unauthorized" });
var response = await _staff.SaveStaff(model);
return Ok(response);
}
[HttpPost("[action]")]
public async Task<IActionResult> ResetPassStaff([FromBody] ResetPassDto model)
{
//var currentUser = (User?)HttpContext.Items["User"];
//if (null == currentUser)
// return Unauthorized(new { message = "Unauthorized" });
var response = await _staff.ResetPassword(model);
return Ok(response);
}
[HttpPost("[action]")]
public async Task<IActionResult> SearchStaff([FromBody] StaffCriteria criteria)
{
var retval = await _staff.GetStaff(criteria);
return Ok(retval);
}
[HttpGet("{id}")]
public async Task<IActionResult> Staff( int id)
{
/*
// only admins can access other user records
var currentUser = (User)HttpContext.Items["User"];
if (id != currentUser.Id && currentUser.Role != Role.Admin)
return Unauthorized(new { message = "Unauthorized" });
*/
var retval = await _staff.GetStaffById(id);
return Ok(retval);
}
[HttpPost("[action]")]
public async Task<IActionResult> DeleteStaff(DeleteCriteria<int> criteria)
{
var currentUser = (UserDto?)HttpContext.Items["User"];
if (null == currentUser)
return Unauthorized(new { message = "Unauthorized" });
var retval = await _staff.Delete(criteria.Id);
return Ok(retval);
}
}
@@ -1,73 +0,0 @@
using FamilyTreeAPI.Interface;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Controllers;
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[AllowAnonymous]
[HttpPost("[action]")]
public async Task<IActionResult> Login([FromBody] AuthenticateRequest model)
{
string remoteIpAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
var response = await _userService.Login(model);
return Ok(response);
}
//[AllowAnonymous]
//[HttpPost("[action]")]
//public async Task<IActionResult> LoginAD(AuthenticateRequest model)
//{
// var response = await _userService.LoginAD(model);
// return Ok(response);
//}
[AllowAnonymous]
[HttpPost("[action]")]
public async Task<IActionResult> LoginApiAD([FromBody] AuthenticateRequest model)
{
string remoteIpAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
var response = await _userService.LoginApiAD(model, remoteIpAddress);
return Ok(response);
}
[HttpPost("[action]")]
public async Task<IActionResult> Logout()
{
var currentUser = (UserDto)HttpContext.Items["User"];
if (null == currentUser)
return Unauthorized(new { message = "Unauthorized" });
string token = Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
string remoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
var response = await _userService.Logout(token, currentUser, remoteIpAddress);
return Ok(response);
}
//[AllowAnonymous]
//[HttpGet("[action]")]
//public async Task<IActionResult> SearchADStaff(string stafflinkNo)
//{
// var user = await _userService.SearchADStaff(stafflinkNo);
// return Ok(user);
//}
/*
[AllowAnonymous]
[HttpGet("[action]")]
public async Task<ResultModel<User>> SearchADStaff(string stafflinkNo)
{
var user = await _userService.SearchADStaff(stafflinkNo);
return user;
}
*/
}
-105
View File
@@ -1,105 +0,0 @@
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
namespace FamilyTreeAPI.Models
{
public partial class FamilyTreeDBContext
{
private enum EnumStaff
{
Id =0,
Firstname,
Lastname,
Email,
Stype,
Active,
Role,
Password
};
private enum EnumStaffWork
{
id =0,
staffid,
description,
startdate,
sactive,
did,
starttime,
stoptime,
task
}
private enum EnumServiceTask
{
id = 0,
staffid,
sdate,
description,
code,
status,
assignTo,
clientid,
serviceno
}
public async Task<List<StaffDto>> LoadStaffAsync(StaffCriteria criteria)
{
List<StaffDto> list = new();
using (DbConnection connect = this.Database.GetDbConnection())
{
DbCommand command = connect.CreateCommand();
command.CommandType = System.Data.CommandType.Text;
command.CommandText = "SELECT * From usp_search_user(:iemail,:ifirstname,:ilastname)";
var loginParam = command.CreateParameter();
loginParam.ParameterName = "iemail";
loginParam.Value = string.IsNullOrEmpty(criteria.Email) ? "" : criteria.Email;
var fistnameParam = command.CreateParameter();
fistnameParam.ParameterName = "ifirstname";
fistnameParam.Value = string.IsNullOrEmpty(criteria.FirstName) ? "" : criteria.FirstName;
var surnameParam = command.CreateParameter() ;
surnameParam.ParameterName = "ilastname";
surnameParam.Value = string.IsNullOrEmpty(criteria.LastName) ? "" : criteria.LastName;
command.Parameters.Add(loginParam);
command.Parameters.Add(fistnameParam);
command.Parameters.Add(surnameParam);
await connect.OpenAsync();
DbDataReader reader = await command.ExecuteReaderAsync();
StaffDto staff;
int idx = 0;
while (reader.Read())
{
staff = new();
idx = (int)EnumStaff.Id;
staff.Id = reader.GetFieldValue<int>(idx);
idx = (int)EnumStaff.Firstname;
staff.Firstname = reader.GetFieldValue<string?>(idx);
idx = (int) EnumStaff.Lastname;
staff.Lastname = reader.GetFieldValue<string?>(idx);
idx = (int)EnumStaff.Email;
staff.Email = reader.GetFieldValue<string?>(idx);
idx = (int)EnumStaff.Active;
staff.Active = reader.GetFieldValue<bool?>(idx);
idx = (int)EnumStaff.Role;
staff.RoleType = reader.GetFieldValue<int?>(idx);
idx = (int)EnumStaff.Password;
if (!reader.IsDBNull(idx))
staff.Password = reader.GetFieldValue<string?>(idx);
list.Add(staff);
}
}
return list;
}
}
}
-39
View File
@@ -1,39 +0,0 @@
# 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.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
#FROM mcr.microsoft.com/dotnet/aspnet:9::.0 AS base
ARG BUILD_CONFIGURATION=Release
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["FamilyTreeAPI/FamilyTreeAPI.csproj", "FamilyTreeAPI/"]
RUN dotnet restore "./FamilyTreeAPI/FamilyTreeAPI.csproj"
COPY . .
WORKDIR "/src/FamilyTreeAPI"
RUN dotnet build "./FamilyTreeAPI.csproj" -c Release -o /app/build
# create directory for import folder, and image folder
RUN mkdir app_import
RUN mkdir app_images
VOLUME /app_import
VOLUME /app_images
# docker run -v my_path_name_volume_host:/app_import my_image_name
# docker run -v my_path_named_volume_host:/app_images my_image_name
# docker cp /home/user/my_app/config.json my_container:/app/config.json
# docker cp /home/user/my_app/data my_container:/app/data ### copy folder data
# docker cp mycontainer:/app/output.txt /home/user/documents/
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
RUN dotnet publish "./FamilyTreeAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FamilyTreeAPI.dll"]
@@ -1,18 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class AppSettings
{
public string Secret { get; set; }
public string SQLConnectionString { get; set; }
public string LoginWebAPI { get; set; }
public string ClientURL { get; set; }
}
/*
entity.HasOne(d => d.Staff)
.WithMany(p => p.Staffworks)
.HasForeignKey(d => d.Staffid)
.HasConstraintName("staffwork_staffid_fkey")
.OnDelete(DeleteBehavior.ClientCascade);
*/
@@ -1,15 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace FamilyTreeAPI.Entities;
public class AuthenticateRequest
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
// public string Lhd { get; set; }
}
@@ -1,35 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class AuthenticateResponse
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Token { get; set; }
public string Email { get; set; }
public string? Department { get; set; }
public string? Phone { get; set; }
public string? Position { get; set; }
public string? ManagerEmail { get; set; }
public int Role { get; set; }
public AuthenticateResponse(UserDto user, string token, int role)
{
Id = user.Id;
FirstName = user.FirstName;
LastName = user.LastName;
Username = user.Username;
Email = user.Email;
Department = user.Department;
Position = user.Position;
ManagerEmail = user.ManagerEmail;
Role = role;
Token = token;
Phone = user.Phone;
}
}
@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FamilyTreeAPI.Entities;
public class FileContent
{
public byte[] Content { get; set; }
public string FileName { get; set; }
}
public class DownloadFileCriteria
{
public string FileName { get; set; }
}
public class UploadCriteria
{
public string FileName { get; set; }
public string FamilyId { get; set; }
public IFormFile File { get; set; }
}
public class DeleteFileCriteria
{
public int FamilyId { get; set; }
public string Filename { get; set; }
}
@@ -1,35 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class CodeDto<T>
{
public T Code { get; set; }
public string Description { get; set; }
}
//using to store id from file then after insert now the real id from db
// the put that in and update the db
public class MappingFatherMother
{
public int TId { get; set; }
public int IId { get; set; }
public int TFatherId { get; set; }
public int TMotherId { get; set; }
public int IFatherId { get; set; }
public int IMotherId { get; set; }
}
public class LookupDto
{
public int Id { get; set; } = 0;
public string CodeId { get; set; }
public string Description { get; set; }
}
public class LookupEditDto
{
public int Id { get; set; } = 0;
public string CodeId { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public string Type { get; set; }
}
@@ -1,53 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class ChildCriteria
{
public int FatherId { get; set; }
public int MotherId { get; set; }
}
public class FamilyCriteria
{
public bool UseFather { get; set; }
public bool UseMother { get; set; }
}
public class PersonCriteria
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class PersonForSave
{
public PersonDto Person { get; set; }
public string? FormData { get; set; }
public string? FileName { get; set; }
}
public partial class PersonDto
{
public PersonDto()
{
}
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? Image { get; set; }
public string? Sex { get; set; }
public string? Address { get; set; }
public bool? Alive { get; set; }
public string? dob { get; set; }
public int? FatherId { get; set; }
public int? MotherId { get; set; }
public List<RelationShipDto>? RelationShips { get; set; }
}
@@ -1,22 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class RelationShiftContainer
{
public int familyId {get; set;}
public List<RelationShipDto> relationShips {get; set;}
}
public class ImportRelation
{
public int FatherId { get; set; }
public int MotherId { get; set; }
}
public class RelationShipDto
{
public enumState State { get; set; }
public int Id { get; set; }
public int PersonId { get; set; }
public int RelatePersonId { get; set; }
}
@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FamilyTreeAPI.Entities;
public enum enumState
{
Delete = -1,
NoChange = 0,
New = 1,
Modify = 2,
}
public class ResultModel<T>
{
public T Data { get; set; }
public int StatusCode { get; set; }
public string Message { get; set; }
}
public class DeleteCriteria<T>
{
public T Id { get; set; }
}
@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace FamilyTreeAPI.Entities;
public partial class StaffCriteria
{
public string Email { get; set; } = null!;
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
public partial class StaffDto
{
public int Id { get; set; }
public string? Phone { get; set; } = null!;
public string? Firstname { get; set; }
public string? Lastname { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public int? Type { get; set; }
public bool? Active { get; set; }
public int? RoleType { get; set; }
}
public partial class ResetPassDto
{
public int Id { get; set; }
public string Password { get; set; } = null!;
}
@@ -1,17 +0,0 @@
namespace FamilyTreeAPI.Entities;
public class TreeNode<T>
{
public string? Label { get; set; }
public T? Data { get; set; }
public List<TreeNode<T>>? Children { get; set; }
public string? Icon { get; set; }
public bool? Checked { get; set; }
public bool? Leaf { get; set; }
public bool? Expanded { get; set; }
public string? Type { get; set; }
public string StyleClass { get; set; }
public string? Key { get; set; }
public bool? Loading { get; set; }
}
@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FamilyTreeAPI.Entities;
/*
* ROLE ID
* Normal = 1,
Admin = 2,
Manager =3,
*/
public class UserDto
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Username { get; set; }
public string? Department { get; set; }
public string? Phone { get; set; }
public string? Position { get; set; }
public string? ManagerEmail { get; set; }
public int Role { get; set; }
}
@@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Models_old\**" />
<Content Remove="Models_old\**" />
<EmbeddedResource Remove="Models_old\**" />
<None Remove="Models_old\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.6" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.6" />
<PackageReference Include="HotChocolate.Pagination.EntityFramework" Version="14.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="SpreadsheetLight" Version="3.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup>
</Project>
@@ -1,19 +0,0 @@
using FamilyTreeAPI.Models;
namespace FamilyTreeAPI.GraphQL.Query
{
public class QueryFamilyTree
{
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Person> GetPersons([Service] FamilyTreeDBContext dBContext) => dBContext.Persons;
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<staff> GetStaffs([Service] FamilyTreeDBContext dBContext) => dBContext.staff;
}
}
@@ -1,28 +0,0 @@
namespace FamilyTreeAPI.Helper
{
public class Helpers
{
public static string? DateToStr(DateTime? date)
{
string? result = null;
if (date.HasValue)
{
result = date.Value.ToString("yyyy-MM-dd");
}
return result;
}
public static DateTime? DateToDateTime(string? date)
{
DateTime? result = null;
if (!string.IsNullOrEmpty(date))
{
DateTime dt;
if (DateTime.TryParse(date, out dt))
{
result = dt;
}
}
return result;
}
}
}
@@ -1,35 +0,0 @@
The family Tree
Man and Woman
top level
1) TAM (M) has partner Jenny (F)
had child CAO (M)
had Child Emmy (F)
has Child Loan (F)
2) CAO (M) has partner Lin (F)
has child Joe (M)
has child TOM (M)
3) Joe has partner Sophia (F)
has child Olivia (F)
has child Mai (F)
4) Henry has partner Loan (F)
has child Enya (F)
has child Thanh (M)
has child Chuong (M)
5) Henry has partner Brisa (F)
has child Fern (F)
has child Tim (M)
-107
View File
@@ -1,107 +0,0 @@
1)
ubuntu
$) sudo bash
$) apt-get update
$) apt-get install -y dotnet-sdk-9.0
$) apt-get install -y aspnetcore-runtime-9.0
$) apt-get install -y dotnet-runtime-9.0
$) dotnet --info
$) apt-get install nginx
windows
net 9 bundle.
Windows Hosting Bundle Installer!
Copy the Files to the Linux Server
Next, we need to copy the deployment files to the Ubuntu server. Before we move the files,
let’s create the destination folder on the server and set the permissions
so that we can add files to it:
$) cd /var/www
$) sudo mkdir app
$) sudo chmod 777 app
Run the App on a Kestrel Web Server
In a terminal, navigate to the deployment path and run the app in Kestrel:
cd /var/www/app
sudo dotnet DeployingToLinuxWithNginx.dll
//edit nginx config file
$ sudo nano /etc/nginx/sites-avaible/default
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo nginx -t
sudo nginx -s reload
To create the service, we first need to create the configurations in a systemd unit file.
The unit file contains information regarding the unit, which is a service in this case.
For services, it should have the .service extension and contain some
information about the service. These files are required to be in the
/etc/systemd/system directory.
Let’s use “nano” to create the unit file and name it kestrel-app.service:
[Unit]
Description=ASP.NET Core Web App running on Ubuntu
[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/dotnet /var/www/app/DeployingToLinuxWithNginx.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-web-app
# This user should exist on the server and have ownership of the deployment directory
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
After adding the content, save it. Then let’s enable and start the service:
sudo systemctl enable kestrel-app.service
sudo systemctl start kestrel-app.service
postgresql.conf:
Edit postgresql.conf:
Locate the postgresql.conf file, typically in /etc/postgresql/<version>/main/postgresql.conf.
Change the listen_addresses parameter to '*' to allow connections from any IP address, or specify the IP address(es) you want to allow.
```
listen_addresses = '*'
Locate the postgresql.conf file within the container (e.g., /etc/postgresql/<version>/main/postgresql.conf).
Modify listen_addresses:
Change listen_addresses = 'localhost' to listen_addresses = '*' to allow connections from any IP address.
Alternatively, specify the container's IP address.
host all all 0.0.0.0/0 md5
pg_hba.conf
**Edit `pg_hba.conf`:**
* Locate the `pg_hba.conf` file, typically in `/etc/postgresql/<version>/main/pg_hba.conf`.
* Add a line to allow connections from your desired network or specific IP addresses. For example, to allow connections from any IP address on the network using MD5 authentication:
* ```
host all all 0.0.0.0/0 md5
setup password for postgresql
sudo -u postgres psql
postgres=#
then type \password postgres
docker run -p 5432:5432 -e POSTGRES_PASSWORD=123456789 \
-d postgres:9.3.6 \
-c config_file=/path/to/postgresql.conf
@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IJwtUtils
{
public string GenerateJwtToken(UserDto user);
public UserDto? ValidateJwtToken(string token);
}
@@ -1,16 +0,0 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface ILookup
{
Task<ResultModel<int>> SaveLookupAsync(LookupEditDto lookup);
Task<ResultModel<List<LookupDto>>> GetLookupAsync(string type);
Task<ResultModel<Dictionary<string, LookupDto>>> GetLookupDicAsync(string type);
Task<ResultModel<List<LookupEditDto>>> GetLookupEditAsync(string type);
Task<ResultModel<LookupEditDto>> GetLookupEditByIdAsync(int codeId, string type);
Task<ResultModel<List<LookupDto>>> GetPersonsAsync();
Task<ResultModel<List<LookupDto>>> GetStaffAsync();
}
@@ -1,18 +0,0 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IPerson
{
Task<ResultModel<TreeNode<string>>> GetByFamilyAsync(int id);
Task<ResultModel<List<TreeNode<string>>>> GetFamilyTreeBy(FamilyCriteria criteria);
Task<ResultModel<List<PersonDto>>> GetChildren(ChildCriteria criteria);
Task<ResultModel<List<PersonDto>>> GetPerson(PersonCriteria criteria);
Task<ResultModel<Dictionary<int,PersonDto>>> GetDicFamily();
Task<ResultModel<PersonDto>> GetByIdAsync(int id);
Task<ResultModel<int>> DeleteAsync(int id);
Task<ResultModel<string>> UploadImage(UploadCriteria criteria);
ResultModel<int> DeleteUploadFile(DeleteFileCriteria criteria);
Task<ResultModel<int>> SaveAsync(PersonForSave dto);
}
@@ -1,13 +0,0 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IRelationShipd
{
Task<ResultModel<int>> SaveAsync(List<RelationShipDto> dto);
//load by personId
Task<ResultModel<List<RelationShipDto>>> GetByPersonIdAsync(int personId);
Task<ResultModel<RelationShipDto>> GetByIdAsync(int Id);
Task<ResultModel<int>> DeleteAsync(int personId);
}
@@ -1,8 +0,0 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IReport
{
// Task<ResultModel<FileContent>> GetMotorVehicleReportAsync(MotorVehicleCriteria criteria);
// Task<ResultModel<FileContent>> GetStaffWorkReportAsync(StaffWorkCriteria criteria);
}
@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IStaff
{
Task<ResultModel<Dictionary<int,StaffDto>>> GetDicStaffs();
Task<ResultModel<List<StaffDto>>> GetStaff(StaffCriteria criteria);
Task<ResultModel<StaffDto>> GetStaffById(int id);
Task<ResultModel<int>> SaveStaff(StaffDto code);
Task<ResultModel<int>> ResetPassword(ResetPassDto code);
Task<ResultModel<int>> SaveStaffNew(StaffDto adminUser);
Task<ResultModel<int>> Delete(int id);
}
@@ -1,13 +0,0 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IUserService
{
Task<ResultModel<AuthenticateResponse>> LoginApiAD(AuthenticateRequest model, string remoteIpAddress);
Task<ResultModel<AuthenticateResponse>> Login(AuthenticateRequest model);
Task<ResultModel<int>> Logout(string token, UserDto user, string remoteIpAddress);
// Task<ResultModel<User>> SearchApiStaff(string staffLinkNo);
// Task<ResultModel<User>> SearchADStaff(string staffLinkNo);
}
@@ -1,136 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace FamilyTreeAPI.Models
{
public partial class FamilyTreeDBContext : DbContext
{
public FamilyTreeDBContext()
{
}
public FamilyTreeDBContext(DbContextOptions<FamilyTreeDBContext> options)
: base(options)
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}
public virtual DbSet<Person> Persons { get; set; } = null!;
public virtual DbSet<RelationShip> RelationShips { get; set; } = null!;
public virtual DbSet<Lookup> Lookups { get; set; } = null!;
public virtual DbSet<staff> staff { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.ToTable("person");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Address)
.HasMaxLength(120)
.HasColumnName("address");
entity.Property(e => e.Alive).HasColumnName("alive");
entity.Property(e => e.dob).HasColumnName("dob");
entity.Property(e => e.MotherId).HasColumnName("motherid");
entity.Property(e => e.FatherId).HasColumnName("fatherid");
entity.Property(e => e.FirstName)
.HasMaxLength(70)
.HasColumnName("firstname");
entity.Property(e => e.Sex)
.HasMaxLength(10)
.HasColumnName("sex");
entity.Property(e => e.Image)
.HasMaxLength(300)
.HasColumnName("image");
entity.Property(e => e.LastName)
.HasMaxLength(70)
.HasColumnName("lastname");
entity.Property(e => e.Email)
.HasMaxLength(80)
.HasColumnName("email");
entity.Property(e => e.Phone)
.HasMaxLength(30)
.HasColumnName("phone");
});
modelBuilder.Entity<RelationShip>(entity =>
{
entity.ToTable("relationship");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.RelatePersonId).HasColumnName("relatepersonid");
entity.Property(e => e.PersonId).HasColumnName("personid");
});
modelBuilder.Entity<Lookup>(entity =>
{
entity.ToTable("lookup");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Code)
.HasMaxLength(10)
.HasColumnName("code");
entity.Property(e => e.Description)
.HasMaxLength(80)
.HasColumnName("description");
entity.Property(e => e.Lactive).HasColumnName("lactive");
entity.Property(e => e.Type).HasMaxLength(20)
.HasColumnName("ltype");
});
modelBuilder.Entity<staff>(entity =>
{
entity.ToTable("staff");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Email)
.HasMaxLength(80)
.HasColumnName("email");
entity.Property(e => e.Firstname)
.HasMaxLength(70)
.HasColumnName("firstname");
entity.Property(e => e.Lastname)
.HasMaxLength(70)
.HasColumnName("lastname");
entity.Property(e => e.Phone)
.HasMaxLength(30)
.HasColumnName("phone");
entity.Property(e => e.Sactive).HasColumnName("sactive");
entity.Property(e => e.Spassword)
.HasMaxLength(200)
.HasColumnName("spassword");
entity.Property(e => e.Srole).HasColumnName("srole");
entity.Property(e => e.Stype).HasColumnName("stype");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

Some files were not shown because too many files have changed in this diff Show More