Commit ee917e20 by Mac Stephens

Initial server complete endpoint tested, working on client

parent 7bc2e580
emT3webServer/__history/
emT3webServer/bin/logs/
emT3webServer/Source/__history/
emT3webServer/Win32/Debug/
emT3webServer/bin/static/
object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate
Height = 358
Width = 519
object ucEmT3: TUniConnection
ProviderName = 'MySQL'
Database = 'emt3_web_db'
Username = 'root'
Server = '192.168.102.129'
Connected = True
LoginPrompt = False
Left = 75
Top = 65
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 178
Top = 66
end
object uqUsers: TUniQuery
Connection = ucEmT3
SQL.Strings = (
'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME')
OnCalcFields = uqUsersCalcFields
Left = 304
Top = 70
object uqUsersUSER_ID: TIntegerField
FieldName = 'USER_ID'
Required = True
end
object uqUsersNAME: TStringField
FieldName = 'NAME'
Size = 40
end
object uqUsersSTATUS: TStringField
FieldName = 'STATUS'
Size = 7
end
object uqUsersREPRESENTATIVE: TStringField
FieldKind = fkCalculated
FieldName = 'REPRESENTATIVE'
Calculated = True
end
end
object uqProjectTasks: TUniQuery
Connection = ucEmT3
SQL.Strings = (
'SELECT *'
'FROM task_items'
'WHERE PROJECT_ID = :PROJECT_ID'
'ORDER BY TASK_ID, TASK_ITEM_ID;')
Active = True
Left = 308
Top = 142
ParamData = <
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end>
object uqProjectTasksTASK_ITEM_ID: TStringField
FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end
object uqProjectTasksTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqProjectTasksPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Required = True
Size = 7
end
object uqProjectTasksAPPLICATION: TStringField
FieldName = 'APPLICATION'
Size = 255
end
object uqProjectTasksAPP_VERSION: TStringField
FieldName = 'APP_VERSION'
Size = 50
end
object uqProjectTasksTASK_DATE: TDateField
FieldName = 'TASK_DATE'
end
object uqProjectTasksREPORTED_BY: TStringField
FieldName = 'REPORTED_BY'
Size = 50
end
object uqProjectTasksASSIGNED_TO: TStringField
FieldName = 'ASSIGNED_TO'
Size = 50
end
object uqProjectTasksSTATUS: TStringField
FieldName = 'STATUS'
Size = 100
end
object uqProjectTasksSTATUS_DATE: TDateField
FieldName = 'STATUS_DATE'
end
object uqProjectTasksFIXED_VERSION: TStringField
FieldName = 'FIXED_VERSION'
Size = 50
end
object uqProjectTasksFORM_SECTION: TStringField
FieldName = 'FORM_SECTION'
Size = 255
end
object uqProjectTasksISSUE: TStringField
FieldName = 'ISSUE'
Size = 1000
end
object uqProjectTasksNOTES: TStringField
FieldName = 'NOTES'
Size = 1000
end
end
end
unit Api.Database;
interface
uses
System.SysUtils, System.Classes, Data.DB, MemDS, DBAccess, Uni, UniProvider,
PostgreSQLUniProvider, System.Variants, System.Generics.Collections, System.IniFiles,
Common.Logging, Vcl.Forms, MySQLUniProvider;
type
TApiDatabase = class(TDataModule)
ucEmT3: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider;
uqUsers: TUniQuery;
uqUsersUSER_ID: TIntegerField;
uqUsersNAME: TStringField;
uqUsersSTATUS: TStringField;
uqUsersREPRESENTATIVE: TStringField;
uqProjectTasks: TUniQuery;
uqProjectTasksTASK_ITEM_ID: TStringField;
uqProjectTasksTASK_ID: TStringField;
uqProjectTasksPROJECT_ID: TStringField;
uqProjectTasksAPPLICATION: TStringField;
uqProjectTasksAPP_VERSION: TStringField;
uqProjectTasksTASK_DATE: TDateField;
uqProjectTasksREPORTED_BY: TStringField;
uqProjectTasksASSIGNED_TO: TStringField;
uqProjectTasksSTATUS: TStringField;
uqProjectTasksSTATUS_DATE: TDateField;
uqProjectTasksFIXED_VERSION: TStringField;
uqProjectTasksFORM_SECTION: TStringField;
uqProjectTasksISSUE: TStringField;
uqProjectTasksNOTES: TStringField;
procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet);
private
{ Private declarations }
public
{ Public declarations }
end;
var
ApiDatabase: TApiDatabase;
implementation
uses
uLibrary;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TApiDatabase.DataModuleCreate(Sender: TObject);
begin
Logger.Log( 1, 'TApiDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucEmT3, 'emT3webServer.ini' );
try
ucEmT3.Connect;
except
on E: Exception do
begin
Logger.Log(2, '--TApiDatabase.DataModuleCreate -Error connecting to database: ' + E.Message);
end;
end;
end;
procedure TApiDatabase.uqUsersCalcFields(DataSet: TDataSet);
begin
uqUsersREPRESENTATIVE.AsString := uqUsersNAME.AsString + '(' + uqUsersSTATUS.AsString + ')';
end;
end.
object ApiServerModule: TApiServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 86
Top = 30
end
object XDataServer: TXDataServer
Dispatcher = SparkleHttpSysDispatcher
ModelName = 'Api'
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerOptions.AuthMode = Jwt
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 89
Top = 112
object XDataServerLogging: TSparkleGenericMiddleware
OnMiddlewareCreate = XDataServerLoggingMiddlewareCreate
end
object XDataServerCORS: TSparkleCorsMiddleware
end
object XDataServerCompress: TSparkleCompressMiddleware
end
object XDataServerJWT: TSparkleJwtMiddleware
OnGetSecret = XDataServerJWTGetSecret
end
end
end
// Server Module for the API part of the project.
unit Api.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Aurelius.Drivers.SQLite,
Aurelius.Comp.Connection,
Aurelius.Drivers.Interfaces,
XData.Aurelius.ConnectionPool, XData.Server.Module, Sparkle.Comp.Server,
XData.Comp.Server, XData.Comp.ConnectionPool, Sparkle.Comp.HttpSysDispatcher,
Sparkle.Comp.JwtMiddleware, Sparkle.Middleware.Jwt, Aurelius.Criteria.Linq,
Sparkle.HttpServer.Module, Sparkle.HttpServer.Context,
Sparkle.Comp.CompressMiddleware, Sparkle.Comp.CorsMiddleware,
Sparkle.Comp.GenericMiddleware, Aurelius.Drivers.UniDac, UniProvider,
Data.DB, DBAccess, Uni;
type
TApiServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
XDataServer: TXDataServer;
XDataServerLogging: TSparkleGenericMiddleware;
XDataServerCORS: TSparkleCorsMiddleware;
XDataServerCompress: TSparkleCompressMiddleware;
XDataServerJWT: TSparkleJwtMiddleware;
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
procedure XDataServerJWTGetSecret(Sender: TObject; var Secret: string);
private
{ Private declarations }
public
{ Public declarations }
procedure StartApiServer(ABaseUrl: string; AModelName: string);
end;
const
SERVER_PATH_SEGMENT = 'api';
var
ApiServerModule: TApiServerModule;
implementation
uses
Sparkle.HttpServer.Request,
Sparkle.Middleware.Cors,
Sparkle.Middleware.Compress,
XData.OpenApi.Service,
XData.Sys.Exceptions,
Common.Logging,
Common.Middleware.Logging,
Common.Config, Vcl.Forms, IniFiles;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TApiServerModule }
function IsAdmin(Request: THttpServerRequest): Boolean;
var
User: IUserIdentity;
begin
User := Request.User;
Result := (User <> nil) and User.Claims.Exists('admin') and User.Claims['admin'].AsBoolean;
end;
procedure TApiServerModule.StartApiServer(ABaseUrl: string; AModelName: string);
var
Url: string;
begin
RegisterOpenApiService;
Url := ABaseUrl;
if not Url.EndsWith('/') then
Url := Url + '/';
Url := Url + SERVER_PATH_SEGMENT;
XDataServer.BaseUrl := Url;
XDataServer.ModelName := AModelName;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('Api server module listening at "%s"', [XDataServer.BaseUrl]));
end;
procedure TApiServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
begin
Middleware := TLoggingMiddleware.Create(Logger);
end;
procedure TApiServerModule.XDataServerJWTGetSecret(Sender: TObject;
var Secret: string);
begin
Secret := serverConfig.jwtTokenSecret;
end;
end.
unit Api.Service;
interface
uses
XData.Service.Common,
System.Generics.Collections,
System.Classes,
System.SysUtils;
const
API_MODEL = 'Api';
type
TTaskItem = class
public
taskItemId: string;
taskId: string;
projectId: string;
application: string;
version: string;
taskDate: TDateTime;
reportedBy: string;
assignedTo: string;
status: string;
statusDate: TDateTime;
fixedVersion: string;
formSection: string;
issue: string;
notes: string;
end;
TTask = class
public
taskId: string;
projectId: string;
items: TList<TTaskItem>;
constructor Create;
destructor Destroy; override;
end;
TTasksList = class
public
count: integer;
data: TList<TTask>;
constructor Create;
destructor Destroy; override;
end;
[ServiceContract]
IApiService = interface(IInvokable)
['{A8CBF627-BB64-4A53-821C-9A84C2752248}']
end;
[ServiceContract]
ITasksService = interface(IInvokable)
['{D5E1B7A2-6A9D-4D9A-9F7F-9A3E8A9E3B11}']
[HttpGet] function GetProjectTasks(projectId: string): TTasksList;
end;
implementation
constructor TTask.Create;
begin
inherited;
items := TList<TTaskItem>.Create;
end;
destructor TTask.Destroy;
begin
items.Free;
inherited;
end;
constructor TTasksList.Create;
begin
inherited;
data := TList<TTask>.Create;
end;
destructor TTasksList.Destroy;
begin
data.Free;
inherited;
end;
initialization
RegisterServiceType(TypeInfo(ITasksService));
end.
unit Api.ServiceImpl;
interface
uses
XData.Server.Module, System.Generics.Collections,
XData.Service.Common,
Api.Service, Api.Database;
type
[ServiceImplementation]
TApiService = class(TInterfacedObject, IApiService)
private
ApiDB: TApiDatabase;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
function GetProjectTasks(projectId: string): TTasksList;
end;
implementation
procedure TApiService.AfterConstruction;
begin
inherited;
ApiDB := TApiDatabase.Create(nil);
end;
procedure TApiService.BeforeDestruction;
begin
ApiDB.Free;
inherited;
end;
function TApiService.GetProjectTasks(projectId: string): TTasksList;
var
taskMap: TDictionary<string, TTask>;
task: TTask;
item: TTaskItem;
taskId: string;
begin
Result := TTasksList.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
taskMap := TDictionary<string, TTask>.Create;
try
ApiDB.uqProjectTasks.Close;
ApiDB.uqProjectTasks.ParamByName('PROJECT_ID').AsString := projectId;
ApiDB.uqProjectTasks.Open;
while not ApiDB.uqProjectTasks.Eof do
begin
taskId := ApiDB.uqProjectTasksTASK_ID.AsString;
if not taskMap.TryGetValue(taskId, task) then
begin
task := TTask.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(task);
task.taskId := taskId;
task.projectId := ApiDB.uqProjectTasksPROJECT_ID.AsString;
taskMap.Add(taskId, task);
Result.data.Add(task);
end;
item := TTaskItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.taskItemId := ApiDB.uqProjectTasksTASK_ITEM_ID.AsString;
item.taskId := ApiDB.uqProjectTasksTASK_ID.AsString;
item.projectId := ApiDB.uqProjectTasksPROJECT_ID.AsString;
item.application := ApiDB.uqProjectTasksAPPLICATION.AsString;
item.version := ApiDB.uqProjectTasksAPP_VERSION.AsString;
if ApiDB.uqProjectTasksTASK_DATE.IsNull then
item.taskDate := 0
else
item.taskDate := ApiDB.uqProjectTasksTASK_DATE.AsDateTime;
item.reportedBy := ApiDB.uqProjectTasksREPORTED_BY.AsString;
item.assignedTo := ApiDB.uqProjectTasksASSIGNED_TO.AsString;
item.status := ApiDB.uqProjectTasksSTATUS.AsString;
if ApiDB.uqProjectTasksSTATUS_DATE.IsNull then
item.statusDate := 0
else
item.statusDate := ApiDB.uqProjectTasksSTATUS_DATE.AsDateTime;
item.fixedVersion := ApiDB.uqProjectTasksFIXED_VERSION.AsString;
item.formSection := ApiDB.uqProjectTasksFORM_SECTION.AsString;
item.issue := ApiDB.uqProjectTasksISSUE.AsString;
item.notes := ApiDB.uqProjectTasksNOTES.AsString;
task.items.Add(item);
ApiDB.uqProjectTasks.Next;
end;
Result.count := Result.data.Count;
finally
taskMap.Free;
end;
end;
initialization
RegisterServiceType(TApiService);
end.
object AppServerModule: TAppServerModule
Height = 173
Width = 218
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 88
Top = 16
end
object SparkleStaticServer: TSparkleStaticServer
BaseUrl = 'http://localhost:2004/emsys/envoy/app/'
Dispatcher = SparkleHttpSysDispatcher
Left = 88
Top = 88
object SparkleStaticServerCompress: TSparkleCompressMiddleware
end
object SparkleStaticServerLogging: TSparkleLoggingMiddleware
FormatString = ':method :url :statuscode - :responsetime ms'
ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s'
ErrorResponseOptions.ErrorCode = 'ServerError'
ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s'
end
end
end
unit App.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Sparkle.Comp.Server, Sparkle.Comp.StaticServer, Sparkle.Comp.HttpSysDispatcher,
Sparkle.Module.Static, Sparkle.Comp.CompressMiddleware,
Sparkle.HttpServer.Module, Sparkle.HttpServer.Context,
Sparkle.Comp.LoggingMiddleware;
type
TAppServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
SparkleStaticServer: TSparkleStaticServer;
SparkleStaticServerCompress: TSparkleCompressMiddleware;
SparkleStaticServerLogging: TSparkleLoggingMiddleware;
private
{ Private declarations }
public
{ Public declarations }
procedure StartAppServer(ABaseUrl: string);
end;
const
SERVER_PATH_SEGMENT = 'app';
var
AppServerModule: TAppServerModule;
implementation
uses
Sparkle.Middleware.Compress,
Common.Logging,
Common.Config;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TAppServerModule }
procedure TAppServerModule.StartAppServer(ABaseUrl: string);
var
url: string;
begin
url := ABaseUrl;
if not url.EndsWith('/') then
url := url + '/';
url := url + SERVER_PATH_SEGMENT;
SparkleStaticServer.BaseUrl := url;
SparkleStaticServer.RootDir := serverConfig.webAppFolder;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('App server module listening at "%s", rootDir: %s', [url, serverConfig.webAppFolder]));
end;
end.
object AuthDatabase: TAuthDatabase
OnCreate = DataModuleCreate
OnDestroy = DataModuleDestroy
Height = 249
Width = 433
object uq: TUniQuery
Connection = ucKG
SQL.Strings = (
'select * from users')
FetchRows = 100
Left = 162
Top = 45
end
object uqMisc: TUniQuery
FetchRows = 100
Left = 249
Top = 45
end
object ucKG: TUniConnection
ProviderName = 'MySQL'
LoginPrompt = False
Left = 67
Top = 131
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 230
Top = 140
end
end
// Auth Database to verify logins
unit Auth.Database;
interface
uses
System.SysUtils, System.Classes, IniFiles, Vcl.Forms, MemDS,
Data.DB, DBAccess, Uni, UniProvider, PostgreSQLUniProvider, MySQLUniProvider;
type
TAuthDatabase = class(TDataModule)
uq: TUniQuery;
uqMisc: TUniQuery;
ucKG: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
AuthDatabase: TAuthDatabase;
implementation
uses
System.JSON,
Common.Config,
Common.Logging,
uLibrary;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TAuthDatabase.DataModuleCreate(Sender: TObject);
begin
Logger.Log( 1, 'TAuthDatabase.DataModuleCreate' );
LoadDatabaseSettings( ucKG, 'kgOrdersServer.ini' );
try
ucKG.Connect;
except
on E: Exception do
begin
Logger.Log(2, '--TAuthDatabase.DataModuleCreate -Error connecting to database: ' + E.Message);
end;
end;
end;
procedure TAuthDatabase.DataModuleDestroy(Sender: TObject);
begin
ucKG.Connected := false;
end;
end.
object AuthServerModule: TAuthServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 88
Top = 16
end
object XDataServer: TXDataServer
Dispatcher = SparkleHttpSysDispatcher
ModelName = 'Auth'
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 91
Top = 92
object XDataServerLogging: TSparkleGenericMiddleware
OnMiddlewareCreate = XDataServerLoggingMiddlewareCreate
end
object XDataServerCORS: TSparkleCorsMiddleware
end
object XDataServerCompress: TSparkleCompressMiddleware
end
end
end
// Auth Server Module for the project
unit Auth.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Aurelius.Comp.Connection,
Aurelius.Drivers.Interfaces,
XData.Aurelius.ConnectionPool, XData.Server.Module, XData.Comp.ConnectionPool,
Sparkle.Comp.Server, Sparkle.Comp.JwtMiddleware, XData.Comp.Server,
Sparkle.Comp.HttpSysDispatcher, Sparkle.Comp.CompressMiddleware,
Sparkle.Comp.CorsMiddleware, Sparkle.HttpServer.Module,
Sparkle.HttpServer.Context, Sparkle.Comp.GenericMiddleware;
type
TAuthServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
XDataServer: TXDataServer;
XDataServerLogging: TSparkleGenericMiddleware;
XDataServerCORS: TSparkleCorsMiddleware;
XDataServerCompress: TSparkleCompressMiddleware;
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
private
{ Private declarations }
public
{ Public declarations }
procedure StartAuthServer(ABaseUrl: string; AModelName: string);
end;
const
SERVER_PATH_SEGMENT = 'auth';
var
AuthServerModule: TAuthServerModule;
implementation
uses
Sparkle.Middleware.Cors,
Sparkle.Middleware.Compress,
XData.OpenApi.Service,
Common.Logging,
Common.Middleware.Logging;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TAuthServerModule }
procedure TAuthServerModule.StartAuthServer(ABaseUrl: string;
AModelName: string);
var
Url: string;
begin
RegisterOpenApiService;
Url := ABaseUrl;
if not Url.EndsWith('/') then
Url := Url + '/';
Url := Url + SERVER_PATH_SEGMENT;
XDataServer.BaseUrl := Url;
XDataServer.ModelName := AModelName;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('Auth server module listening at "%s"', [XDataServer.BaseUrl]));
end;
procedure TAuthServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
begin
Middleware := TLoggingMiddleware.Create(Logger);
end;
end.
// Auth Interface service declaration
unit Auth.Service;
interface
uses
XData.Service.Common,
Aurelius.Mapping.Attributes,
System.Generics.Collections,
System.JSON;
const
AUTH_MODEL = 'Auth';
type
[ServiceContract, Model(AUTH_MODEL)]
IAuthService = interface(IInvokable)
['{9CFD59B2-A832-4F82-82BB-9A25FC93F305}']
function Login(const user, password: string): string;
function VerifyVersion(ClientVersion: string): TJSONObject;
end;
implementation
end.
// Implementation of Auth Serice that will eventually retrieve login information
// from the auth database.
unit Auth.ServiceImpl;
interface
uses
XData.Service.Common,
XData.Server.Module,
Auth.Service,
Auth.Database,
Uni, Data.DB, System.Hash, System.IniFiles, System.JSON;
type
[ServiceImplementation]
TAuthService = class(TInterfacedObject, IAuthService)
strict private
authDB: TAuthDatabase;
function GetQuery: TUniQuery;
private
userName: string;
userFullName: string;
userId: string;
userPerspectiveID: string;
userQBID: string;
userAccessType: string;
userEmail: string;
userStatus: string;
qbEnabled: boolean;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
function CheckUser(const user, password: string): Integer;
public
function Login(const user, password: string): string;
function VerifyVersion(ClientVersion: string): TJSONObject;
end;
implementation
uses
System.SysUtils,
System.DateUtils,
System.Generics.Collections,
Bcl.JOSE.Core.Builder,
Bcl.JOSE.Core.JWT,
Aurelius.Global.Utils,
XData.Sys.Exceptions,
Common.Logging,
Common.Config,
uLibrary;
{ TLoginService }
procedure TAuthService.AfterConstruction;
begin
inherited;
try
authDB := TAuthDatabase.Create(nil);
except
on E: Exception do
begin
Logger.Log(1, 'Error when creating the Auth database: ' + E.Message);
raise EXDataHttpException.Create(500, 'Unable to create Auth database: A KGOrders Server Error has occured!');
end;
end;
end;
procedure TAuthService.BeforeDestruction;
begin
authDB.Free;
inherited;
end;
function TAuthService.GetQuery: TUniQuery;
begin
Result := authDB.uq;
end;
function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject;
var
iniFile: TIniFile;
webClientVersion: string;
begin
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
Result.AddPair('webClientVersion', webClientVersion);
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false);
if webClientVersion = '' then
begin
Result.AddPair('error', 'webClientVersion is not configured.');
Exit;
end;
if clientVersion <> webClientVersion then
begin
Result.AddPair('error',
'Your browser is running an old version of the app.' + sLineBreak +
'Please click below to reload.');
end;
finally
iniFile.Free;
end;
end;
function TAuthService.Login(const user, password: string): string;
var
userState: Integer;
iniFile: TIniFile;
JWT: TJWT;
begin
Logger.Log(3, Format( 'AuthService.Login - User: "%s"', [User]));
userState := CheckUser( user, password );
try
userState := CheckUser(user, password);
except
on E: Exception do
begin
Logger.Log(1, 'Login failed due to database error: ' + E.Message);
raise EXDataHttpException.Create(500, 'Login failed: Unable to connect to the database.');
end;
end;
if userState = 0 then
begin
raise EXDataHttpUnauthorized.Create('Invalid username or password');
logger.Log(2, 'Login Error: Invalid username or password');
end
else if userState = 1 then
begin
raise EXDataHttpUnauthorized.Create('User does not exist!');
logger.Log(2, 'Login Error: User does not exist!');
end
else if userState = 2 then
begin
raise EXDataHttpUnauthorized.Create('User not active!');
logger.Log(2, 'Login Error: User not active!');
end;
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
qbEnabled := iniFile.ReadBool('Quickbooks', 'Enabled', false);
finally
iniFile.Free;
end;
JWT := TJWT.Create;
try
JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
JWT.Claims.IssuedAt := Now;
JWT.Claims.Expiration := IncHour(Now, 24);
JWT.Claims.SetClaimOfType<string>('user_name', userName);
JWT.Claims.SetClaimOfType<string>('user_fullname', userFullName);
JWT.Claims.SetClaimOfType<string>('user_id', userId);
JWT.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveID);
JWT.Claims.SetClaimOfType<string>('user_status', userStatus);
JWT.Claims.SetClaimOfType<string>('user_email', userEmail);
JWT.Claims.SetClaimOfType<string>('user_qb_id', userQBID);
JWT.Claims.SetClaimOfType<string>('user_access_type', userAccessType);
JWT.Claims.SetClaimOfType<boolean>('qb_enabled', qbEnabled);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, JWT);
finally
JWT.Free;
end;
end;
function TAuthService.CheckUser(const user, password: string): Integer;
var
userStr: string;
SQL: string;
name: string;
checkString: string;
begin
Result := 0;
Logger.Log(1, Format('AuthService.CheckUser - User: "%s"', [user]) );
SQL := 'select * from users where USER_NAME = ' + QuotedStr(user);
DoQuery(authDB.uq, SQL);
if authDB.uq.IsEmpty then
begin
Result := 1; //user does not exist, replace this with 0 for more security
end
else if ( authDB.uq.FieldByName('STATUS').AsString <> 'ACTIVE' ) then
Result := 2 // user is not active
else
begin
name := authDB.uq.FieldByName('NAME').AsString;
checkString := THashSHA2.GetHashString(name + password, THashSHA2.TSHA2Version.SHA512).ToUpper;
if authDB.uq.FieldByName('PASSWORD').AsString = checkString then
begin
userName := user;
userFullName:= authDB.uq.FieldByName('NAME').AsString;;
userId := authDB.uq.FieldByName('USER_ID').AsString;
userStatus := authDB.uq.FieldByName('STATUS').AsString;
userPerspectiveID := authDB.uq.FieldByName('PERSPECTIVE_ID').AsString;
userEmail := authDB.uq.FieldByName('EMAIL').AsString;
userQBID := authDB.uq.FieldByName('QB_ID').AsString;
userAccessType := authDB.uq.FieldByName('ACCESS_TYPE').AsString;
Logger.Log(1, Format('AuthDB.SetLoginAuditEntry: "%s"', [user]) );
Result := 3; // Succcess
end
else
Result := 0; // invalid password
end;
end;
initialization
RegisterServiceType(TAuthService);
end.
unit Common.Config;
interface
const
defaultServerUrl = 'http://localhost:2004/kgOrders/';
type
TServerConfig = class
private
Furl: string;
FjwtTokenSecret: string;
FadminPassword: string;
FwebAppFolder: string;
FreportsFolder: string;
public
property url: string read Furl write Furl;
property jwtTokenSecret: string read FjwtTokenSecret write FjwtTokenSecret;
property adminPassword: string read FadminPassword write FadminPassword;
property webAppFolder: string read FwebAppFolder write FwebAppFolder;
property reportsFolder: string read FreportsFolder write FreportsFolder;
constructor Create;
end;
procedure LoadServerConfig;
var
serverConfig: TServerConfig;
implementation
uses
Bcl.Json, System.SysUtils, System.IOUtils, Common.Logging, System.StrUtils;
procedure LoadServerConfig;
var
configFile: string;
localConfig: TServerConfig;
begin
Logger.Log(1, '--LoadServerConfig - start');
configFile := TPath.ChangeExtension(ParamStr(0), '.json');
Logger.Log(1, '-- Config file: ' + ConfigFile);
if TFile.Exists(configFile) then
begin
Logger.Log(1, '-- Config file found.');
localConfig := TJson.Deserialize<TServerConfig>(TFile.ReadAllText(configFile));
Logger.Log(1, '-- localConfig loaded from config file');
serverConfig.Free;
Logger.Log(1, '-- serverConfig.Free - called');
serverConfig := localConfig;
Logger.Log(1, '-- serverConfig := localConfig - called');
end
else
begin
Logger.Log(1, '-- Config file not found.');
end;
Logger.Log(1, '--- Server Config Values ---');
Logger.Log(1, '-- url: ' + serverConfig.url + IfThen(serverConfig.url = defaultServerUrl, ' [default]', ' [from config]'));
Logger.Log(1, '-- adminPassword: ' + serverConfig.adminPassword + IfThen(serverConfig.adminPassword = 'whatisthisusedfor', ' [default]', ' [from config]'));
Logger.Log(1, '-- jwtTokenSecret: ' + serverConfig.jwtTokenSecret + IfThen(serverConfig.jwtTokenSecret = 'super_secret0123super_secret4567', ' [default]', ' [from config]'));
Logger.Log(1, '-- webAppFolder: ' + serverConfig.webAppFolder + IfThen(serverConfig.webAppFolder = 'static', ' [default]', ' [from config]'));
Logger.Log(1, '-- serverConfig.reportsFolder: ' + serverConfig.reportsFolder);
Logger.Log(1, '--LoadServerConfig - end');
end;
{ TServerConfig }
constructor TServerConfig.Create;
var
ServerConfigStr: string;
begin
Logger.Log(1, '--TServerConfig.Create - start');
url := defaultServerUrl;
adminPassword := 'whatisthisusedfor';
jwtTokenSecret := 'super_secret0123super_secret4567';
webAppFolder := 'static';
reportsFolder := 'static/';
ServerConfigStr := Bcl.Json.TJson.Serialize(ServerConfig);
Logger.Log(1, '--ServerConfigSerialize: ' + ServerConfigStr);
Logger.Log(1, '--TServerConfig.Create - end');
end;
end.
unit Common.Logging;
interface
uses
Generics.Collections;
type
ILog = interface;
ILogAppender = interface;
ILogger = interface
['{4D667DD2-BE11-496B-B92A-C47E03520BD6}']
procedure Log(logLevel: integer; Msg: string); overload;
procedure Log(logLevel: integer; Log: ILog); overload;
procedure AddAppender(ALogAppender: ILogAppender);
function Appenders: TArray<ILogAppender>;
end;
ILogAppender = interface
['{A3B7D6FB-C75F-4BEF-8797-907B6FDAD5D2}']
procedure Send(logLevel: integer; Log: ILog);
end;
ILog = interface
['{8E9C6580-C099-47C0-8B1B-6D7A28EC4FA3}']
function GetMessage: string;
end;
TLogger = class( TInterfacedObject, ILogger )
strict private
FAppenders: TList<ILogAppender>;
public
constructor Create; overload;
constructor Create(ALogger: ILogger); overload;
destructor Destroy; override;
procedure Log(logLevel: integer; Msg: string); overload;
procedure Log(logLevel: integer; Log: ILog); overload;
procedure AddAppender(ALogAppender: ILogAppender);
function Appenders: TArray<ILogAppender>;
end;
TLogMessage = class( TInterfacedObject, ILog )
private
FMsg: string;
public
constructor Create(AMsg: string);
function GetMessage: string;
end;
function Logger: ILogger;
implementation
var
_Logger: ILogger;
function Logger: ILogger;
begin
Result := _Logger;
end;
{ TLogMessage }
constructor TLogMessage.Create(AMsg: string);
begin
FMsg := AMsg;
end;
function TLogMessage.GetMessage: string;
begin
Result := FMsg;
end;
{ TLogger }
procedure TLogger.AddAppender(ALogAppender: ILogAppender);
begin
FAppenders.Add(ALogAppender);
end;
function TLogger.Appenders: TArray<ILogAppender>;
var
I: integer;
begin
SetLength(Result, FAppenders.Count);
for I := 0 to FAppenders.Count - 1 do
Result[I] := FAppenders[I];
end;
constructor TLogger.Create(ALogger: ILogger);
var
Appender: ILogAppender;
begin
FAppenders := TList<ILogAppender>.Create;
if ALogger <> nil then
for Appender in ALogger.Appenders do
AddAppender(Appender);
end;
constructor TLogger.Create;
begin
Create(nil);
end;
destructor TLogger.Destroy;
begin
FAppenders.Free;
inherited;
end;
procedure TLogger.Log(logLevel: integer; Log: ILog);
var
Appender: ILogAppender;
begin
for Appender in FAppenders do
Appender.Send(logLevel, Log);
end;
procedure TLogger.Log(logLevel: integer; Msg: string);
begin
Log(logLevel, TLogMessage.Create(Msg));
end;
initialization
_Logger := TLogger.Create;
end.
unit Common.Middleware.Logging;
interface
uses
System.Classes, System.SysUtils,
Sparkle.HttpServer.Module,
Sparkle.HttpServer.Context,
Sparkle.Http.Headers,
Common.Logging;
type
TLoggingMiddleware = class(THttpServerMiddleware, IHttpServerMiddleware)
private
FLogger: ILogger;
function GetNewHttpRequestLog(Request: THttpServerRequest): ILog;
protected
procedure ProcessRequest(Context: THttpServerContext; Next: THttpServerProc); override;
public
constructor Create(ALogger: ILogger);
end;
THttpRequestLog = class( TInterfacedObject, ILog )
strict private
FMethod: string;
FUriPath: string;
FUriQuery: string;
FProtocol: string;
FRemoteIp: string;
FHeaders: string;
FContent: string;
FContentLength: Int64;
public
constructor Create(AMethod: string; AUriPath: string; AUriQuery: string;
AProtocol: string; ARemoteIp: string; AHeaders: string; AContent: string;
AContentLength: Int64);
function GetMessage: string;
end;
// THttpResponseLog = class( TInterfacedObject, ILog )
// strict private
// FMethod: string;
// FUriPath: string;
// FUriQuery: string;
// FProtocol: string;
// FRemoteIp: string;
// FHeaders: string;
// FContent: string;
// FContentLength: Int64;
// public
// constructor Create(AMethod: string; AUriPath: string; AUriQuery: string;
// AProtocol: string; ARemoteIp: string; AHeaders: string; AContent: string;
// AContentLength: Int64);
// function GetMessage: string;
// end;
implementation
{ TLoggingMiddleware }
constructor TLoggingMiddleware.Create(ALogger: ILogger);
begin
FLogger := TLogger.Create(ALogger);
end;
function TLoggingMiddleware.GetNewHttpRequestLog(
Request: THttpServerRequest): ILog;
var
Msg: TStrings;
Header: THttpHeaderInfo;
StringStream: TStringStream;
Headers, Content: string;
begin
Result := nil;
Msg := TStringList.Create;
try
if Length(Request.Headers.AllHeaders.ToArray) = 0 then
Headers := ''
else
begin
for Header in Request.Headers.AllHeaders do
Msg.Add(Header.Name + ': ' + Header.Value);
Headers := Msg.Text;
end;
finally
Msg.Free;
end;
StringStream := TStringStream.Create(Request.Content);
try
Content := StringStream.DataString
finally
StringStream.Free;
end;
Result := THttpRequestLog.Create(
Request.Method,
Request.Uri.Path,
Request.Uri.Query,
Request.Protocol,
Request.RemoteIp,
Headers,
Content,
Request.ContentLength
);
end;
procedure TLoggingMiddleware.ProcessRequest(Context: THttpServerContext;
Next: THttpServerProc);
var
RequestLogMessage: string;
begin
Context.Response.OnHeaders(
procedure(Resp: THttpServerResponse)
begin
if (Resp.StatusCode >= 400) and (Resp.StatusCode <= 499) then
FLogger.Log(5, Format('%d %s on %s', [Resp.StatusCode, Resp.StatusReason, RequestLogMessage]));
end
);
RequestLogMessage := GetNewHttpRequestLog(Context.Request).GetMessage;
FLogger.Log(5, RequestLogMessage);
Next(Context);
end;
{ THttpRequestLog }
constructor THttpRequestLog.Create(AMethod, AUriPath, AUriQuery,
AProtocol, ARemoteIp, AHeaders, AContent: string; AContentLength: Int64);
begin
FMethod := AMethod;
FUriPath := AUriPath;
FUriQuery := AUriQuery;
FProtocol := AProtocol;
FRemoteIp := ARemoteIp;
FHeaders := AHeaders;
FContent := AContent;
FContentLength := AContentLength;
end;
function THttpRequestLog.GetMessage: string;
var
Msg: TStrings;
begin
Result := '';
Msg := TStringList.Create;
try
Msg.Add(Format('%s %s %s',
[
FMethod,
FUriPath + FUriQuery,
FProtocol,
FRemoteIp
]));
// if Not FHeaders.IsEmpty then
// Msg.Add(FHeaders);
// if (Not FContent.IsEmpty) then
// Msg.Add(FContent);
Result := Trim(Msg.Text);
finally
Msg.Free;
end;
end;
end.
object FMain: TFMain
Left = 0
Top = 0
Caption = 'emT3 Web Server'
ClientHeight = 597
ClientWidth = 773
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OnClose = FormClose
DesignSize = (
773
597)
TextHeight = 13
object memoInfo: TMemo
Left = 12
Top = 44
Width = 753
Height = 549
Anchors = [akLeft, akTop, akRight, akBottom]
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Lucida Console'
Font.Style = []
ParentFont = False
ReadOnly = True
ScrollBars = ssVertical
TabOrder = 0
end
object btnApiSwaggerUI: TButton
Left = 297
Top = 8
Width = 100
Height = 25
Caption = 'Api SwaggerUI'
TabOrder = 1
OnClick = btnApiSwaggerUIClick
end
object btnExit: TButton
Left = 671
Top = 8
Width = 75
Height = 25
Caption = 'Exit'
TabOrder = 2
OnClick = btnExitClick
end
object btnAuthSwaggerUI: TButton
Left = 169
Top = 8
Width = 100
Height = 25
Caption = 'Auth SwaggerUI'
TabOrder = 3
OnClick = btnAuthSwaggerUIClick
end
object initTimer: TTimer
OnTimer = initTimerTimer
Left = 60
Top = 398
end
object ExeInfo1: TExeInfo
Version = '1.6.1.1'
Left = 156
Top = 406
end
end
unit Main;
//Authors:
//Elias Sarraf
//Mac Stephens
//Cameron Hayes
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Winapi.ShellApi,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles,
Auth.Service, Auth.Server.Module, Api.Service, Api.Server.Module, App.Server.Module,
ExeInfo;
type
TFMain = class(TForm)
memoInfo: TMemo;
btnApiSwaggerUI: TButton;
btnExit: TButton;
initTimer: TTimer;
btnAuthSwaggerUI: TButton;
ExeInfo1: TExeInfo;
procedure btnApiSwaggerUIClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure ContactFormData(AText: String);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure initTimerTimer(Sender: TObject);
procedure btnAuthSwaggerUIClick(Sender: TObject);
strict private
procedure StartServers;
procedure UpdateGUI;
end;
var
FMain: TFMain;
implementation
uses
Common.Logging,
Common.Config,
Sparkle.Utils,
Api.Database;
{$R *.dfm}
{ TMainForm }
procedure TFMain.ContactFormData(AText: String);
begin
if memoInfo.CanFocus then
TThread.Queue(nil, procedure
begin
memoInfo.Lines.Add(AText);
end)
else
TThread.Synchronize(nil, procedure
begin
memoInfo.Lines.Add(AText);
end);
end;
procedure TFMain.btnExitClick(Sender: TObject);
begin
Close;
end;
procedure TFMain.btnAuthSwaggerUIClick(Sender: TObject);
begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end;
procedure TFMain.btnApiSwaggerUIClick(Sender: TObject);
begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(ApiServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end;
procedure TFMain.initTimerTimer(Sender: TObject);
begin
initTimer.Enabled := False;
Caption := Caption + ' ver ' + ExeInfo1.FileVersion;
ServerConfig := TServerConfig.Create;
LoadServerConfig;
StartServers;
end;
procedure TFMain.StartServers;
var
iniFile: TIniFile;
iniStr: string;
begin
Logger.Log( 1, '*******************************************************' );
Logger.Log( 1, '* emT3webServer *' );
Logger.Log(1, Format(' Version: %s ', [FMain.ExeInfo1.FileVersion]));
Logger.Log( 1, '* Developed by EM Systems, Inc. *' );
Logger.Log( 1, '*******************************************************' );
Logger.Log( 1, '' );
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
try
Logger.Log( 1, 'iniFile: ' + ExtractFilePath(Application.ExeName) + 'emT3webServer.ini' );
Logger.Log( 1, '' );
Logger.Log(1, '--- Settings ---');
iniStr := iniFile.ReadString( 'Settings', 'MemoLogLevel', '' );
if iniStr.IsEmpty then
Logger.Log( 1, '--Settings->memoLogLevel: Entry not found - default: 3' )
else
Logger.Log( 1, '--Settings->memoLogLevel: ' + iniStr );
iniStr := iniFile.ReadString( 'Settings', 'FileLogLevel', '' );
if iniStr.IsEmpty then
Logger.Log( 1, '--Settings->fileLogLevel: Entry not found - default: 4' )
else
Logger.Log( 1, '--Settings->fileLogLevel: ' + iniStr );
Logger.Log( 1, '' );
iniStr := iniFile.ReadString( 'Settings', 'LogFileNum', '' );
if iniStr.IsEmpty then
Logger.Log( 1, '--Settings->LogFileNum: Entry not found' )
else
Logger.Log( 1, '--Settings->LogFileNum: ' + IntToStr(StrToInt(iniStr) - 1) );
Logger.Log(1, '--- Database ---');
iniStr := IniFile.ReadString( 'Database', 'Server', '' );
if iniStr.IsEmpty then
Logger.Log( 1, '--Database->Server: Entry not found' )
else
Logger.Log( 1, '--Database->Server: ' + iniStr );
iniStr := iniFile.ReadString('Database', 'Database', '');
if iniStr.IsEmpty then
Logger.Log( 1, '----Database->Database: Entry not found' )
else
Logger.Log( 1, '----Database->Database: ' + iniStr );
iniStr := iniFile.ReadString('Database', 'Username', '');
if iniStr.IsEmpty then
Logger.Log( 1, '----Database->Username: Entry not found' )
else
Logger.Log( 1, '----Database->Username: ' + iniStr );
iniStr := iniFile.ReadString('Database', 'Password', '');
if iniStr.IsEmpty then
Logger.Log( 1, '----Database->Password: Entry not found' )
else
Logger.Log( 1, '----Database->Password: xxxxxxxx' );
Logger.Log( 1, '' );
finally
IniFile.Free;
end;
AuthServerModule := TAuthServerModule.Create(Self);
AuthServerModule.StartAuthServer(serverConfig.url, AUTH_MODEL);
ApiServerModule := TApiServerModule.Create( Self );
ApiServerModule.StartApiServer(serverConfig.url, API_MODEL);
AppServerModule := TAppServerModule.Create(Self);
AppServerModule.StartAppServer( serverConfig.url );
UpdateGUI;
end;
procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ServerConfig.Free;
AuthServerModule.Free;
ApiServerModule.Free;
AppServerModule.Free;
end;
procedure TFMain.UpdateGUI;
begin
if AuthServerModule.SparkleHttpSysDispatcher.Active then
memoInfo.Lines.Add( 'AuthServer started at: ' + AuthServerModule.XDataServer.BaseUrl )
else
memoInfo.Lines.Add( 'AuthServer stopped' );
if ApiServerModule.SparkleHttpSysDispatcher.Active then
memoInfo.Lines.Add( 'ApiServer started at: ' + ApiServerModule.XDataServer.BaseUrl )
else
memoInfo.Lines.Add( 'ApiServer stopped' );
end;
end.
unit uLibrary;
interface
uses
System.Classes, Uni;
procedure LoadDatabaseSettings( uc: TUniConnection; iniFilename: string );
procedure DoQuery( uq: TUniQuery; sql: string );
implementation
uses
System.SysUtils,
System.IniFiles,
Vcl.Forms,
Data.DB;
procedure LoadDatabaseSettings( uc: TUniConnection; iniFilename: string );
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + iniFilename );
try
uc.Server := iniFile.ReadString('Database', 'Server', uc.Server);
uc.Database := iniFile.ReadString('Database', 'Database', uc.Database);
uc.Username := iniFile.ReadString('Database', 'Username', uc.Username);
uc.Password := iniFile.ReadString('Database', 'Password', uc.Password);
finally
iniFile.Free;
end;
end;
procedure DoQuery(uq: TUniQuery; sql: string);
begin
uq.Close;
uq.SQL.Text := sql;
uq.Open;
end;
end.
[Settings]
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.0.1
LogFileNum=175
[Database]
--Server=192.168.116.138
Server=192.168.102.129
--Server=192.168.75.133
--Server=192.168.159.10
Database=emt3_web_db
Username=root
Password=emsys01
--Password=emsys!012
{
"url": "http://localhost:2004/kgOrders/",
"jwtTokenSecret": "super_secret0123super_secret4567",
"adminPassword": "whatisthisusedfor",
"webAppFolder": "static",
"reportsFolder": ".\\static\\"
}
\ No newline at end of file
program emT3webServer;
uses
FastMM4,
System.SyncObjs,
System.SysUtils,
Vcl.StdCtrls,
IniFiles,
Vcl.Forms,
Api.Server.Module in 'Source\Api.Server.Module.pas' {ApiServerModule: TDataModule},
Main in 'Source\Main.pas' {FMain},
Common.Logging in 'Source\Common.Logging.pas',
Api.Database in 'Source\Api.Database.pas' {ApiDatabase: TDataModule},
Common.Middleware.Logging in 'Source\Common.Middleware.Logging.pas',
Common.Config in 'Source\Common.Config.pas',
Auth.Server.Module in 'Source\Auth.Server.Module.pas' {AuthServerModule: TDataModule},
Auth.Database in 'Source\Auth.Database.pas' {AuthDatabase: TDataModule},
uLibrary in 'Source\uLibrary.pas',
Auth.Service in 'Source\Auth.Service.pas',
Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas',
App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule},
Api.Service in 'Source\Api.Service.pas',
Api.ServiceImpl in 'Source\Api.ServiceImpl.pas';
type
TMemoLogAppender = class( TInterfacedObject, ILogAppender )
private
FLogLevel: Integer;
FLogMemo: TMemo;
FCriticalSection: TCriticalSection;
public
constructor Create(ALogLevel: Integer; ALogMemo: TMemo);
destructor Destroy; override;
procedure Send(logLevel: Integer; Log: ILog);
end;
TFileLogAppender = class( TInterfacedObject, ILogAppender )
private
FLogLevel: Integer;
FLogFile: string;
FCriticalSection: TCriticalSection;
public
constructor Create(ALogLevel: Integer; AFilename: string);
destructor Destroy; override;
procedure Send(logLevel: Integer; Log: ILog);
end;
{ TMemoLogAppender }
constructor TMemoLogAppender.Create(ALogLevel: Integer; ALogMemo: TMemo);
begin
FLogLevel := ALogLevel;
FLogMemo := ALogMemo;
FCriticalSection := TCriticalSection.Create;
end;
destructor TMemoLogAppender.Destroy;
begin
FCriticalSection.Free;
inherited;
end;
procedure TMemoLogAppender.Send(logLevel: Integer; Log: ILog);
var
FormattedMessage: string;
LogTime: TDateTime;
LogMsg: string;
begin
FCriticalSection.Acquire;
try
LogTime := Now;
FormattedMessage := FormatDateTime('[yyyy-mm-dd HH:nn:ss.zzz]', LogTime);
LogMsg := Log.GetMessage;
if LogMsg.IsEmpty then
FormattedMessage := ''
else
FormattedMessage := FormattedMessage + '[' + IntToStr(logLevel) +'] ' + LogMsg;
if logLevel <= FLogLevel then
FLogMemo.Lines.Add( FormattedMessage );
finally
FCriticalSection.Release;
end;
end;
{ TFileLogAppender }
constructor TFileLogAppender.Create(ALogLevel: integer; AFilename: string);
var
iniFile: TIniFile;
fileNum: integer;
logsDir: string;
begin
FLogLevel := ALogLevel;
FCriticalSection := TCriticalSection.Create;
logsDir := ExtractFilePath(Application.ExeName) + 'logs\';
if not DirectoryExists(logsDir) then
CreateDir(logsDir);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
try
fileNum := iniFile.ReadInteger( 'Settings', 'LogFileNum', 0 );
FLogFile := logsDir + AFilename + Format( '%.4d', [fileNum] ) + '.log';
iniFile.WriteInteger( 'Settings', 'LogFileNum', fileNum + 1 );
finally
iniFile.Free;
end;
end;
destructor TFileLogAppender.Destroy;
begin
FCriticalSection.Free;
inherited;
end;
procedure TFileLogAppender.Send(logLevel: integer; Log: ILog);
var
formattedMessage: string;
logTime: TDateTime;
logMsg: string;
txtFile: TextFile;
begin
FCriticalSection.Acquire;
try
logTime := Now;
formattedMessage := FormatDateTime('[yyyy-mm-dd HH:nn:ss.zzz]', logTime);
logMsg := Log.GetMessage;
if logMsg.IsEmpty then
formattedMessage := ''
else
formattedMessage := formattedMessage + '[' + IntToStr(logLevel) +'] ' + logMsg;
try
AssignFile( txtFile, FLogFile );
if FileExists(FLogFile) then
Append( txtFile )
else
ReWrite( txtFile );
if logLevel <= FLogLevel then
WriteLn( txtFile, formattedMessage );
finally
CloseFile(txtFile);
end;
finally
FCriticalSection.Release;
end;
end;
{$R *.res}
var
iniFile: TIniFile;
memoLogLevel: Integer;
fileLogLevel: Integer;
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFMain, FMain);
iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
try
memoLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 3 );
fileLogLevel := iniFile.ReadInteger( 'Settings', 'memoLogLevel', 4 );
finally
iniFile.Free;
end;
Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'kgOrdersServer' ));
Application.Run;
end.
<?xml version="1.0" encoding="utf-8"?>
<BorlandProject>
<Transactions>
<Transaction>2025/12/12 11:42:39.000.548,C:\Projects\emT3web\emT3webServer\kgOrdersServer.dproj=C:\Projects\emT3web\emT3webServer\emT3webServer.dproj</Transaction>
<Transaction>2025/12/12 13:13:17.000.618,C:\Projects\emT3web\emT3webServer\Source\qbAPI.pas=</Transaction>
<Transaction>2025/12/12 13:13:22.000.328,C:\Projects\emT3web\emT3webServer\Source\QBService.pas=</Transaction>
<Transaction>2025/12/12 13:13:26.000.015,C:\Projects\emT3web\emT3webServer\Source\QBServiceImplementation.pas=</Transaction>
<Transaction>2025/12/12 13:13:33.000.083,C:\Projects\emT3web\emT3webServer\Source\rOrderCorrugated.pas=</Transaction>
<Transaction>2025/12/12 13:13:38.000.580,C:\Projects\emT3web\emT3webServer\Source\rOrderCutting.pas=</Transaction>
<Transaction>2025/12/12 13:13:42.000.116,C:\Projects\emT3web\emT3webServer\Source\rOrderList.pas=</Transaction>
<Transaction>2025/12/12 13:13:45.000.790,C:\Projects\emT3web\emT3webServer\Source\rOrderWeb.pas=</Transaction>
<Transaction>2025/12/12 13:13:57.000.649,C:\Projects\emT3web\emT3webServer\Source\Data.pas=</Transaction>
<Transaction>2025/12/12 16:13:27.000.870,C:\Projects\emT3web\emT3webServer\Source\Api.ServiceImpl.pas=C:\Projects\emT3web\emT3webServer\Source\Lookup.ServiceImpl.pas</Transaction>
<Transaction>2025/12/12 16:13:36.000.405,C:\Projects\emT3web\emT3webServer\Source\Api.Service.pas=C:\Projects\emT3web\emT3webServer\Source\Lookup.Service.pas</Transaction>
<Transaction>2025/12/12 16:14:45.000.718,C:\Projects\emT3web\emT3webServer\Source\Api.Service.pas=</Transaction>
<Transaction>2025/12/12 16:14:51.000.384,C:\Projects\emT3web\emT3webServer\Source\Api.ServiceImpl.pas=</Transaction>
<Transaction>2025/12/12 16:15:44.000.991,=C:\Projects\emT3web\emT3webServer\ApiService.pas</Transaction>
<Transaction>2025/12/12 16:15:45.000.820,=C:\Projects\emT3web\emT3webServer\ApiServiceImplementation.pas</Transaction>
<Transaction>2025/12/12 16:15:55.000.962,C:\Projects\emT3web\emT3webServer\Source\ApiServiceImplementation.pas=C:\Projects\emT3web\emT3webServer\ApiServiceImplementation.pas</Transaction>
<Transaction>2025/12/12 16:16:05.000.589,C:\Projects\emT3web\emT3webServer\Source\ApiService.pas=C:\Projects\emT3web\emT3webServer\ApiService.pas</Transaction>
<Transaction>2025/12/12 16:26:49.025,C:\Projects\emT3web\emT3webServer\Source\ApiServiceImpl.pas=C:\Projects\emT3web\emT3webServer\Source\ApiServiceImplementation.pas</Transaction>
<Transaction>2025/12/12 16:26:54.793,C:\Projects\emT3web\emT3webServer\Source\Api.ServiceImpl.pas=C:\Projects\emT3web\emT3webServer\Source\ApiServiceImpl.pas</Transaction>
<Transaction>2025/12/12 16:27:42.275,C:\Projects\emT3web\emT3webServer\Source\Api.Service.pas=C:\Projects\emT3web\emT3webServer\Source\ApiService.pas</Transaction>
</Transactions>
<ProjectSortOrder AutoSort="0" SortType="0">
<File Path="Source"/>
<File Path="Source\Api.Database.pas"/>
<File Path="Source\Api.Database.dfm"/>
<File Path="Source\Api.Server.Module.pas"/>
<File Path="Source\Api.Server.Module.dfm"/>
<File Path="Source\Api.Service.pas"/>
<File Path="Source\Api.ServiceImpl.pas"/>
<File Path="Source\App.Server.Module.pas"/>
<File Path="Source\App.Server.Module.dfm"/>
<File Path="Source\Auth.Database.pas"/>
<File Path="Source\Auth.Database.dfm"/>
<File Path="Source\Auth.Server.Module.pas"/>
<File Path="Source\Auth.Server.Module.dfm"/>
<File Path="Source\Auth.Service.pas"/>
<File Path="Source\Auth.ServiceImpl.pas"/>
<File Path="Source\Common.Config.pas"/>
<File Path="Source\Common.Logging.pas"/>
<File Path="Source\Common.Middleware.Logging.pas"/>
<File Path="Source\Main.pas"/>
<File Path="Source\Main.dfm"/>
<File Path="Source\uLibrary.pas"/>
</ProjectSortOrder>
</BorlandProject>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment