put in signal for edit

This commit is contained in:
2025-09-25 17:21:13 +10:00
parent 4b85a90b71
commit ee19397f59
171 changed files with 12181 additions and 15 deletions
+2 -2
View File
@@ -12,9 +12,9 @@ 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_import
RUN mkdir /app_images
RUN mkdir app_images
VOLUME /app_import
+2 -2
View File
@@ -1,5 +1,5 @@
import { ApplicationConfig, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { JwtInterceptor, ErrorInterceptor, initializeApp } from './shares';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
@@ -27,7 +27,7 @@ export const appConfig: ApplicationConfig = {
}
}
}),
provideRouter(routes)
provideRouter(routes, withComponentInputBinding())
]
};
/*
+14 -11
View File
@@ -1,4 +1,4 @@
import { Component, inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, inject, input, numberAttribute, OnDestroy, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { Subject, Subscription} from 'rxjs';
@@ -23,9 +23,15 @@ export class StaffEditComponent implements OnInit, OnDestroy {
loginUser ='';
_error ='';
_id= -1;
Roles: Code[];
Roles: Code[] = [];
id = input(0, { transform: numberAttribute });
isNew = false;
validationPoints?: Code[];
private staffService = inject(StaffService);
private messageService = inject(MessageService);
private route = inject(ActivatedRoute);
private router = inject(Router);
private lookupService = inject(LookupService);
msg="[adminUser Component] ";
private formBuilder = inject(UntypedFormBuilder);
//for focus input
@@ -46,12 +52,11 @@ export class StaffEditComponent implements OnInit, OnDestroy {
});
constructor(
private staffService: StaffService,
private messageService: MessageService,
private lookupService: LookupService,
private router: Router, private route: ActivatedRoute,
) {
this.fillRole();
}
fillRole() :void
{
this.Roles = [];
let item:Code = {id:userRole.Admin, name: 'Admin', status:'Admin'};
this.Roles.push(item);
@@ -61,11 +66,9 @@ constructor(
this.Roles.push(item);
item = {id:userRole.ServiceManager, name: 'Service Manager', status:'ServiceManager'};
this.Roles.push(item);
//item = {id:userRole.Normal, name: 'Switch', status:'Switch'};
//this.Roles.push(item);
}
}
getClassForRequire(prev:string,name:string){
const notok = !this.adminuserForm.controls[name].valid &&
this.adminuserForm.controls[name].touched;
@@ -76,7 +79,7 @@ getClassForRequire(prev:string,name:string){
}
ngOnInit():void {
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
const id = Number(this.route.snapshot.paramMap.get('id'));
const id = this.id(); // Number(this.route.snapshot.paramMap.get('id'));
// now load thing up
const user = Utils.getCurrentUser();
// console.log(this.msg + "current login user ", user);
+30
View File
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
+63
View File
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
+363
View File
@@ -0,0 +1,363 @@
## 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
@@ -0,0 +1,31 @@
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
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.8",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
@@ -0,0 +1,7 @@
using System;
namespace FamilyTreeAPI.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }
@@ -0,0 +1,36 @@
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 };
//}
}
}
@@ -0,0 +1,38 @@
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);
}
}
@@ -0,0 +1,111 @@
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;
}
}
}
@@ -0,0 +1,82 @@
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);
}
}
@@ -0,0 +1,68 @@
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);
}
}
}
@@ -0,0 +1,110 @@
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);
}
}
}
@@ -0,0 +1,47 @@
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);
}
}
@@ -0,0 +1,71 @@
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);
}
}
@@ -0,0 +1,73 @@
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
@@ -0,0 +1,105 @@
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
@@ -0,0 +1,39 @@
# 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"]
@@ -0,0 +1,18 @@
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);
*/
@@ -0,0 +1,15 @@
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; }
}
@@ -0,0 +1,35 @@
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;
}
}
@@ -0,0 +1,28 @@
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; }
}
@@ -0,0 +1,35 @@
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; }
}
@@ -0,0 +1,53 @@
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; }
}
@@ -0,0 +1,22 @@
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; }
}
@@ -0,0 +1,26 @@
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; }
}
@@ -0,0 +1,37 @@
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!;
}
@@ -0,0 +1,17 @@
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; }
}
@@ -0,0 +1,30 @@
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; }
}
@@ -0,0 +1,38 @@
<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>
@@ -0,0 +1,19 @@
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;
}
}
@@ -0,0 +1,28 @@
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;
}
}
}
@@ -0,0 +1,35 @@
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
@@ -0,0 +1,107 @@
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,
lets 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.
Lets 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 lets 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
@@ -0,0 +1,13 @@
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);
}
@@ -0,0 +1,16 @@
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();
}
@@ -0,0 +1,18 @@
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);
}
@@ -0,0 +1,13 @@
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);
}
@@ -0,0 +1,8 @@
using FamilyTreeAPI.Entities;
namespace FamilyTreeAPI.Interface;
public interface IReport
{
// Task<ResultModel<FileContent>> GetMotorVehicleReportAsync(MotorVehicleCriteria criteria);
// Task<ResultModel<FileContent>> GetStaffWorkReportAsync(StaffWorkCriteria criteria);
}
@@ -0,0 +1,20 @@
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);
}
@@ -0,0 +1,13 @@
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);
}
@@ -0,0 +1,136 @@
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);
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace FamilyTreeAPI.Models
{
public partial class Lookup
{
public int Id { get; set; }
public string? Code { get; set; }
public string? Description { get; set; }
public string? Type { get; set; }
public bool? Lactive { get; set; }
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace FamilyTreeAPI.Models
{
public partial class Person
{
public Person()
{
// Servicetasks = new HashSet<Servicetask>();
}
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? Address { get; set; }
public string? Sex { get; set; }
public string? Image { get; set; }
public bool? Alive { get; set; }
public int? MotherId { get; set; }
public int? FatherId { get; set; }
public DateTime? dob { get; set; }
//public virtual ICollection<Staffwork> Staffworks { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace FamilyTreeAPI.Models;
public class RelationShip
{
public int Id { get; set; }
public int RelatePersonId { get; set; }
public int PersonId { get; set; }
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace FamilyTreeAPI.Models
{
public partial class staff
{
public staff()
{
// ServicetaskAssigntoNavigations = new HashSet<Servicetask>();
// ServicetaskStaffs = new HashSet<Servicetask>();
// Staffworks = new HashSet<Staffwork>();
}
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 int? Stype { get; set; }
public int? Srole { get; set; }
public string? Spassword { get; set; }
public bool? Sactive { get; set; }
// public virtual ICollection<Servicetask> ServicetaskAssigntoNavigations { get; set; }
// public virtual ICollection<Servicetask> ServicetaskStaffs { get; set; }
//public virtual ICollection<Staffwork> Staffworks { get; set; }
}
}
+184
View File
@@ -0,0 +1,184 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
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;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using FamilyTreeAPI.Repository;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
#region Services
var services = builder.Services;
services.AddCors();
services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
var appSettingsSection = builder.Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddHttpContextAccessor();
services.AddScoped<IJwtUtils, JwtUtils>();
services.AddScoped<ImportPersonRepository>();
services.AddDbContext<FamilyTreeDBContext>(options =>
{
// options.LogTo(s => Console.WriteLine(s));
//options.UseNpgsql(appSettings.SQLConnectionString);
string? conn = builder.Configuration.GetValue<string>("AppSettings:SQLConnectionString");
// options.LogTo(s => Console.WriteLine(s));
if (conn != null)
options.UseNpgsql(conn);
});
services.AddGraphQLServer()
.AddFiltering()
.AddSorting()
.AddProjections()
.RegisterDbContextFactory<FamilyTreeDBContext>()
.AddQueryType<QueryFamilyTree>();
services.AddScoped<ILookup, LookupRepository>();
services.AddScoped<IStaff, StaffRepository>();
services.AddScoped<IRelationShipd,RelationShipRepository>();
services.AddScoped<IPerson, PersonRepository>();
services.AddScoped<IUserService, UserServiceRepository>();
services.AddScoped<IReport, ReportRepository>();
services.AddScoped<Seed>();
/*
public FamilyTreeDBContext(DbContextOptions<FamilyTreeDBContext> options)
: base(options)
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}
services.AddScoped<IAdminUser, AdminUserRepository>();
services.AddScoped<IMotorVehicles, MotorVehiclesRepository>();
*/
#endregion
#region app
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
try
{
var context = scope.ServiceProvider.GetRequiredService<FamilyTreeDBContext>();
var db = context.Database;
if (db != null)
{
db.Migrate();
db.EnsureCreated();
var staff = context.GetService<Seed>();
int id = staff.InsertOneUser();
if (id < 0)
{
var databaseCreator = (context.GetService<IDatabaseCreator>() as RelationalDatabaseCreator);
if (databaseCreator != null)
{
//if (!databaseCreator.Exists())
databaseCreator.CreateTables();
}
id = staff.InsertOneUser();
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
app.UseMiddleware<JwtMiddleware>();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())//
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
// global cors policy
app.UseCors(x => x
.SetIsOriginAllowed(origin => true)
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Content-Disposition")
.AllowCredentials());
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapGraphQL();
app.MapControllers();
app.Run();
#endregion
/*
https://www.youtube.com/watch?v=WQFx2m5Ub9M
https://csharptotypescript.azurewebsites.net/
\ services.AddDbContext<BloggingContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("BloggingContext")));
dotnet ef dbcontext scaffold "host=postgresdb;port=5432;Database=FamilyTreeDB;Username=postgres;password=Positive~1" Npgsql.EntityFrameworkCore.PostgreSQL -Schemas schema1 --output-dir Models
PM> Scaffold-DbContext "Host=postgresdb;Port=5432;database=FamilyTreeDB;user id=postgres;Password=Positive~1" Npgsql.EntityFrameworkCore.PostgreSQL -OutputDir Models
*/
/*
graphql
https://www.youtube.com/watch?v=HnXA8RI7Tvc
query {
family {
nodes{
id
firstname
lastname
email
}
}
}
*/
@@ -0,0 +1,42 @@
{
"profiles": {
"FamilyTreeAPI": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5016",
"applicationUrl1": "http://192.168.8.188:5015"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"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
}
}
}
@@ -0,0 +1,54 @@
using System.Net.Mail;
namespace FamilyTreeAPI.Repository
{
public class Email
{
public static void SendMail(string smtpServer, string sender, string sendTo,
string header, string message, bool mailPriority = false)
{
if (string.IsNullOrEmpty(smtpServer) && string.IsNullOrEmpty(sender) && string.IsNullOrEmpty(sendTo))
return;
System.Net.Mail.SmtpClient client = new SmtpClient(smtpServer);
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(sender);
var splitEmails = sendTo.Split(';');
foreach (string item in splitEmails)
{
mailMessage.To.Add(item);
}
if (mailPriority)
mailMessage.Priority = MailPriority.High;
// mailMessage.To.Add(sendTo);
mailMessage.Subject = header;
string text = message;
string signature = "";
try
{
text += "<br/>" + signature;
mailMessage.Body = text;
mailMessage.IsBodyHtml = true;
}
catch //(Exception ex)
{
// ErrorLogger.logError(ex, "CPA");
throw;
}
try
{
string userState = "send to fleet manager";
client.SendAsync(mailMessage, userState);
}
catch //(Exception ex)
{
throw;
}
}
}
}
@@ -0,0 +1,199 @@
using DocumentFormat.OpenXml.Spreadsheet;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using SpreadsheetLight;
namespace FamilyTreeAPI.Repository;
public class ImportPersonRepository
{
enum enumIdx
{
Id = 1,
FirstName,
LastName,
Email,
Phone,
Image,
Sex,
Address,
Alive,
Dob,
FatherId,
MotherId,
}
private FamilyTreeDBContext _context;
private IConfiguration _config;
private IHttpContextAccessor _httpContext;
private IRelationShipd _relationship;
public ImportPersonRepository(IConfiguration config, FamilyTreeDBContext context,
IRelationShipd relationship,
IHttpContextAccessor httpContext)
{
_context = context;
_relationship = relationship;
_config = config;
_httpContext = httpContext;
}
public async Task<string> GetImageAsBase64String(string imagePath)
{
try
{
// Read the image file into a byte array
//byte[] imageBytes = File.ReadAllBytes(imagePath);
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
// Convert the byte array to a Base64 string
string base64String = Convert.ToBase64String(imageBytes);
return base64String;
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: The file at '{imagePath}' was not found.");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
return null;
}
}
public async Task<ResultModel<string>> DownloadFile(DownloadFileCriteria criteria)
{
int statusCode = -1;
string result = "";
string error = "";
try
{
string imagePath = _config.GetValue<string>("AppSettings:ImageFolder");
string imageUrl = System.IO.Path.Combine(imagePath, criteria.FileName);
if (System.IO.File.Exists(imageUrl))
{
result = await this.GetImageAsBase64String(imageUrl);
statusCode = 1;
}
else
{
error = "error filename " + criteria.FileName + " can not find in this folder:" + imagePath;
statusCode = -1;
}
}
catch (Exception ex)
{
statusCode = -1;
error = ex.Message;
}
return new ResultModel<string>
{
Data = result,
StatusCode = statusCode,
Message = error
};
}
public async Task<List<CodeDto<string>>> ImportPerson(MemoryStream fileStream, string sheetName)
{
Dictionary<string, ImportRelation> relationDic = new();
int id;
string keypair = ""; //father and mother key
List<CodeDto<string>> output = new();
Dictionary<int,MappingFatherMother> updateperson = new();
CodeDto<string> colums;
List<int> ids = new();
Person person;
Person? uperson;
ImportRelation iRelation;
MappingFatherMother mitem,faitem,moitem;
int fid, mid;
// MemoryStream msFirstPass = new MemoryStream();
SLDocument sl = new SLDocument(fileStream, sheetName);
// There is no way that I can see to get the Rows
SLWorksheetStatistics stats = sl.GetWorksheetStatistics();
for (int row = 2; row <= stats.NumberOfRows; row++)
{
person = new Person();
mitem = new();
id = sl.GetCellValueAsInt32(row, (int) enumIdx.Id);
mitem.IId = id;
person.FirstName = sl.GetCellValueAsString(row, (int) enumIdx.FirstName);
person.LastName = sl.GetCellValueAsString(row, (int) enumIdx.LastName);
person.Email = sl.GetCellValueAsString(row, (int) enumIdx.Email);
person.Phone = sl.GetCellValueAsString(row, (int) enumIdx.Phone);
person.Image = sl.GetCellValueAsString(row, (int) enumIdx.Image);
person.Alive = sl.GetCellValueAsBoolean(row, (int) enumIdx.Alive);
person.dob = sl.GetCellValueAsDateTime(row, (int) enumIdx.Dob);
person.Sex = sl.GetCellValueAsString(row, (int) enumIdx.Sex);
person.Address = sl.GetCellValueAsString(row, (int) enumIdx.Address);
mitem.IFatherId = sl.GetCellValueAsInt32(row, (int) enumIdx.FatherId);
mitem.IMotherId = sl.GetCellValueAsInt32(row, (int) enumIdx.MotherId);
_context.Persons.Add(person);
_context.SaveChanges();
mitem.TId = person.Id;
ids.Add(id);
updateperson.Add(mitem.IId, mitem);
colums = new();
colums.Code = $"{mitem.IId} {mitem.IFatherId} {mitem.IMotherId}";
colums.Description = $"{mitem.TId} {mitem.TFatherId} {mitem.TMotherId}";
output.Add(colums);
}
for (int i = 0; i < ids.Count; i++)
{
id = ids[i];
mitem = updateperson[id];
fid = mitem.IFatherId;
if (updateperson.ContainsKey(fid))
{
faitem = updateperson[fid];
mitem.TFatherId = faitem.TId;
}
mid = mitem.IMotherId;
if (updateperson.ContainsKey(mid))
{
moitem = updateperson[mid];
mitem.TMotherId = moitem.TId;
}
uperson = await _context.Persons.FindAsync(mitem.TId);
if (uperson == null) continue;
uperson.FatherId = mitem.TFatherId;
uperson.MotherId = mitem.TMotherId;
if (mitem.TFatherId > 0 && mitem.TMotherId > 0)
{
keypair = mitem.TFatherId + "," + mitem.TMotherId;
if (!relationDic.ContainsKey(keypair))
{
iRelation = new();
iRelation.FatherId = mitem.TFatherId;
iRelation.MotherId = mitem.TMotherId;
relationDic.Add(keypair, iRelation);
}
}
await _context.SaveChangesAsync();
}
//finally add relation ship table
RelationShip model;
foreach (KeyValuePair<string, ImportRelation> item in relationDic)
{
model = new RelationShip();
model.PersonId = item.Value.FatherId;
model.RelatePersonId = item.Value.MotherId;
_context.RelationShips.Add(model);
_context.SaveChanges();
}
return output;
}
}
@@ -0,0 +1,278 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace FamilyTreeAPI.Repository
{
public class LookupRepository: ILookup
{
private readonly FamilyTreeDBContext _context;
public LookupRepository(FamilyTreeDBContext context)
{
_context = context;
}
private bool checkDescription(string desc, string type, int id)
{
bool result = false;
if (!string.IsNullOrEmpty(desc))
{
string ldesc = desc.ToLower();
int count = _context.Lookups.Where(x => (x.Code ?? "").ToLower() == ldesc
&& id != x.Id
).ToList().Count();
result = count > 0;
}
return result;
}
public async Task<ResultModel<int>> SaveLookupAsync(LookupEditDto lookup)
{
int result = -1;
int statusCode = 0;
string desc = lookup.Description.Trim();
string error = "";
try
{
Lookup model = null!;
bool ok = !checkDescription(desc, lookup.Type, lookup.Id);
if (ok)
{
if (lookup.Id < 1)
{
model = new();
model.Code = lookup.CodeId;
model.Description = desc;
model.Type = lookup.Type;
model.Lactive = lookup.Active;
_context.Lookups.Add(model);
}
else
{
Lookup? model1 = await _context.Lookups.FindAsync(lookup.Id);
if (model1 != null)
{
model1.Description = desc;
model1.Code = lookup.CodeId;
model1.Lactive = lookup.Active;
}
}
await _context.SaveChangesAsync();
if (model != null)
result = model.Id;
statusCode = 1;
}
else
{
statusCode = 0;
error = "description is already in database";
}
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<int>()
{
Data = result,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<LookupEditDto>> GetLookupEditByIdAsync(int id, string type)
{
List<LookupEditDto> resultList;
LookupEditDto item = null!;
int statusCode = 0;
string error = "";
try
{
resultList = await _context.Lookups.Where(x => x.Type == type
&& (x.Id == id))
.Select(item => new LookupEditDto
{
CodeId = string.IsNullOrEmpty(item.Code) ? "0": item.Code,
Active = item.Lactive ?? false,
Id = item.Id,
Description = item.Description ?? ""
})
.ToListAsync();
if (resultList.Count > 0)
item = resultList[0];
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<LookupEditDto>()
{
Data = item,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<List<LookupDto>>> GetLookupAsync(string type)
{
List<LookupDto> resultList = new();
int statusCode = 0;
string error = "";
try
{
resultList = await _context.Lookups.Where(x => x.Type == type && ((x.Lactive ?? false) == true))
.Select(item => new LookupDto { Id = item.Id, CodeId = item.Code ?? "", Description = item.Description ?? "" })
.ToListAsync();
resultList.Sort((x, y) => x.Description.CompareTo(y.Description));
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<LookupDto>>()
{
Data = resultList,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<Dictionary<string, LookupDto>>> GetLookupDicAsync(string type)
{
Dictionary<string, LookupDto> resultList = new();
int statusCode = 0;
string error = "";
try
{
resultList = await _context.Lookups.Where(x => x.Type == type && ((x.Lactive ?? false) == true))
.Select(item => new LookupDto { Id = item.Id, CodeId = item.Code ?? "", Description = item.Description ?? "" })
.ToDictionaryAsync(x => x.CodeId);
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<Dictionary<string, LookupDto>>()
{
Data = resultList,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<List<LookupEditDto>>> GetLookupEditAsync(string type)
{
List<LookupEditDto> resultList = new();
int statusCode = 0;
string error = "";
try
{
resultList = await _context.Lookups.Where(x => x.Type == type)
.Select(item => new LookupEditDto
{
Id = item.Id,
Active = item.Lactive ?? false,
CodeId = item.Code ?? "",
Type = item.Type ?? "",
Description = item.Description ?? ""
})
.ToListAsync();
resultList.Sort((x, y) => x.Description.CompareTo(y.Description));
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<LookupEditDto>>()
{
Data = resultList,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<List<LookupDto>>> GetPersonsAsync()
{
List<LookupDto> resultList = new();
int statusCode = 0;
string error = "";
try
{
resultList = await _context.Persons.Where(x => x.Alive == true)
.Select(item => new LookupDto { Id = item.Id, CodeId = item.Id.ToString(), Description = item.FirstName ?? "" })
.ToListAsync();
resultList.Sort((x, y) => x.Description.CompareTo(y.Description));
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<LookupDto>>()
{
Data = resultList,
StatusCode = statusCode,
Message = error
};
}
public async Task<ResultModel<List<LookupDto>>> GetStaffAsync()
{
List<LookupDto> resultList = new();
int statusCode = 0;
string error = "";
try
{
resultList = await _context.staff.Where(x => x.Sactive == true)
.Select(item => new LookupDto { Id = item.Id, CodeId = item.Firstname ?? "", Description = item.Lastname ?? "" })
.ToListAsync();
resultList.Sort((x, y) => x.Description.CompareTo(y.Description));
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<LookupDto>>()
{
Data = resultList,
StatusCode = statusCode,
Message = error
};
}
}
}
@@ -0,0 +1,693 @@
using DocumentFormat.OpenXml.EMMA;
using DocumentFormat.OpenXml.Office.CustomUI;
using DocumentFormat.OpenXml.Office2010.Excel;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Helper;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections;
using System.Collections.Generic;
//using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
namespace FamilyTreeAPI.Repository;
public partial class PersonRepository : IPerson
{
private readonly FamilyTreeDBContext _context;
private readonly IRelationShipd _relationship;
private readonly IHttpContextAccessor _httpContext;
private readonly IConfiguration _config;
const string dateFormat = "yyyy-MM-dd";
public PersonRepository(IConfiguration config, FamilyTreeDBContext context,
IRelationShipd relationship,
IHttpContextAccessor httpContext)
{
_context = context;
_relationship = relationship;
_config = config;
_httpContext = httpContext;
}
private PersonDto FillDto(Person model)
{
PersonDto dto = new();
dto.Id = model.Id;
dto.FirstName = model.FirstName;
dto.LastName = model.LastName;
dto.Email = model.Email;
dto.Address = model.Address;
dto.Phone = model.Phone;
dto.Alive = model.Alive;
dto.Image = model.Image;
dto.FatherId = model.FatherId;
dto.MotherId = model.MotherId;
dto.Sex = model.Sex;
dto.dob = Helpers.DateToStr(model.dob);
/*
dto.AddedOn = model.AddedOn;
dto.RoleType = model.RoleType;
*/
return dto;
}
private Person FillModel(Person model, PersonDto dto)
{
model.FirstName = dto.FirstName;
model.LastName = dto.LastName;
model.Email = dto.Email;
model.Address = dto.Address;
model.Phone = dto.Phone;
model.Alive = dto.Alive;
model.Image = dto.Image;
model.FatherId = dto.FatherId;
model.MotherId = dto.MotherId;
model.Sex = dto.Sex;
model.dob = Helpers.DateToDateTime(dto.dob);
return model;
}
public async Task<ResultModel<Dictionary<int, PersonDto>>> GetDicFamily()
{
Dictionary<int, PersonDto> list = new();
int statuscode = 0;
string error = "";
PersonDto item;
Person jitem;
try
{
//list = await _context.LoadStaffAsync(criteria);
var jlist = await _context.Persons.ToListAsync();
for (int i = 0; i < jlist.Count; i++)
{
jitem = jlist[i];
item = FillDto(jitem);
list.Add(item.Id,item);
}
statuscode = 1;
//list.Sort((x, y) => x.Code.CompareTo(y.Code));
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel< Dictionary<int, PersonDto> > ()
{
Data = list,
StatusCode = statuscode,
Message = error
};
}
private async Task<List<PersonDto>> GetChildrens(int FatherId, int MotherId)
{
List<PersonDto> list = new();
PersonDto item;
Person jitem;
var jlist = await _context.Persons.Where(x =>
(x.MotherId == FatherId && x.FatherId == MotherId) ||
(x.FatherId == FatherId && x.MotherId == MotherId)).ToListAsync();
for (int i = 0; i < jlist.Count; i++)
{
jitem = jlist[i];
item = FillDto(jitem);
list.Add(item);
}
return list;
}
public async Task<ResultModel<List<PersonDto>>> GetChildren(ChildCriteria criteria)
{
List<PersonDto> list = new();
int statuscode = 0;
string error = "";
PersonDto item;
Person jitem;
try
{
//list = await _context.LoadStaffAsync(criteria);
list = await GetChildrens(criteria.FatherId, criteria.MotherId);
statuscode = 1;
//list.Sort((x, y) => x.Code.CompareTo(y.Code));
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<List<PersonDto>>()
{
Data = list,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<List<PersonDto>>> GetPerson(PersonCriteria criteria)
{
List<PersonDto> list = new();
List<Person>? jlist;
int statuscode = 0;
string error = "";
PersonDto item;
Person jitem;
string firstName = criteria.FirstName;
string lastName = criteria.LastName;
int Id = criteria.Id;
try
{
//list = await _context.LoadStaffAsync(criteria);
if (!string.IsNullOrEmpty(firstName))
{
firstName = firstName.ToLower();
jlist = await _context.Persons.Where(x =>
EF.Functions.Like(x.FirstName.ToLower(),firstName + "%")).ToListAsync();
}
else if (!string.IsNullOrEmpty(firstName) && !string.IsNullOrEmpty(lastName))
{
firstName = firstName.ToLower();
lastName = lastName.ToLower();
jlist = await _context.Persons.Where(x => EF.Functions.Like(x.FirstName.ToLower(),firstName + "%")
&& EF.Functions.Like(x.LastName.ToLower(),lastName + "%")).ToListAsync();
}
else
jlist = await _context.Persons.ToListAsync();
for (int i = 0; i < jlist.Count; i++)
{
jitem = jlist[i];
item = FillDto(jitem);
list.Add(item);
}
statuscode = 1;
//list.Sort((x, y) => x.Code.CompareTo(y.Code));
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<List<PersonDto>>()
{
Data = list,
StatusCode = statuscode,
Message = error
};
}
private async Task<Person> GetPerson(int id)
{
Person rval = await _context.Persons.FindAsync(id);
return rval;
}
private string getStyleClass(string va)
{
string result = "";
//result = "bg-indigo-500 text-black";
if (va == "T")
{
result = "bg-indigo-500 text-white";
}
else if (va == "P")
{
//result = "bg-purple-500 text-black";
result = "bg-purple-500 text-black";
}
else if (va == "C")
{
result = "bg-teal-500 text-white";
}
return result;
}
public async Task<ResultModel<TreeNode<string>>> GetByFamilyAsync(int id)
{
int statuscode = 0;
string error = "";
string key = "";
string data = "";
string type = "default";
string pName = "";
Person person;
TreeNode<string> citem;
TreeNode<string> child;
TreeNode<string> node = new();
node.Children = new();
PersonDto dto = new();
try
{
var item = await _context.Persons.FindAsync(id);
if (item == null)
{
statuscode = -1;
}
else
{
node.Label = item.FirstName;
node.Key = item.Id.ToString();
node.Type = type;
node.Icon = "T";
node.StyleClass = getStyleClass(node.Icon);
node.Expanded = true;
node.Data = item.Sex;
statuscode = 1;
dto = FillDto(item);
await GetPartnerChildrens(dto, node);
}
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<TreeNode<string>> ()
{
Data = node,
StatusCode = statuscode,
Message = error
};
}
private async Task GetPartnerChildrens(PersonDto person, TreeNode<string> node)
{
/*****************************/
ResultModel<List<RelationShipDto>> rlist = await _relationship.GetByPersonIdAsync(person.Id);
List<RelationShipDto> relationShips = rlist.Data;
RelationShipDto relate;
string type = node.Type;
TreeNode<string> child;
PersonDto dto;
Person pe;
int fatherId, motherId;
fatherId = motherId = 0;
string data = person.Sex;
TreeNode<string> citem;
string pName = "";
string key = "";
for (int i = 0; i < relationShips.Count; i++)
{
relate = relationShips[i];
if (relate.PersonId == person.Id)
{
if (data == "M")
{
fatherId = relate.PersonId;
motherId = relate.RelatePersonId;
}
else
{
fatherId = relate.RelatePersonId;
motherId = relate.PersonId;
}
}
else
{
if (data == "M")
{
fatherId = relate.RelatePersonId;
motherId = relate.PersonId;
}
else
{
fatherId = relate.PersonId;
motherId = relate.RelatePersonId;
}
}
//get children
List<PersonDto> children = await GetChildrens(fatherId, motherId);
if (children.Count > 0)
{
if (person.Id != motherId)
{
//kham
if (motherId > 0)
{
pe = await GetPerson(motherId);
pName = pe.FirstName;
key = motherId.ToString();
data = pe.Sex;
}
}
else
{
if (fatherId > 0)
{
pe = await GetPerson(fatherId);
pName = pe.FirstName;
key = fatherId.ToString();
data = pe.Sex;
}
}
citem = new TreeNode<string>();
citem.Label = pName;
citem.Icon = "P";
citem.StyleClass = getStyleClass(citem.Icon);
citem.Expanded = true;
citem.Data = data;
citem.Type = type;
citem.Key = key;
citem.Children = new List<TreeNode<string>>();
node.Children.Add(citem);
for (int j = 0; j < children.Count; j++)
{
dto = children[j];
child = new TreeNode<string>();
child.Expanded = true;
child.Type = type;
child.Icon = "C";
child.StyleClass = getStyleClass(child.Icon);
child.Label = dto.FirstName;
child.Key = dto.Id.ToString();
child.Data = dto.Sex;
child.Children = new();
citem.Children.Add(child);
//get child partner and repeat this.
await GetPartnerChildrens(dto, child);
}
}
}
/*****************************************/
}
public async Task<ResultModel<PersonDto>> GetByIdAsync(int id)
{
int statuscode = 0;
string error = "";
PersonDto dto = new();
try
{
var item = await _context.Persons.FindAsync(id);
if (item == null)
{
statuscode = -1;
}
else
{
dto = FillDto(item);
statuscode = 1;
ResultModel<List<RelationShipDto>> rlist = await _relationship.GetByPersonIdAsync(dto.Id);
dto.RelationShips = rlist.Data;
statuscode = rlist.StatusCode;
error = rlist.Message;
}
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<PersonDto>()
{
Data = dto,
StatusCode = statuscode,
Message = error
};
}
private string GetDateTimeNow()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
/// <summary>
/// need filename to get extension file and family name for id combine to
/// name to table.
/// </summary>
/// <param name="fileName"></param>
/// <param name="familyId"></param>
/// <param name="base64"></param>
private string SaveFile(string fileName, int familyId, string base64)
{
string path = "";
int statusCode = 0;
string error = "";
string sdate = GetCurrentDateTime();
string filename, extention;
filename = extention = "";
int first = base64.IndexOf("base64,") +"base64,".Length;
int last = base64.Length;
string rbase = base64.Substring(first, last - first);
byte[] newBytes = Convert.FromBase64String(rbase);
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))
{
StreamWriter writer = new StreamWriter(fileStream);
writer.Write(newBytes);
//writer.BaseStream.Write(bytes, 0, bytes.Length);
}
*/
/*
using (MemoryStream ms = new MemoryStream(newBytes))
{
Image image = Image.FromStream(ms);
image.Save(fullpath);
}
*/
using (FileStream fileStream = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.None))
{
fileStream.Write(newBytes, 0, newBytes.Length);
}
return filename;
}
public async Task<ResultModel<int>> SaveAsync(PersonForSave container)
{
int result = default(int);
int statuscode = 0;
string error = "";
HttpContext? httpContext = _httpContext.HttpContext;
string loginName = "";
if (httpContext != null)
{
UserDto? user = (UserDto?)httpContext.Items["User"];
if (user != null)
loginName = user.FirstName + " " + user.LastName;
}
PersonDto item = container.Person;
try
{
Person model;
if (item.Id < 1)
{
model = new();
model = FillModel(model, item);
// model.Active = true;
// model.AddedBy = loginName;
// model.AddedOn = DateTime.Now;
_context.Persons.Add(model);
var successid = await _context.SaveChangesAsync();
result = model.Id;
if (!string.IsNullOrEmpty(container.FileName))
{
string image = SaveFile(container.FileName, result, container.FormData);
// update the image in table to new
model.Image = image;
await _context.SaveChangesAsync();
}
}
else
{
var model1 = await _context.Persons.FindAsync(item.Id);
if (model1 != null)
{
model1 = FillModel(model1, item);
result = item.Id;
if (!string.IsNullOrEmpty(container.FileName))
{
string image = SaveFile(container.FileName, result, container.FormData);
// update the image in table to new
model1.Image = image;
}
var successid = await _context.SaveChangesAsync();
}
// model.LastModified = DateTime.Now;
// model.LastModifiedId = loginName;
}
statuscode = 1;
if (item.RelationShips != null)
{
ResultModel<int> rresult = await this._relationship.SaveAsync(item.RelationShips);
statuscode = rresult.StatusCode;
error += rresult.Message;
}
}
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>> DeleteAsync(int id)
{
int result = 1;
int statuscode = 0;
string error = "";
try
{
var model = await _context.Persons.FindAsync(id);
if (model != null)
_context.Persons.Remove(model);
var successCount = await _context.SaveChangesAsync();
statuscode = 1;
result = 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 Task<ResultModel<string>> UploadImage(UploadCriteria criteria) {
return UploadImagep(criteria);
}
private string GetCurrentDateTime()
{
DateTime now = DateTime.Now;
return now.ToString("yyyyMMdd");
}
private async Task<ResultModel<string>> UploadImagep(UploadCriteria criteria)
{
string path = "";
int statusCode = 0;
string error = "";
string sdate = GetCurrentDateTime();
string filename, extention;
filename = extention = "";
try
{
if (criteria.File.Length > 0)
{
path = _config["AppSettings:ImageFolder"];
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
extention = System.IO.Path.GetExtension(criteria.FileName);
filename = criteria.FamilyId + "_" + sdate + extention;
using (var fileStream = new FileStream(System.IO.Path.Combine(path, filename), FileMode.Create))
{
await criteria.File.CopyToAsync(fileStream);
}
statusCode = 1;
}
else
{
statusCode = -1;
error = "file contain is empty";
}
}
catch (Exception ex)
{
statusCode = -1;
error = ex.Message;
}
return new ResultModel<string>()
{
Data = filename,
StatusCode = statusCode,
Message = error
};
}
public ResultModel<int> DeleteUploadFile(DeleteFileCriteria criteria)
{
int result = -1;
int statusCode = 0;
string error = "";
var filePath = _config["AppSettings:ImageFolder"];
var myfile = filePath + "\\" + criteria.Filename;
try
{
File.Delete(myfile);
result = 1;
statusCode = 1;
Person? model = _context.Persons.Find(criteria.FamilyId);
if (model != null)
{
model.Image = null;
_context.SaveChanges();
}
}
catch (Exception e)
{
result = -1;
statusCode = -1;
error += e.Message;
}
return new ResultModel<int>()
{
Data = result,
StatusCode = statusCode,
Message = error
};
}
}
@@ -0,0 +1,140 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
using System.Drawing.Text;
namespace FamilyTreeAPI.Repository;
public partial class PersonRepository
{
private TreeNode<string> PopulateItem(Person model)
{
TreeNode<string> treeNode = new();
treeNode.Label = model.FirstName;
treeNode.Data = model.Id.ToString();
treeNode.Key = model.Id.ToString();
treeNode.Expanded = false;
treeNode.Children = new();
return treeNode;
}
private bool UseFamilyCriteria(FamilyCriteria criteria, Person person)
{
bool result = false;
if (criteria.UseFather && criteria.UseMother)
{
result = person.FatherId.GetValueOrDefault(0) == 0
&& person.MotherId.GetValueOrDefault(0) == 0;
}
else if (criteria.UseFather)
{
result = person.FatherId.GetValueOrDefault(0) == 0;
}
else if (criteria.UseMother)
{
result = person.MotherId.GetValueOrDefault(0) == 0;
}
return result;
}
private bool UseChildFamilyCriteria(FamilyCriteria criteria, int id, Person person)
{
bool result = false;
int parentId;
if (criteria.UseFather)
{
parentId = person.FatherId.GetValueOrDefault(0);
result = parentId == id;
}
else if (criteria.UseMother)
{
parentId = person.MotherId.GetValueOrDefault(0);
result = parentId == id;
}
return result;
}
private List<Person> SplitListOfTopLevel(List<Person> list, FamilyCriteria criteria)
{
List<Person> result = new();
Person item;
for (int i = list.Count - 1; i > -1; i--)
{
item = list[i];
if (UseFamilyCriteria(criteria,item))
{
result.Add(item);
list.RemoveAt(i);
}
}
return result;
}
private List<Person> GetParentId(List<Person> list,int id, FamilyCriteria criteria, Func<FamilyCriteria, int, Person, bool> conditionFn)
{
List<Person> result = new();
Person item;
for (int i = list.Count - 1; i > -1; i--)
{
item = list[i];
if (conditionFn(criteria,id, item))
{
result.Add(item);
list.RemoveAt(i);
}
}
return result;
}
private List<TreeNode<string>> PopulateChild(FamilyCriteria criteria,int id, List<Person> childList, Func<FamilyCriteria, int ,Person,bool> conditionFn)
{
Person person;
TreeNode<string> treeNode;
List<TreeNode<string>> list = new();
List<Person> children = GetParentId(childList,id, criteria, conditionFn);
for (int i = 0; i < children.Count; i++)
{
person = children[i];
treeNode = PopulateItem(person);
list.Add(treeNode);
// get children of this one too
treeNode.Children = PopulateChild(criteria, person.Id, childList, UseChildFamilyCriteria);
}
return list;
}
public async Task<ResultModel<List<TreeNode<string>>> > GetFamilyTreeBy(FamilyCriteria criteria)
{
int statusCode = -1;
string error = "";
Person person;
TreeNode<string> treeNode;
List<TreeNode<string>> data = new();
try
{
var personList = await _context.Persons.ToListAsync();
var topList = SplitListOfTopLevel(personList, criteria);
for (int i = 0; i < topList.Count; i++)
{
person = topList[i];
treeNode = PopulateItem(person);
data.Add(treeNode);
treeNode.Children = PopulateChild(criteria, person.Id, personList, UseChildFamilyCriteria);
}
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<TreeNode<string>>>
{
Data = data,
StatusCode = statusCode,
Message = error
};
}
}
@@ -0,0 +1,184 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace FamilyTreeAPI.Repository;
public class RelationShipRepository: IRelationShipd
{
private readonly FamilyTreeDBContext _context;
private readonly IHttpContextAccessor _httpContext;
private readonly IConfiguration _config;
public RelationShipRepository(IConfiguration config, FamilyTreeDBContext context, IHttpContextAccessor httpContext)
{
_context = context;
_config = config;
_httpContext = httpContext;
}
private RelationShip FillModel(RelationShipDto dto, RelationShip model)
{
model.RelatePersonId = dto.RelatePersonId;
model.PersonId = dto.PersonId;
return model;
}
private RelationShipDto FillDto(RelationShip model)
{
RelationShipDto dto = new();
dto.Id = model.Id;
dto.PersonId = model.PersonId;
dto.RelatePersonId = model.RelatePersonId;
return dto;
}
public async Task<ResultModel<int>> SaveAsync(List<RelationShipDto> list)
{
int statusCode = 0;
string error = "";
RelationShipDto item;
RelationShip model;
try
{
for (int i = 0; i < list.Count; i++)
{
item = list[i];
if (item.Id < 1)
{
model = new();
model = FillModel(item, model);
_context.RelationShips.Add(model);
}
else if (item.State == enumState.Modify)
{
RelationShip? RelationShip = await _context.RelationShips.FindAsync(item.Id);
if (RelationShip != null)
{
RelationShip = FillModel(item, RelationShip);
}
}
else if (item.State == enumState.Delete)
{
RelationShip? RelationShip = await _context.RelationShips.FindAsync(item.Id);
if (RelationShip != null)
{
_context.RelationShips.Remove(RelationShip);
}
}
}
statusCode = 1;
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<int>
{
StatusCode = statusCode,
Data = statusCode,
Message = error
};
}
public async Task<ResultModel<List<RelationShipDto>>> GetByPersonIdAsync(int personId)
{
string error = "";
int statusCode = -1;
List<RelationShipDto> list = new();
RelationShipDto dto;
RelationShip model;
try
{
var mlist = await _context.RelationShips.Where(x => x.PersonId == personId).ToListAsync();
for (int i = 0; i < mlist.Count; i++)
{
model = mlist[i];
dto = FillDto(model);
list.Add(dto);
}
var rlist = await _context.RelationShips.Where(x => x.RelatePersonId == personId).ToListAsync();
for (int i = 0; i < rlist.Count; i++)
{
model = rlist[i];
dto = FillDto(model);
list.Add(dto);
}
statusCode = 1;
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<List<RelationShipDto>>
{
StatusCode = statusCode,
Data = list,
Message = error
};
}
public async Task<ResultModel<RelationShipDto>> GetByIdAsync(int Id)
{
string error = "";
int statusCode = -1;
RelationShipDto dto =new();
RelationShip? model;
try
{
model = await _context.RelationShips.FindAsync(Id);
if (model != null)
dto = FillDto(model);
}
catch (Exception ex)
{
error = ex.ToString();
statusCode = -1;
}
return new ResultModel<RelationShipDto>
{
StatusCode = statusCode,
Data = dto,
Message = error
};
}
public async Task<ResultModel<int>> DeleteAsync(int Id)
{
int result = -1;
string error = "";
int statusCode = -1;
try
{
RelationShip? RelationShip = await _context.RelationShips.FindAsync(Id);
if (RelationShip != null)
{
_context.RelationShips.Remove(RelationShip);
}
}
catch (Exception e)
{
error = e.ToString();
statusCode = -1;
}
return new ResultModel<int>
{
StatusCode = statusCode,
Data = result,
Message = error
};
}
}
@@ -0,0 +1,272 @@
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using SpreadsheetLight;
using System.Linq;
using DocumentFormat.OpenXml.Spreadsheet;
namespace FamilyTreeAPI.Repository;
public class ReportRepository : IReport
{
private enum EnumStaffWork
{
Id = 0,
FirstName,
LastName,
StartDate,
startTime,
stopTime,
Task,
Job,
Hour
};
private readonly FamilyTreeDBContext _context;
//private readonly ILookup _lookup;
const string DateTimeFormat = "dd/MM/yyyy HH:mm";
const string TimeFormat = "HH:mm";
const string DateFormat = "dd/MM/yyyy";
private readonly IStaff _staff;
private Dictionary<int, StaffDto> _dstaff;
private readonly ILookup _lookup;
private string DisplayTime(DateTime date)
{
string result = "";
if (date != DateTime.MinValue)
result = date.ToString(TimeFormat);
return result;
}
private string DisplayDateTime(DateTime? date)
{
string result = "";
if (date != DateTime.MinValue)
result = date!.Value.ToString(DateFormat);
return result;
}
private string FormatTime(int hour)
{
string result;
if (hour < 10)
result = "0" + hour.ToString() + ":00";
else
result = hour.ToString() + ":00";
return result;
}
public ReportRepository(FamilyTreeDBContext context, ILookup lookup,IStaff staff)
{
_context = context;
_lookup = lookup;
_staff = staff;
}
// public async Task<ResultModel<FileContent>> GetStaffWorkReportAsync(StaffWorkCriteria criteria)
// {
// //var data = await _lookup.GetLookupDicAsync("TypeOfUse");
// //if (data != null)
// //{
// // this._ServiceTypeDic = data.Data;
// //}
// var rstaff = await _staff.GetDicStaffs();
// if (rstaff.StatusCode == 1)
// _dstaff = rstaff.Data;
// //data = await _lookup.GetLookupDicAsync("Status");
// //if (data != null)
// //{
// // this._StatusDic = data.Data;
// //}
// int statusCode = 1;
// int col = 1;
// int row = 1;
// SLDocument sl = new SLDocument();
// SLStyle styleRed = sl.CreateStyle();
// styleRed.SetFontColor(System.Drawing.Color.Red);
// //first sheetName
// sl.RenameWorksheet(SLDocument.DefaultFirstSheetName, "Staff Work Report");
// SLStyle style = sl.CreateStyle();
// style.SetFontUnderline(DocumentFormat.OpenXml.Spreadsheet.UnderlineValues.Single);
// style.SetFontBold(true);
// style.SetFont("Calibri", 14);
// sl.SetCellStyle(1, 3, style);
// sl.SetRowHeight(1, 1, 25);
// sl.SetCellValue(row++, 3, "Staff Work");
// // sl.SetCellValue(row, 3, "From Date: " + fromDate.ToString(DateFormat) + " - " + toDate.ToString(DateFormat));
// sl.SetCellValue(1, 6, " " + DateTime.Now.ToString("ddd dd/MM/yyyy HH:mm"));
// // sl.SetCellValue(row, 6, "Hospital: " + facility);
// int startTitleRow = 3;
// row = startTitleRow;
// //make the title bold
// style = sl.CreateStyle();
// style.Font.Bold = true;
// sl.SetRowStyle(startTitleRow, style);
// //set it height
// sl.SetRowHeight(startTitleRow, startTitleRow, 25);
// // add the title first
// sl.SetCellValue(row, (int)EnumStaffWork.Id, "Id");
// sl.SetCellValue(row, (int) EnumStaffWork.FirstName, "FirstName");
// sl.SetCellValue(row, (int) EnumStaffWork.LastName, "SurName");
// sl.SetCellValue(row, (int) EnumStaffWork.StartDate, "StartDate");
// sl.SetCellValue(row, (int)EnumStaffWork.startTime, "Start Time");
// sl.SetCellValue(row, (int)EnumStaffWork.stopTime, "Stop Time");
// sl.SetCellValue(row, (int)EnumStaffWork.Hour, "Hour");
// /*
// style = sl.CreateStyle();
// style.SetFontColor(System.Drawing.Color.Blue);
// sl.SetRowStyle(row, style);
// */
// sl.FreezePanes(row, 0); //frozen the row.
// //wrap text on is concession Expiry
// style = sl.CreateStyle();
// style.SetWrapText(true);
// sl.SetColumnStyle(4, style);
// //format date
// // style = sl.CreateStyle();
// //style.FormatCode = DateTimeFormat;
// // sl.SetColumnStyle(1, style);
// //make column width
// sl.SetColumnWidth((int)EnumStaffWork.Id, 10);
// sl.SetColumnWidth((int)EnumStaffWork.FirstName, 30);
// sl.SetColumnWidth((int)EnumStaffWork.LastName, 30);
// sl.SetColumnWidth((int)EnumStaffWork.StartDate, 25);
// sl.SetColumnWidth((int)EnumStaffWork.startTime, 10);
// sl.SetColumnWidth((int)EnumStaffWork.stopTime, 10);
// sl.SetColumnWidth((int)EnumStaffWork.Hour, 20);
// style = sl.CreateStyle();
// style.FormatCode = DateFormat;
// sl.SetColumnStyle(5, style);
// //style = sl.CreateStyle();
// //style.FormatCode = DateTimeFormat;
// //sl.SetColumnStyle(1, style);
// sl.SetColumnWidth(10, 20);
// int startRowFrom = startTitleRow + 1;
//// DateTime ttdate = toDate.ToLocalTime(); //add 1 days so if to inclusive todate 02/09
// var container = await _context.LoadStaffWorkAsync(criteria); //fromDate.ToLocalTime(), ttdate.AddDays(1), typeOfCall);
// var list = container;
// list.Sort((x, y) => x.StartDate.Value.CompareTo(y.StartDate.Value));
// StaffWorkViewDto item;
// DateTime startTime, stopTime;
// double hour =0;
// string fname, lname;
// StaffWorkDetailDto detail;
// for (int i = 0; i < list.Count; i++)
// {
// item = list[i];
// row = i + startRowFrom;
// (startTime, stopTime ) = GetSSTime(item);
// if (item.Details != null)
// for (int j = 0; j < item.Details.Count; j++)
// {
// detail = item.Details[j];
// hour += detail.TotalMinuts();
// }
// (fname, lname) = GetStaffName(item.StaffId.Value);
// sl.SetCellValue(row, (int)EnumStaffWork.StartDate, item.StartDate.Value);
// sl.SetCellValue(row, (int)EnumStaffWork.FirstName, fname);
// sl.SetCellValue(row, (int)EnumStaffWork.LastName, lname);
// sl.SetCellValue(row, (int)EnumStaffWork.startTime, startTime);
// sl.SetCellValue(row, (int)EnumStaffWork.stopTime, stopTime);
// sl.SetCellValue(row, (int)EnumStaffWork.Hour, hour/60);
// }
// byte[] array;
// // sl.SaveAs("C:\\Temp\\Report\\ConcessionValidationDate" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xlsx");
// using (MemoryStream ws = new MemoryStream())
// {
// sl.SaveAs(ws);
// array = ws.ToArray();
// /*
// using (FileStream fs = new FileStream("c:\\temp\\Report\\conReport.xlsx",FileMode.Create,FileAccess.Write))
// {
// ws.WriteTo(fs);
// fs.Close();
// }
// */
// ws.Close();
// }
// var result = new FileContent
// {
// Content = array,
// FileName = "StaffWork_Report" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm") + ".xlsx",
// };
// return new ResultModel<FileContent>
// {
// Data = result,
// StatusCode = statusCode
// };
// }
private (string, string) GetStaffName(int staffId)
{
StaffDto staff;
if (_dstaff.ContainsKey(staffId))
{
staff = _dstaff[staffId];
return ( staff.Firstname ?? "" , staff.Lastname ?? "");
}
return ("","");
}
//private (DateTime firstTime, DateTime lastTime) GetSSTime(StaffWorkViewDto item)
//{
// DateTime sTime, lTime;
// DateTime? value;
// int count = 0;
// sTime = lTime = DateTime.Now;
// List<StaffWorkDetailDto> detail = item.Details ?? new();
// count = detail.Count;
// if (count > 0)
// {
// value = detail[0].StartTime;
// if (value.HasValue)
// {
// sTime = value.Value;
// }
// value = detail[count - 1].StopTime;
// if (value.HasValue)
// {
// lTime = value.Value;
// }
// }
// return (sTime, lTime);
//}
private string GetValue(string item, string text)
{
string result = item;
if (!string.IsNullOrEmpty(text))
result = text;
return result;
}
//private string GetPools(List<string> poolIds)
//{
// string result = "";
// string id;
// for (int i = 0; i < poolIds.Count; i++)
// {
// id = poolIds[i];
// if (this._PoolDic.ContainsKey(id))
// result += _PoolDic[id].Description + ",";
// }
// result = result.Remove(result.Length-1, 1);
// return result;
//}
}
@@ -0,0 +1,107 @@
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace FamilyTreeAPI.Repository;
public class Seed
{
private readonly FamilyTreeDBContext _context;
public Seed(FamilyTreeDBContext context)
{
_context = context;
}
public int InsertOneUser()
{
string sptext = "CREATE OR REPLACE FUNCTION public.usp_search_user( " +
" iemail character varying,ifirstname character varying,ilastname character varying) " +
" RETURNS TABLE(id integer, fistname character varying, lastname character varying, email character varying," +
" stype integer, sactive boolean, srole integer, spassword character varying) " +
" AS $$ BEGIN " +
" return query SELECT e.id, e.firstname, e.lastname, e.email, e.stype," +
" e.sactive, e.srole, e.spassword " +
" FROM public.staff e " +
" WHERE (iemail = '' or e.email ilike iemail || '%') " +
" and (ilastname = '' or e.lastname ilike ilastname || '%') " +
" and (ifirstname = '' or e.firstname ilike ifirstname || '%'); "+
"END; " +
" $$ " +
" LANGUAGE 'plpgsql'; ";
int id = -1;
//password = password
string txt = " INSERT INTO staff ( " +
"firstname, lastname, email, phone, stype, srole, spassword, sactive) " +
"VALUES" +
" ( 'kham', 'vilaythong', 'kham.vilaythong@gmail.com', '009', 1, 2, 'cGFzc3dvcmQ=', true), " +
" ( 'sy', 'vilaythong', 'sy.vilaythong@gmail.com', '007', 1, 2, 'cGFzc3dvcmQ=', true), " +
" ( 'Hung', 'Nguyen', 'hung.gnuyen@gmail.com', '008', 1, 2, 'cGFzc3dvcmQ=', true); ";
string workertxt = "INSERT INTO person ( firstname, lastname,email,phone,address,dob ,alive, fatherId, image, sex) " +
" VALUES " +
" ('Ho 1','Tran', 'Ho.Tran@hotmail.com', '002', '1 Cabramatta','1960-09-01', true, 0,'', 'M'), " +
" ('Jimmy 2','Tran', 'Ho.Tran@hotmail.com', '003', '34 Cabramatta','1980-09-01', true, 1,'','M'), " +
" ('Joe 3','Tran', 'Joe.Tran@hotmail.com', '006', '32 Cabramatta','1980-10-01', true, 1,'','M'), " +
" ('John 4','Tran', 'John.Tran@hotmail.com', '008', '30 Cabramatta','1990-12-01', true, 3,'','M')," +
" ('Lee 5','Tran', 'Lee.Tran@hotmail.com', '030', '18 Cabramatta','2000-12-01', true, 2,'','M'), " +
" ('Len 6','Nguyen', 'Len.nguyen@hotmail.com', '001', '1 Home','1950-01-01', true, 0,'','M'), " +
" ('Son 7','Nguyen', 'son.Nguyen@hotmail.com', '001', '10 Home','1980-01-01', true,6,'','M'), " +
" ('Loa 8','Tran', 'Loa.Tran@hotmail.com', '020', '11 Cabramatta','2002-12-01', true, 3,'','M'), " +
" ('Du 9','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 4,'','M'), " +
" ('Linh 10','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 5,'','M'), " +
" ('COA 11','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 8,'','M'), " +
" ('Hoa 12','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 8,'','F'), " +
" ('Nhi 13','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 8,'','F'), " +
" ('Mai 14','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 8,'','F'), " +
" ('Trang 15','Tran', 'Du.Tran@hotmail.com', '001', '12 Cabramatta','2002-12-01', true, 7,'','F'), " +
" ('Tom 16','Tran', 'Ho.Tran@hotmail.com', '007', '34 Cabramatta','1999-01-01', true, 2,'','M');";
try
{
int count = _context.staff.Count();
if (count < 1)
{
var conn = _context.Database.GetDbConnection();
try
{
conn.Open();
var command = conn.CreateCommand();
command.CommandText = sptext;
command.CommandType = System.Data.CommandType.Text;
command.ExecuteNonQuery();
command = conn.CreateCommand();
command.CommandText = txt;
command.CommandType = System.Data.CommandType.Text;
command.ExecuteNonQuery();
command.CommandText = workertxt;
command.CommandType = System.Data.CommandType.Text;
command.ExecuteNonQuery();
id = 9;
}
catch
{
id = -1;
}
finally
{
conn.Close();
}
}
else
id = 1;
}
catch (Exception ex)
{
id = -10;
}
return id;
}
}
@@ -0,0 +1,381 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FamilyTreeAPI.Models;
using FamilyTreeAPI.Entities;
using FamilyTreeAPI.Interface;
using DocumentFormat.OpenXml.Drawing.Diagrams;
using Microsoft.AspNetCore.Http;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Wordprocessing;
namespace FamilyTreeAPI.Repository;
/* password = cGFzc3dvcmQ=
*
INSERT INTO public.staff(
firstname, lastname, email, phone, stype, srole, spassword, sactive)
VALUES ( 'kham', 'vilaythong', 'kham.vilaythong', '009', 1, 2, 'cGFzc3dvcmQ=', true);
*/
public class StaffRepository : IStaff
{
private readonly FamilyTreeDBContext _context;
private readonly IHttpContextAccessor _httpContext;
public StaffRepository(FamilyTreeDBContext context, IHttpContextAccessor httpContext)
{
_context = context;
_httpContext = httpContext;
}
private StaffDto FillDto(staff model)
{
StaffDto dto = new StaffDto();
dto.Firstname = model.Firstname;
dto.Lastname = model.Lastname;
dto.Type = model.Stype;
dto.Phone = model.Phone;
dto.Active = model.Sactive ?? false;
dto.Email = model.Email;
/*
dto.AddedOn = model.AddedOn;
dto.RoleType = model.RoleType;
*/
dto.RoleType = model.Srole;
dto.Id = model.Id;
return dto;
}
private staff FillModel(staff model, StaffDto dto)
{
if (!string.IsNullOrEmpty(dto.Firstname))
model.Firstname = dto.Firstname.Trim();
if (!string.IsNullOrEmpty(dto.Lastname))
model.Lastname = dto.Lastname.Trim();
if (!string.IsNullOrEmpty(dto.Phone))
model.Phone = dto.Phone.Trim();
if (dto.Id > 0)
model.Id = dto.Id;
if (!string.IsNullOrEmpty(dto.Password))
{
string password = dto.Password.Trim();
model.Spassword = Ultils.Base64Encode(password);
}
model.Email = dto.Email;
model.Srole = dto.RoleType;
model.Sactive = dto.Active;
return model;
}
public async Task<ResultModel<Dictionary<int,StaffDto>>> GetDicStaffs()
{
Dictionary<int,StaffDto> dlist = new();
int statuscode = 0;
string error = "";
StaffDto item;
staff model;
try
{
var list = await _context.staff.ToListAsync();
for (int i = 0; i< list.Count; i++)
{
model = list[i];
item = FillDto(model);
dlist.Add(item.Id, item);
}
statuscode = 1;
//list.Sort((x, y) => x.Code.CompareTo(y.Code));
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<Dictionary<int,StaffDto>>()
{
Data = dlist,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<List<StaffDto> >> GetStaff(StaffCriteria criteria)
{
List<StaffDto> list = new();
int statuscode = 0;
string error = "";
try
{
list = await _context.LoadStaffAsync(criteria);
statuscode = 1;
//list.Sort((x, y) => x.Code.CompareTo(y.Code));
}
catch (Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<List<StaffDto>>()
{
Data = list,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<StaffDto>> GetStaffById(int id)
{
int statuscode = 0;
string error = "";
StaffDto dto = new StaffDto();
try
{
var item = await _context.staff.FindAsync(id);
if (item == null)
{
statuscode = -1;
}
else
{
dto = FillDto(item);
statuscode = 1;
}
}
catch(Exception ex)
{
error = ex.ToString();
statuscode = -1;
}
return new ResultModel<StaffDto>()
{
Data = dto,
StatusCode = statuscode,
Message = error
};
}
private string GetDateTimeNow()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
public async Task<ResultModel<int>> ResetPassword(ResetPassDto item)
{
int result = -1;
int statuscode = 0;
string error = "";
try
{
staff? model1 = await _context.staff.FindAsync(item.Id);
if (model1 != null)
{
if (!string.IsNullOrEmpty(item.Password))
{
string password = item.Password.Trim();
model1.Spassword = Ultils.Base64Encode(password);
}
// model.LastModified = DateTime.Now;
// model.LastModifiedId = loginName;
var successid = await _context.SaveChangesAsync();
result = item.Id;
}
statuscode = 1;
}
catch (Exception ex)
{
statuscode = -1;
error = ex.ToString();
}
return new ResultModel<int>()
{
Data = result,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<int>> SaveStaff(StaffDto item)
{
int result = default(int);
int statuscode = 0;
string error = "";
HttpContext? httpContext = _httpContext.HttpContext;
string loginName = "";
if (httpContext != null)
{
UserDto? user = (UserDto?)httpContext.Items["User"];
if (user != null)
loginName = user.FirstName + " " + user.LastName;
}
try
{
if (item.Id < 1)
{
bool already = await CheckLoginAlready(item.Email.Trim());
if (already)
{
return new ResultModel<int>()
{
Data = 0,
StatusCode = 0,
Message = "user name already exist in Database"
};
}
}
staff model;
if (item.Id < 1)
{
model = new();
model = FillModel(model, item);
// model.Active = true;
// model.AddedBy = loginName;
// model.AddedOn = DateTime.Now;
_context.staff.Add(model);
await _context.SaveChangesAsync();
result = model.Id;
}
else
{
staff? model1 = await _context.staff.FindAsync(item.Id);
model1 = FillModel(model1, item);
// model.LastModified = DateTime.Now;
// model.LastModifiedId = loginName;
var successid = await _context.SaveChangesAsync();
result = item.Id;
}
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
};
}
private async Task<bool> CheckLoginAlready(string login)
{
bool result = false;
var model = await _context.staff.Where(x => x.Email == login).ToListAsync();
if (model.Count > 0)
result = true;
return result;
}
public async Task<ResultModel<int>> SaveStaffNew(StaffDto item)
{
int result = default(int);
int statuscode = 0;
string error = "";
try
{
if (item.Id < 1)
{
bool already = await CheckLoginAlready(item.Email.Trim());
if (already)
{
return new ResultModel<int>()
{
Data = 0,
StatusCode = 0,
Message = "user name already exist in db"
};
}
}
staff model;
if (item.Id < 1)
{
model = new();
model = FillModel(model, item);
// model.AddedOn = DateTime.Now;
// model.Active = true;
_context.staff.Add(model);
}
else
{
model = await _context.staff.FindAsync(item.Id);
model = FillModel(model, item);
}
var successid = await _context.SaveChangesAsync();
result = 1;
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>> Delete(int id)
{
int result = 1;
int statuscode = 0;
string error = "";
try
{
staff model;
model = await _context.staff.FindAsync(id);
// if (model != null)
// model.Active = false;
var successCount = await _context.SaveChangesAsync();
statuscode = 1;
result = 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
};
}
}
@@ -0,0 +1,251 @@
/**************************************/
CREATE TABLE IF NOT EXISTS public.client
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
clientname character(80) COLLATE pg_catalog."default",
contact character(80) COLLATE pg_catalog."default",
cactive boolean,
address character(120),
email character(80),
phone character(30),
CONSTRAINT client_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.client
OWNER to postgres;
CREATE TABLE IF NOT EXISTS public.lookup
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
code character(10) COLLATE pg_catalog."default",
description character(80) COLLATE pg_catalog."default",
lactive boolean,
ltype character(20) COLLATE pg_catalog."default",
CONSTRAINT lookup_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.lookup
OWNER to postgres;
-- Table: public.job
-- DROP TABLE IF EXISTS public.job;
CREATE TABLE IF NOT EXISTS public.job
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
code character(10) COLLATE pg_catalog."default",
description character(80) COLLATE pg_catalog."default",
jactive boolean,
CONSTRAINT job_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.job
OWNER to postgres;
-- Table: public.servicetask
-- DROP TABLE IF EXISTS public.servicetask;
CREATE TABLE IF NOT EXISTS public.servicetask
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
assignto integer,
clientid integer,
code character(10) COLLATE pg_catalog."default",
description character(80) COLLATE pg_catalog."default",
sdate date,
serviceno character(20) COLLATE pg_catalog."default",
staffid integer,
status integer,
CONSTRAINT servicetask_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.servicetask
OWNER to postgres;
-- Table: public.staffwork
-- DROP TABLE IF EXISTS public.staffwork;
CREATE TABLE IF NOT EXISTS public.staffwork
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
description character(80) COLLATE pg_catalog."default",
sactive boolean,
staffid integer,
startdate date,
CONSTRAINT staffwork_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.staffwork
OWNER to postgres;
-- Table: public.staffworkdetail
-- DROP TABLE IF EXISTS public.staffworkdetail;
CREATE TABLE IF NOT EXISTS public.staffworkdetail
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
jobid integer,
sactive boolean,
starttime timestamp without time zone,
stoptime timestamp without time zone,
task character(80) COLLATE pg_catalog."default",
swid integer,
CONSTRAINT staffworkdetail_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.staffworkdetail
OWNER to postgres;
/******************************************/
ALTER TABLE servicetask
ADD FOREIGN KEY (clientid) REFERENCES client(id);
ALTER TABLE servicetask
ADD FOREIGN KEY (staffid) REFERENCES staff(id);
ALTER TABLE servicetask
ADD FOREIGN KEY (assignto) REFERENCES staff(id);
ALTER TABLE staffwork
ADD FOREIGN KEY (staffid) REFERENCES staff(id);
ALTER TABLE staffworkdetail
ADD FOREIGN KEY (swid) REFERENCES staffwork(id);
ALTER TABLE staffworkdetail
ADD FOREIGN KEY (jobid) REFERENCES job(id);
/************************************/
CREATE OR REPLACE FUNCTION usp_search_user (
iemail character varying,
ifirstname character varying,
ilastname character varying)
RETURNS TABLE(id integer, fistname character varying, lastname character varying, email character varying, stype integer, sactive boolean, srole integer, spassword character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
begin
return query SELECT
e.id,
e.firstname,
e.lastname,
e.email,
e.stype,
e.sactive,
e.srole,
e.spassword
FROM staff e
WHERE (iemail = '' or e.email ilike iemail || '%')
and (ilastname = '' or e.lastname ilike ilastname || '%')
and (ifirstname = '' or e.firstname ilike ifirstname || '%');
end;
$BODY$;
CREATE OR REPLACE FUNCTION usp_staffwork_detail(
ifromdate date,
itodate date,
istaffid integer)
RETURNS TABLE(id integer, staffid integer, description character varying(80),
startdate date, sactive boolean, did integer, starttime timestamp, stoptime timestamp,
task character varying(80))
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
begin
return query SELECT
w.id,
w.staffid,
w.description,
w.startdate,
w.sactive,
d.id as did,
d.starttime,
d.stoptime,
d.task
FROM staffwork w,
staffworkdetail d
where w.id = d.swid
and (istaffid = 0 or w.staffid = istaffid)
and w.startdate >= ifromdate
and w.startdate <= itodate
order by id, did;
end;
$BODY$;
CREATE OR REPLACE FUNCTION usp_servicetask_sel(
ifromdate date,
itodate date,
istaffid integer,
iassignto integer,
iclientid integer
)
RETURNS TABLE(id integer,
staffid integer,
sdate date,
description character varying(80),
code character varying(10),
status integer,
assignTo integer,
clientid integer,
serviceno character varying(20))
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
AS $BODY$
begin
return query SELECT
s.id,
s.staffid,
s.sdate,
s.description,
s.code,
s.status,
s.assignto,
s.clientid,
s."serviceNo"
FROM servicetask s
where (istaffid < 1 or s.staffid = istaffid)
and (iassignto < 1 or s.assignto = iassignto)
and (iclientid < 1 or s.clientid = iclientid)
and s.sdate >= ifromdate
and s.sdate <= itodate
;
end;
$BODY$;
@@ -0,0 +1,16 @@
namespace FamilyTreeAPI.Repository
{
public class Ultils
{
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
}
}
@@ -0,0 +1,257 @@
//using BCryptNet = BCrypt.Net.BCrypt;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Entities;
using System;
using FamilyTreeAPI.Models;
using Microsoft.AspNetCore.Http;
using System.Text.Json;
namespace FamilyTreeAPI.Repository;
public class UserServiceRepository : IUserService
{
private FamilyTreeDBContext _context;
private IJwtUtils _jwtUtils;
private readonly IHttpContextAccessor _httpcontext;
private readonly AppSettings _appSettings;
public UserServiceRepository(
FamilyTreeDBContext context,
IJwtUtils jwtUtils,
IHttpContextAccessor httpcontext,
IOptions<AppSettings> appSettings)
{
_context = context;
_jwtUtils = jwtUtils;
_appSettings = appSettings.Value;
this._httpcontext = httpcontext;
}
public Task<ResultModel<int>> Logout(string token, UserDto user, string remoteIpAddress)
{
/*
int retval = 1;
int statuscode = 1;
string str255 = token;
var histo = await _context.AdminLoginHistories.Where(x => x.UserId == user.Id
&& x.RecordIntegrity == token
&& x.LoginTyped == user.Username).ToListAsync();
if (histo.Count > 0)
{
histo[0].LogoutDatetime = DateTime.Now;
await _context.SaveChangesAsync();
}
*/
var result = new ResultModel<int>()
{
Data = 1,
StatusCode = 1
};
return Task.FromResult(result);
}
private bool checkLogin(staff user, string email, string password)
{
bool result = false;
// string dpassword = Ultils.Base64Encode(password);
result = user.Spassword == password;
return result;
}
public async Task<ResultModel<AuthenticateResponse>> Login(AuthenticateRequest model)
{
UserDto myUser = new();
AuthenticateResponse retval = null;
string error = "user name is not in DB or user Name profile is not generic";
int statuscode = 0;
try
{
//now check the adminuser table
myUser.Username = model.Username;
var user = _context.staff.
SingleOrDefault(x => x.Email == model.Username
&& true == x.Sactive);
if (user != null)
{
bool loginOK = checkLogin(user, model.Username, model.Password);
if (loginOK)
{
// myUser.Role = user.RoleType;
myUser.Id = user.Id;
myUser.Role = user.Srole ?? 0;
myUser.Email = user.Email;
myUser.Phone = user.Phone;
myUser.FirstName = user.Firstname;
myUser.LastName = user.Lastname;
//myUser.ValidationPointId = user.ValidationPointID ?? 0;
statuscode = 1;
}
else //not allow
{
statuscode = -1;
myUser.Role = 1;
myUser.Id = -1;
error = "user name cannot login email or password";
}
// validate
// if (user == null || !BCryptNet.Verify(model.Password, user.PasswordHash))
// authentication successful so generate jwt token
if (statuscode == 1)
{
var jwtToken = _jwtUtils.GenerateJwtToken(myUser);
retval = new AuthenticateResponse(myUser, jwtToken, myUser.Role);
error = "";
}
}
else
{
error = "user name or password is not correct";
}
}
catch (Exception ex)
{
retval = null;
error = ex.ToString();
statuscode = -1;
}
//writelog for login user.
// if (myUser != null)
// await AddToSession(myUser);
return new ResultModel<AuthenticateResponse>()
{
Data = retval,
StatusCode = statuscode,
Message = error
};
}
public async Task<ResultModel<AuthenticateResponse>> LoginApiAD(AuthenticateRequest model, string remoteIpAddress)
{
UserDto myUser = new();
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;
//KCO, D204KCO
//now check the adminuser table
//ward clerk
try
{
//now check the adminuser table
myUser.Username = model.Username;
var user = _context.staff.
SingleOrDefault(x => x.Email == model.Username
&& true == x.Sactive);
if (user != null)
{
bool loginOK = checkLogin(user,model.Username, model.Password);
if (loginOK)
{
// myUser.Role = user.RoleType;
myUser.Id = user.Id;
myUser.Role = user.Srole ?? 0;
myUser.Email = user.Email;
myUser.Phone = user.Phone;
myUser.FirstName = user.Firstname;
myUser.LastName = user.Lastname;
//myUser.ValidationPointId = user.ValidationPointID ?? 0;
statuscode = 1;
}
else // allow
{
statuscode = 1;
myUser.Role = 1;
myUser.Id = -1;
//error = "user name does not exist in adminUser";
}
// validate
// if (user == null || !BCryptNet.Verify(model.Password, user.PasswordHash))
// authentication successful so generate jwt token
if (statuscode == 1)
{
var jwtToken = _jwtUtils.GenerateJwtToken(myUser);
retval = new AuthenticateResponse(myUser, jwtToken, myUser.Role);
error = "";
}
}
else
{
error = "user name or password is not correct";
}
}
catch (Exception ex)
{
retval = null;
error = ex.ToString();
statuscode = -1;
}
//writelog for login user.
// if (myUser != null)
// await AddToSession(myUser);
return new ResultModel<AuthenticateResponse>()
{
Data = retval,
StatusCode = statuscode,
Message = error
};
}
//get like this
private async Task GetCurrentUser()
{
await _httpcontext.HttpContext.Session.LoadAsync();
string userString = _httpcontext.HttpContext.Session.GetString("user");
if (userString != null && userString != "")
{
var user = JsonSerializer.Deserialize<UserDto>(userString);
if (user != null)
{
// return user;
}
}
}
/*
private User LoginADStaff(ADConfig adConfig, string username, string password)
{
ADStaffLink staffLink = new ADStaffLink(adConfig);
MyADObject myADObj = staffLink.CheckADCredentials(username,password);
User user = new()
{
Username = myADObj.StafflinkNo,
Email = myADObj.Email,
FirstName = myADObj.FirstName,
LastName = myADObj.LastName
};
// myADObj.JobTitle;
return user;
// return null;
}
*/
}
@@ -0,0 +1,115 @@
/*
declare @id int
set @id = 7
delete from MotorAccess where mv_id = @id
delete from [dbo].[MotorVehicle]
where mv_id = @id
*/
CREATE OR REPLACE FUNCTION public.usp_search_user(
iemail character varying,
ifirstname character varying,
ilastname character varying)
RETURNS TABLE(id integer, fistname character varying, lastname character varying, email character varying,
stype integer, sactive boolean, srole integer, spassword character varying)
AS $BODY$
begin
return query SELECT
e.id,
e.firstname,
e.lastname,
e.email,
e.stype,
e.sactive,
e.srole,
e.spassword
FROM staff e
WHERE (iemail = '' or e.email ilike iemail || '%')
and (ilastname = '' or e.lastname ilike ilastname || '%')
and (ifirstname = '' or e.firstname ilike ifirstname || '%');
end;
$BODY$;
CREATE OR REPLACE FUNCTION get_staff (
iemail character varying,
ifirstname character varying,
ilastname character varying
)
RETURNS TABLE (
id int,
firstname varchar,
lastname varchar,
email varchar,
stype INT,
sactive boolean,
srole INT,
spassword varchar )
AS $$
BEGIN
RETURN QUERY
e.id,
e.firstname,
e.lastname,
e.email,
e.stype,
e.sactive,
e.srole,
e.spassword
FROM staff e
WHERE (iemail = '' or e.email ilike iemail || '%')
and (ilastname = '' or e.lastname ilike ilastname || '%')
and (ifirstname = '' or e.firstname ilike ifirstname || '%');
END; $$
LANGUAGE 'plpgsql';
how to host under .net 8 web api under linux
1) create [webAPI_name].service
******************************************
# Example: /etc/systemd/system/yourwebapp.service
[Unit]
Description=Your .NET 8 Web API
After=network.target
[Service]
WorkingDirectory=/var/www/yourwebapp
ExecStart=/usr/bin/dotnet /var/www/yourwebapp/YourWebApi.dll
Restart=always
RestartSec=10
SyslogIdentifier=yourwebapp
User=www-data # or a dedicated user
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
************************************************
2) start /stop the service under linux
***********************************************
sudo systemctl enable yourwebapp.service
sudo systemctl start yourwebapp.service
**********************************************
3) host under nginx or apache
************************************************
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:5000; # Kestrel's listening port
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
***********************************************************************
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,27 @@
{
"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_25": "host=192.168.1.100;port=5432;database=FamilyTreeDB;username=postgres;password=Positive~1;",
"ImageFolder": "c:\\temp\\Family",
"ImportFolder": "c:\\temp"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.DataProtection": "None"
}
},
"AllowedHosts": "*",
"Kestrel_not_use": {
"Endpoints": {
"Http": {
"Url": "http://*:5015"
}
}
}
}
@@ -0,0 +1,15 @@
[Unit]
Description=ASP.NET Core Web App running on Ubuntu
[Service]
WorkingDirectory=/var/www/familytree/api
ExecStart=/usr/bin/dotnet /var/www/fadmilytree/api/FamilyTreeAPI.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-web-familytree-api
# 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
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<DockerPublishLocally>False</DockerPublishLocally>
<ProjectGuid>81dded9d-158b-e303-5f62-77a2896d2a5a</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>workapi</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>
+12
View File
@@ -0,0 +1,12 @@
services:
workapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_HTTP_PORTS=8080
ports:
- "8080"
+73
View File
@@ -0,0 +1,73 @@
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
familytreeui:
image: ${DOCKER_REGISTRY-}familytreeui
depends_on:
- "familytreeapi"
ports:
- "4200:80"
build:
context: UI
dockerfile: Dockerfile
container_name: familytreeui
volumes:
- ./:/app
networks:
- dev
familytreeapi:
image: ${DOCKER_REGISTRY-}familytreeapi
container_name: familytreeapi-services
depends_on:
- "postgresdb"
environment:
- "Logging:Microsoft.AspNetCore.DataProtection:None"
- "AppSettings:SQLConnectionString=host=postgresdb;database=FamilyTreeDB;username=postgres;password=Positive~1"
volumes:
- app_import:/home/kham/dvolumes/app_import
- app_images:/home/kham/dvolumes/app_images
ports:
- "8080:8080"
build:
context: .
dockerfile: FamilyTreeAPI/Dockerfile
networks:
- dev
volumes:
myapikey:
external: true
pgadmin-data:
external: true
postgresdata:
external: true
+11
View File
@@ -0,0 +1,11 @@
{
"profiles": {
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"serviceActions": {
"workapi": "StartDebugging"
}
}
}
}
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+5
View File
@@ -0,0 +1,5 @@
{
"plugins": {
"@tailwindcss/postcss": {}
}
}
+32
View File
@@ -0,0 +1,32 @@
FROM node:24.0.0 AS build
RUN mkdir -p /app
WORKDIR /app
COPY ["./*.json", "./"]
RUN npm install -g @angular/cli
COPY . .
# Run command in Virtual directory
RUN npm cache clean --force
RUN npm install
#RUN ng build --configuration=production
RUN ng build -c=production
FROM nginx:latest
#### copy nginx conf
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
#COPY --from=build app/dist/Familytree/browser /usr/share/nginx/html
#### copy artifact build from the 'build environment' old is not in browser folder
COPY --from=build /app/dist/Familytree/browser /usr/share/nginx/html
#CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
#docker build -t my_angular_app:latest .
#docker run --name carval -d -p 4200:80 varlidate_ui
#npm error code ENOENT
+59
View File
@@ -0,0 +1,59 @@
# FamilyTree
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.1.1.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
+91
View File
@@ -0,0 +1,91 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"FamilyTree": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"allowedCommonJsDependencies": [
"moment", "file-saver", "xlsx"
],
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "5MB",
"maximumError": "6MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "FamilyTree:build:production"
},
"development": {
"buildTarget": "FamilyTree:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
]
}
}
}
}
},
"cli": {
"analytics": false
}
}
+20
View File
@@ -0,0 +1,20 @@
server {
listen 80;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html =404;
}
}
+56
View File
@@ -0,0 +1,56 @@
{
"name": "family-tree",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"prettier": {
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.1.7",
"@angular/compiler": "^20.1.7",
"@angular/core": "^20.1.7",
"@angular/forms": "^20.1.7",
"@angular/platform-browser": "^20.1.7",
"@angular/router": "^20.1.7",
"@primeuix/themes": "^1.2.1",
"@tailwindcss/postcss": "^4.1.11",
"file-saver": "^2.0.5",
"moment": "^2.30.1",
"postcss": "^8.5.6",
"primeicons": "^7.0.0",
"primeng": "^20.0.0",
"rxjs": "~7.8.0",
"tailwindcss": "^4.1.11",
"tailwindcss-primeui": "^0.6.1",
"tslib": "^2.3.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@angular/build": "^20.1.6",
"@angular/cli": "^20.1.6",
"@angular/compiler-cli": "^20.1.7",
"@types/file-saver": "^2.0.7",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.8.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.8.2"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"baseUrl1": "http://192.168.8.188:5015",
"baseUrl": "http://localhost:5016",
"baseUrl_docker": "http://localhost:8080",
"attachment": "http://localhost/document/family"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Angular Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="./index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
+35
View File
@@ -0,0 +1,35 @@
import { ApplicationConfig, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { JwtInterceptor, ErrorInterceptor, initializeApp } from './shares';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ConfirmationService, MessageService } from 'primeng/api';
import { routes } from './app.routes';
import MyPreset from './mythem';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
MessageService, ConfirmationService,
provideAppInitializer(initializeApp()),
provideHttpClient(withInterceptors([JwtInterceptor, ErrorInterceptor])),
provideZonelessChangeDetection(),
provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: MyPreset,
options: {
cssLayer: {
name: 'primeng',
order: 'theme, base, primeng'
}
}
}
}),
provideRouter(routes)
]
};
/*
ng build --base-href "/familytreeui/" -c production
*/
View File
+5
View File
@@ -0,0 +1,5 @@
<p-toast position="bottom-center" />
<app-toolbar> </app-toolbar>
<router-outlet />
<p-confirmDialog [style]="{width: '50vw'}"
rejectButtonStyleClass="p-button-text"> </p-confirmDialog>
+19
View File
@@ -0,0 +1,19 @@
import { Routes } from '@angular/router';
import { Login } from './login';
import { StaffComponent, StaffEditComponent } from './staff';
import { AuthGuard } from './route-guard';
import { FamilyTree, FamilyList} from './person';
import { ImportCom } from './import.com/import.com';
import { ImageDisplayComponent } from './pickperson/image.display';
export const routes: Routes = [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
// { path: 'approval', component: ApprovalComponent,canActivate: [AuthGuard], data: { roleAllowed: '1,2,3' }},},
{ path: 'login', component: Login},
{ path: 'staff', component: StaffComponent},
{ path: 'person', component: FamilyList},
{ path: 'imagedisplay/:id', component: ImageDisplayComponent},
{ path: 'import', component: ImportCom},
{ path: 'familytree', component: FamilyTree},
{ path: 'staff/:id', component: StaffEditComponent},
];
+14
View File
@@ -0,0 +1,14 @@
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';
@Component({
selector: 'app-root',
imports: [RouterOutlet,ToastModule,ToolbarComponent,ConfirmDialogModule],
templateUrl: './app.html',
styleUrl: './app.css'
})
export class App {
protected readonly title = signal('FamilyTree');
}
@@ -0,0 +1,10 @@
<div>
<label class="flex mb-2"> Import Excel File</label>
<p-fileupload #fu mode="basic" chooseLabel="Choose"
chooseIcon="pi pi-upload" name="file"
url='{{baseUrl}}'
maxFileSize="10000000000" (onUpload)="onUpload($event)" />
<p-button styleClass="mt-4" label="Upload" (onClick)="fu.upload()" severity="primary" />
</div>
@@ -0,0 +1,38 @@
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 { AppSettingService } from '../shares';
/*
interface UploadEvent {
originalEvent: Event;
files: File[];
}*/
@Component({
selector: 'app-import.com',
imports: [FileUploadModule, ButtonModule,ToastModule,FileUpload],
templateUrl: './import.com.html',
styleUrl: './import.com.css'
})
export class ImportCom implements OnInit {
uploadedFiles: any[] = [];
baseUrl = "";
private appSetting = inject(AppSettingService);
constructor(private messageService: MessageService) {}
ngOnInit(): void
{
const cbaseUrl = this.appSetting.appSetting.baseUrl;
//http://localhost:5015/api/FileUpload/UploadFile"
this.baseUrl = cbaseUrl + "/api/FileUpload/UploadFile";
}
onUpload(event:FileUploadEvent) {
for(let file of event.files) {
this.uploadedFiles.push(file);
}
this.messageService.add({severity: 'info', summary: 'File Uploaded', detail: ''});
}
}

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