add all change in now.

This commit is contained in:
2025-08-29 23:17:58 +10:00
parent be2756d85f
commit 6cea606cc2
22 changed files with 259 additions and 63 deletions
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.8",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
@@ -14,10 +14,12 @@ public class FileUploadController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly ImportPersonRepository _importPersonRepository;
public FileUploadController(IWebHostEnvironment hostingEnvironment, ImportPersonRepository importPersonRepository)
private readonly IConfiguration _config;
public FileUploadController(IWebHostEnvironment hostingEnvironment, ImportPersonRepository importPersonRepository, IConfiguration config)
{
_hostingEnvironment = hostingEnvironment;
_importPersonRepository = importPersonRepository;
_config = config;
}
[HttpPost("UploadFile")]
@@ -31,9 +33,11 @@ public class FileUploadController : ControllerBase
try
{
// Define the upload directory
// var uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
var uploadsFolder = Path.Combine(_hostingEnvironment.ContentRootPath, "uploads");
// var uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
string importFolder = _config.GetValue<string>("ImportFolder");
var uploadsFolder = Path.Combine(_hostingEnvironment.ContentRootPath, importFolder);
if (!Directory.Exists(uploadsFolder))
{
Directory.CreateDirectory(uploadsFolder);
@@ -50,9 +54,15 @@ public class FileUploadController : ControllerBase
{
// await file.CopyToAsync(stream);
await file.CopyToAsync(stream);
output = await _importPersonRepository.ImportPerson(stream, "Sheet1");
// 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);
+1
View File
@@ -18,6 +18,7 @@ public class MappingFatherMother
public int IMotherId { get; set; }
}
public class LookupDto
{
public int Id { get; set; } = 0;
@@ -5,6 +5,12 @@ 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; }
+42 -4
View File
@@ -2,12 +2,16 @@
ubuntu
$) sudo bash
$) apt-get update
$) apt-get install dotnet-sdk-8.0
$) apt-get install dotnet-runtime-8.0
$) apt-get install aspnet-core-runtime-8.0
$) 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
@@ -66,4 +70,38 @@ $ sudo nano /etc/nginx/sites-avaible/default
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
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
@@ -38,34 +38,39 @@ public class ImportPersonRepository
_httpContext = httpContext;
}
public async Task<List<CodeDto<string>>> ImportPerson(FileStream fileStream, string sheetName)
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();
// 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 = 1; row <= stats.NumberOfRows; row++)
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.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);
@@ -101,9 +106,33 @@ public class ImportPersonRepository
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;
}
@@ -7,8 +7,9 @@ using FamilyTreeAPI.Interface;
using FamilyTreeAPI.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
//using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
@@ -460,20 +461,28 @@ public partial class PersonRepository : IPerson
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))
/*
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))
{
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)
+29 -5
View File
@@ -10,9 +10,26 @@ public class Seed
{
_context = context;
}
public int InsertOneUser()
public int InsertOneUser()
{
int id = -1;
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) " +
@@ -20,8 +37,9 @@ public class Seed
" ( '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 " +
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'), " +
@@ -51,6 +69,11 @@ public class Seed
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();
@@ -74,9 +97,10 @@ public class Seed
id = 1;
}
catch
catch (Exception ex)
{
id = -10;
}
return id;
}
@@ -11,12 +11,9 @@ 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)
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000
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
+18 -10
View File
@@ -3,17 +3,25 @@
"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;",
"LoginWebAPI": "http://nephmdb-sql006/CommonWebApiAD/api/AD",
"ClientURL": "http://localhost:4200/approval",
"ImageFolder": "c:\\temp\\Family"
"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"
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.DataProtection": "None"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:5015"
}
},
"AllowedHosts": "*"
}
}
}
@@ -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
+1 -1
View File
@@ -31,5 +31,5 @@ export const appConfig: ApplicationConfig = {
]
};
/*
ng build --base-href "/FamilyTreeUI/" -c production
ng build --base-href "/familytreeui/" -c production
*/
+10
View File
@@ -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="http://localhost:5015/api/FileUpload/UploadFile"
maxFileSize="10000000000" (onUpload)="onUpload($event)" />
<p-button styleClass="mt-4" label="Upload" (onClick)="fu.upload()" severity="primary" />
</div>
+29
View File
@@ -0,0 +1,29 @@
import { Component } 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';
/*
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 {
uploadedFiles: any[] = [];
constructor(private messageService: MessageService) {}
onUpload(event:FileUploadEvent) {
for(let file of event.files) {
this.uploadedFiles.push(file);
}
this.messageService.add({severity: 'info', summary: 'File Uploaded', detail: ''});
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
<div class="flex justify-center items-center h-full">
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" style="max-width: 600px;min-width: 450px;">
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" style="max-width: 600px;min-width: 300px;">
<p-card>
<div>
<img class="border-round" style="width:100%;height:100px;" alt="Logo" src="images/application_image_login.png">
+1 -1
View File
@@ -2,7 +2,7 @@
<h3>Person List</h3>
<div>
<form>
<div class="grid grid-cols-4 gap-2">
<div class="grid md:grid-cols-4 gap-2">
<div class="">
<label for="login">Email</label>
<input id="login" pInputText name="login" type="text" [(ngModel)]="email"
+3 -3
View File
@@ -1,7 +1,7 @@
<div class=" mt-2">
<form [formGroup]="adminuserForm" (ngSubmit)="onSubmit($event)" >
<div class="ml-2 grid grid-cols-2 gap-3 p-2">
<div class="ml-2 grid md:grid-cols-2 gap-3 p-2">
<div class="">
<label for="lastname1">Surname<strong class="app-require">*</strong></label>
<input id="lastname1" pInputText formControlName="lastname" type="text"
@@ -17,11 +17,11 @@
<p-select [options]="sexList" optionLabel="name" optionValue="status" placeholder="Select Gender"
formControlName="sex"
[class]="getClassForRequire('inputfield w-full p-inputtext-sm','sex')"
styleClass="w-full p-inputtext-sm mr-1"></p-select >
class="w-full p-inputtext-sm mr-1"></p-select >
</div>
<div class="">
<label for="dob" class="flex w-full">DOB</label>
<p-datepicker ariaLabelledBy="dob" formControlName="dob" dateFormat="dd/mm/yy"></p-datepicker>
<p-datepicker ariaLabelledBy="dob" formControlName="dob" [iconDisplay]="'input'" [showIcon]="true" dateFormat="dd/mm/yy" ></p-datepicker>
</div>
<div class="">
<label for="login">Email</label>
+2 -2
View File
@@ -1,6 +1,6 @@
<div class="flex-row w-full h-full">
<p-table #dt2 [value]="familyList()" sortMode="multiple" styleClass="p-datatable-sm"
styleClass="p-datatable-sm" responsiveLayout="stack" paginatorDropdownAppendTo="body"
<p-table #dt2 [value]="familyList()" sortMode="multiple"
class="p-datatable-sm" paginatorDropdownAppendTo="body"
dataKey="id" selectionMode="single" [(selection)]="selectedPerson"
[paginator]="true" [globalFilterFields]="['lastName', 'firstName', 'sex']"
[rows]="10" [rowsPerPageOptions]="[5,10, 20,50]"
+3 -3
View File
@@ -2,7 +2,7 @@
<h3>Staff List</h3>
<div>
<form>
<div class="grid grid-cols-4 gap-2">
<div class="grid md:grid-cols-4 gap-2">
<div class="">
<label for="login">Email</label>
<input id="login" pInputText name="login" type="text" [(ngModel)]="email"
@@ -28,8 +28,8 @@
</form>
</div>
<div>
<p-table [value]="userList" sortMode="multiple" styleClass="p-datatable-sm"
styleClass="p-datatable-sm" responsiveLayout="stack" [loading]="loading">
<p-table [value]="userList" sortMode="multiple"
class="p-datatable-sm" [loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="email">Email<p-sortIcon field="email"></p-sortIcon>
+1 -1
View File
@@ -3,7 +3,7 @@
{{getEditText()}}
</div>
<form [formGroup]="adminuserForm" (ngSubmit)="onSubmit()" >
<div class="ml-2 grid grid-cols-2 gap-3 p-2">
<div class="ml-2 grid md:grid-cols-2 gap-3 p-2">
<div class="">
<label for="lastname1">Surname<strong class="app-require">*</strong></label>
<input id="lastname1" pInputText formControlName="lastname" type="text"
+7
View File
@@ -128,6 +128,13 @@ export class ToolbarComponent implements OnInit, OnDestroy {
}
},
{
label: 'Import', icon: 'pi pi-users',
command: () => {
this.router.navigate(['/import']);
}
},
{
label: 'Family tree', icon: 'pi pi-users',
command: () => {