diff --git a/API/FamilyTreeAPI/.config/dotnet-tools.json b/API/FamilyTreeAPI/.config/dotnet-tools.json new file mode 100644 index 0000000..837b189 --- /dev/null +++ b/API/FamilyTreeAPI/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "9.0.8", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/API/FamilyTreeAPI/Controllers/FileUploadController.cs b/API/FamilyTreeAPI/Controllers/FileUploadController.cs index a1b0bd9..a36298a 100644 --- a/API/FamilyTreeAPI/Controllers/FileUploadController.cs +++ b/API/FamilyTreeAPI/Controllers/FileUploadController.cs @@ -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("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); diff --git a/API/FamilyTreeAPI/Entities/LookupDto.cs b/API/FamilyTreeAPI/Entities/LookupDto.cs index 7f4c77b..1ee4610 100644 --- a/API/FamilyTreeAPI/Entities/LookupDto.cs +++ b/API/FamilyTreeAPI/Entities/LookupDto.cs @@ -18,6 +18,7 @@ public class MappingFatherMother public int IMotherId { get; set; } } + public class LookupDto { public int Id { get; set; } = 0; diff --git a/API/FamilyTreeAPI/Entities/RelationShipDto.cs b/API/FamilyTreeAPI/Entities/RelationShipDto.cs index 1decd3f..e5d9448 100644 --- a/API/FamilyTreeAPI/Entities/RelationShipDto.cs +++ b/API/FamilyTreeAPI/Entities/RelationShipDto.cs @@ -5,6 +5,12 @@ public class RelationShiftContainer public int familyId {get; set;} public List relationShips {get; set;} } + +public class ImportRelation +{ + public int FatherId { get; set; } + public int MotherId { get; set; } +} public class RelationShipDto { public enumState State { get; set; } diff --git a/API/FamilyTreeAPI/Install_Linux.txt b/API/FamilyTreeAPI/Install_Linux.txt index b731e37..495babf 100644 --- a/API/FamilyTreeAPI/Install_Linux.txt +++ b/API/FamilyTreeAPI/Install_Linux.txt @@ -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, let’s 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 let’s enable and start the service: sudo systemctl enable kestrel-app.service - sudo systemctl start kestrel-app.service \ No newline at end of file + sudo systemctl start kestrel-app.service + + postgresql.conf: + + Edit postgresql.conf: + Locate the postgresql.conf file, typically in /etc/postgresql//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//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//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 \ No newline at end of file diff --git a/API/FamilyTreeAPI/Repository/ImportPersonRepository.cs b/API/FamilyTreeAPI/Repository/ImportPersonRepository.cs index 03f68cd..97590bd 100644 --- a/API/FamilyTreeAPI/Repository/ImportPersonRepository.cs +++ b/API/FamilyTreeAPI/Repository/ImportPersonRepository.cs @@ -38,34 +38,39 @@ public class ImportPersonRepository _httpContext = httpContext; } - public async Task>> ImportPerson(FileStream fileStream, string sheetName) + public async Task>> ImportPerson(MemoryStream fileStream, string sheetName) { + Dictionary relationDic = new(); int id; + string keypair = ""; //father and mother key List> output = new(); Dictionary updateperson = new(); CodeDto colums; List 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 item in relationDic) + { + model = new RelationShip(); + model.PersonId = item.Value.FatherId; + model.RelatePersonId = item.Value.MotherId; + _context.RelationShips.Add(model); + _context.SaveChanges(); + } + + + return output; } diff --git a/API/FamilyTreeAPI/Repository/PersonRepository.cs b/API/FamilyTreeAPI/Repository/PersonRepository.cs index 336946d..dcb5d0e 100644 --- a/API/FamilyTreeAPI/Repository/PersonRepository.cs +++ b/API/FamilyTreeAPI/Repository/PersonRepository.cs @@ -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> SaveAsync(PersonForSave container) diff --git a/API/FamilyTreeAPI/Repository/Seed.cs b/API/FamilyTreeAPI/Repository/Seed.cs index 3569125..4224195 100644 --- a/API/FamilyTreeAPI/Repository/Seed.cs +++ b/API/FamilyTreeAPI/Repository/Seed.cs @@ -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; } diff --git a/API/FamilyTreeAPI/Repository/storeprocedure.sql b/API/FamilyTreeAPI/Repository/storeprocedure.sql index d3073d8..3a7bed0 100644 --- a/API/FamilyTreeAPI/Repository/storeprocedure.sql +++ b/API/FamilyTreeAPI/Repository/storeprocedure.sql @@ -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 diff --git a/API/FamilyTreeAPI/appsettings.json b/API/FamilyTreeAPI/appsettings.json index 11c208b..0bc3c3a 100644 --- a/API/FamilyTreeAPI/appsettings.json +++ b/API/FamilyTreeAPI/appsettings.json @@ -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": "*" + } } +} diff --git a/API/FamilyTreeAPI/familytree_service.service b/API/FamilyTreeAPI/familytree_service.service index e69de29..9ace7f5 100644 --- a/API/FamilyTreeAPI/familytree_service.service +++ b/API/FamilyTreeAPI/familytree_service.service @@ -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 \ No newline at end of file diff --git a/UI/src/app/app.config.ts b/UI/src/app/app.config.ts index 7a8fbcc..2d0fc1b 100644 --- a/UI/src/app/app.config.ts +++ b/UI/src/app/app.config.ts @@ -31,5 +31,5 @@ export const appConfig: ApplicationConfig = { ] }; /* -ng build --base-href "/FamilyTreeUI/" -c production +ng build --base-href "/familytreeui/" -c production */ \ No newline at end of file diff --git a/UI/src/app/import.com/import.com.css b/UI/src/app/import.com/import.com.css new file mode 100644 index 0000000..e69de29 diff --git a/UI/src/app/import.com/import.com.html b/UI/src/app/import.com/import.com.html new file mode 100644 index 0000000..88539cf --- /dev/null +++ b/UI/src/app/import.com/import.com.html @@ -0,0 +1,10 @@ +
+ + + + + +
diff --git a/UI/src/app/import.com/import.com.ts b/UI/src/app/import.com/import.com.ts new file mode 100644 index 0000000..07f1bca --- /dev/null +++ b/UI/src/app/import.com/import.com.ts @@ -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: ''}); + } +} diff --git a/UI/src/app/login/login.html b/UI/src/app/login/login.html index e03eeb4..46bda9d 100644 --- a/UI/src/app/login/login.html +++ b/UI/src/app/login/login.html @@ -1,5 +1,5 @@
-
+
Logo diff --git a/UI/src/app/person/familylist.html b/UI/src/app/person/familylist.html index 024e19a..75319aa 100644 --- a/UI/src/app/person/familylist.html +++ b/UI/src/app/person/familylist.html @@ -2,7 +2,7 @@

Person List

-
+
-
+
+ class="w-full p-inputtext-sm mr-1">
- +
diff --git a/UI/src/app/pickperson/pickperson.html b/UI/src/app/pickperson/pickperson.html index 1a5690b..d56bbb3 100644 --- a/UI/src/app/pickperson/pickperson.html +++ b/UI/src/app/pickperson/pickperson.html @@ -1,6 +1,6 @@
- Staff List
-
+
- + Email diff --git a/UI/src/app/staff/staff.edit.component.html b/UI/src/app/staff/staff.edit.component.html index 7fb3849..0059cbd 100644 --- a/UI/src/app/staff/staff.edit.component.html +++ b/UI/src/app/staff/staff.edit.component.html @@ -3,7 +3,7 @@ {{getEditText()}}
-
+
{ + this.router.navigate(['/import']); + + } + }, { label: 'Family tree', icon: 'pi pi-users', command: () => {