2024-08-02 14:15:12 +00:00
using System.Diagnostics ;
2023-11-08 10:59:15 +00:00
using System.Net ;
2024-08-02 14:15:12 +00:00
using System.Text.RegularExpressions ;
2023-03-08 12:20:33 +00:00
using InnovEnergy.App.Backend.Database ;
2023-03-15 13:38:06 +00:00
using InnovEnergy.App.Backend.DataTypes ;
using InnovEnergy.App.Backend.DataTypes.Methods ;
using InnovEnergy.App.Backend.Relations ;
2023-11-08 10:59:15 +00:00
using InnovEnergy.App.Backend.Websockets ;
2023-03-23 13:23:03 +00:00
using InnovEnergy.Lib.Utils ;
2023-02-16 12:57:06 +00:00
using Microsoft.AspNetCore.Mvc ;
2023-07-05 12:31:53 +00:00
namespace InnovEnergy.App.Backend ;
2023-02-16 12:57:06 +00:00
2023-03-20 09:20:56 +00:00
using Token = String ;
2024-07-11 11:56:13 +00:00
// create JobStatus class to track download battery log job
public class JobStatus
{
public string JobId { get ; set ; }
public string Status { get ; set ; }
public string FileName { get ; set ; }
public DateTime StartTime { get ; set ; }
}
2023-11-08 10:59:15 +00:00
2023-07-27 11:23:17 +00:00
[Controller]
2023-03-15 13:38:06 +00:00
[Route("api/")]
2024-11-28 13:43:47 +00:00
//All the http requests from the frontend that contain "/api" will be forwarded to this controller from the nginx reverse proxy.
2023-03-20 07:33:44 +00:00
public class Controller : ControllerBase
2023-02-16 12:57:06 +00:00
{
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(Login))]
2023-07-06 09:06:55 +00:00
public ActionResult < Session > Login ( String username , String ? password )
2023-02-16 12:57:06 +00:00
{
2024-11-28 13:43:47 +00:00
//Find the user to the database, verify its password and create a new session.
//Store the new session to the database and return it to the frontend.
//If the user log out, the session will be deleted. Each session is valid for 24 hours. The db deletes all invalid/expired sessions every 30 minutes.
2023-07-06 09:09:16 +00:00
var user = Db . GetUserByEmail ( username ) ;
2023-04-13 12:30:01 +00:00
2023-03-23 11:47:25 +00:00
if ( user is null )
2023-10-26 12:09:38 +00:00
throw new Exceptions ( 400 , "Null User Exception" , "Must provide a user to log in as." , Request . Path . Value ! ) ;
2023-04-13 12:30:01 +00:00
2023-10-26 12:09:38 +00:00
if ( ! ( user . Password . IsNullOrEmpty ( ) & & user . MustResetPassword ) & & ! user . VerifyPassword ( password ) )
2023-03-23 11:47:25 +00:00
{
2023-10-26 12:09:38 +00:00
throw new Exceptions ( 401 , "Wrong Password Exception" , "Please try again." , Request . Path . Value ! ) ;
2023-03-23 11:47:25 +00:00
}
2024-08-12 07:48:16 +00:00
2023-03-30 07:27:18 +00:00
var session = new Session ( user . HidePassword ( ) . HideParentIfUserHasNoAccessToParent ( user ) ) ;
2023-03-23 11:47:25 +00:00
2023-03-20 09:20:56 +00:00
return Db . Create ( session )
? session
2023-04-13 12:30:01 +00:00
: throw new Exceptions ( 401 , "Session Creation Exception" , "Not allowed to log in." , Request . Path . Value ! ) ;
2023-02-16 12:57:06 +00:00
}
2023-02-23 10:26:55 +00:00
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(Logout))]
2023-03-20 09:20:56 +00:00
public ActionResult Logout ( Token authToken )
2023-02-21 08:58:21 +00:00
{
2024-11-28 13:43:47 +00:00
//Find the session and delete it from the database.
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-02-21 08:58:21 +00:00
2023-03-15 13:38:06 +00:00
return session . Logout ( )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-02-21 08:58:21 +00:00
}
2023-11-15 16:22:42 +00:00
2023-11-08 10:59:15 +00:00
[HttpGet(nameof(CreateWebSocket))]
public async Task CreateWebSocket ( Token authToken )
{
var session = Db . GetSession ( authToken ) ? . User ;
if ( session is null )
{
2023-11-13 16:51:36 +00:00
Console . WriteLine ( "------------------------------------Unauthorized user----------------------------------------------" ) ;
2023-11-08 10:59:15 +00:00
HttpContext . Response . StatusCode = ( int ) HttpStatusCode . Unauthorized ;
HttpContext . Abort ( ) ;
return ;
}
if ( ! HttpContext . WebSockets . IsWebSocketRequest )
{
2023-11-13 16:51:36 +00:00
Console . WriteLine ( "------------------------------------Not a websocket request ----------------------------------------------" ) ;
2023-11-08 10:59:15 +00:00
HttpContext . Response . StatusCode = ( int ) HttpStatusCode . Unauthorized ;
HttpContext . Abort ( ) ;
return ;
}
var webSocketContext = await HttpContext . WebSockets . AcceptWebSocketAsync ( ) ;
var webSocket = webSocketContext ;
//Handle the WebSocket connection
2023-11-08 11:07:49 +00:00
await WebsocketManager . HandleWebSocketConnection ( webSocket ) ;
2023-11-08 10:59:15 +00:00
}
2023-11-15 16:22:42 +00:00
[HttpGet(nameof(GetAllErrorsForInstallation))]
public ActionResult < IEnumerable < Error > > GetAllErrorsForInstallation ( Int64 id , Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
return Unauthorized ( ) ;
return Db . Errors
. Where ( error = > error . InstallationId = = id )
2023-11-22 15:49:47 +00:00
. OrderByDescending ( error = > error . Date )
. ThenByDescending ( error = > error . Time )
2023-11-15 16:22:42 +00:00
. ToList ( ) ;
}
2023-11-20 16:29:45 +00:00
2024-06-11 12:31:08 +00:00
[HttpGet(nameof(GetHistoryForInstallation))]
public ActionResult < IEnumerable < UserAction > > GetHistoryForInstallation ( Int64 id , Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
return Unauthorized ( ) ;
return Db . UserActions
. Where ( action = > action . InstallationId = = id )
. OrderByDescending ( action = > action . Timestamp )
. ToList ( ) ;
}
2023-11-20 16:29:45 +00:00
[HttpGet(nameof(GetAllWarningsForInstallation))]
public ActionResult < IEnumerable < Warning > > GetAllWarningsForInstallation ( Int64 id , Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
return Unauthorized ( ) ;
return Db . Warnings
. Where ( error = > error . InstallationId = = id )
2023-11-22 15:49:47 +00:00
. OrderByDescending ( error = > error . Date )
. ThenByDescending ( error = > error . Time )
2023-11-20 16:29:45 +00:00
. ToList ( ) ;
}
2024-08-02 14:15:12 +00:00
2023-03-09 11:50:21 +00:00
2024-06-26 15:05:27 +00:00
[HttpGet(nameof(GetCsvTimestampsForInstallation))]
2024-08-02 14:15:12 +00:00
public ActionResult < IEnumerable < Int64 > > GetCsvTimestampsForInstallation ( Int64 id , Int32 start , Int32 end , Token authToken )
2024-06-26 15:05:27 +00:00
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
return Unauthorized ( ) ;
var sampleSize = 100 ;
2024-08-02 14:15:12 +00:00
var allTimestamps = new List < Int64 > ( ) ;
2024-06-26 15:05:27 +00:00
2024-08-02 14:15:12 +00:00
static string FindCommonPrefix ( string str1 , string str2 )
2024-06-26 15:05:27 +00:00
{
2024-08-02 14:15:12 +00:00
int minLength = Math . Min ( str1 . Length , str2 . Length ) ;
int i = 0 ;
while ( i < minLength & & str1 [ i ] = = str2 [ i ] )
{
i + + ;
}
return str1 . Substring ( 0 , i ) ;
2024-06-26 15:05:27 +00:00
}
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
Int64 startTimestamp = Int64 . Parse ( start . ToString ( ) . Substring ( 0 , 5 ) ) ;
Int64 endTimestamp = Int64 . Parse ( end . ToString ( ) . Substring ( 0 , 5 ) ) ;
2024-08-16 07:40:19 +00:00
if ( installation . Product = = 1 )
{
start = Int32 . Parse ( start . ToString ( ) . Substring ( 0 , start . ToString ( ) . Length - 2 ) ) ;
end = Int32 . Parse ( end . ToString ( ) . Substring ( 0 , end . ToString ( ) . Length - 2 ) ) ;
}
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
string configPath = "/home/ubuntu/.s3cfg" ;
while ( startTimestamp < = endTimestamp )
2024-06-26 15:05:27 +00:00
{
2024-08-16 07:40:19 +00:00
string bucketPath = installation . Product = = 0 ? "s3://" + installation . S3BucketId + "-3e5b3069-214a-43ee-8d85-57d72000c19d/" + startTimestamp :
"s3://" + installation . S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/" + startTimestamp ;
2024-08-05 14:31:57 +00:00
Console . WriteLine ( "Fetching data for " + startTimestamp ) ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
try
2024-08-02 14:15:12 +00:00
{
2024-08-05 14:31:57 +00:00
// Set up process start info
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "s3cmd" ,
Arguments = $"--config {configPath} ls {bucketPath}" ,
RedirectStandardOutput = true ,
RedirectStandardError = true ,
UseShellExecute = false ,
CreateNoWindow = true
} ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
// Start the process
Process process = new Process
{
StartInfo = startInfo
} ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
process . Start ( ) ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
// Read the output
string output = process . StandardOutput . ReadToEnd ( ) ;
string error = process . StandardError . ReadToEnd ( ) ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
process . WaitForExit ( ) ;
2024-08-02 14:15:12 +00:00
2024-08-05 14:31:57 +00:00
// Check for errors
if ( process . ExitCode ! = 0 )
{
Console . WriteLine ( "Error executing command:" ) ;
Console . WriteLine ( error ) ;
}
else
2024-08-02 14:15:12 +00:00
{
2024-08-05 14:31:57 +00:00
// Define a regex pattern to match the filenames without .csv extension
var pattern = @"/([^/]+)\.csv$" ;
var regex = new Regex ( pattern ) ;
// Process each line of the output
foreach ( var line in output . Split ( '\n' ) )
2024-08-02 14:15:12 +00:00
{
2024-08-05 14:31:57 +00:00
var match = regex . Match ( line ) ;
2024-08-16 07:40:19 +00:00
2024-08-05 14:31:57 +00:00
if ( match . Success & & long . Parse ( match . Groups [ 1 ] . Value ) > = start & & long . Parse ( match . Groups [ 1 ] . Value ) < = end )
{
allTimestamps . Add ( long . Parse ( match . Groups [ 1 ] . Value ) ) ;
//Console.WriteLine(match.Groups[1].Value);
}
2024-08-02 14:15:12 +00:00
}
2024-08-05 14:31:57 +00:00
}
}
catch ( Exception e )
{
Console . WriteLine ( $"Exception: {e.Message}" ) ;
2024-08-02 14:15:12 +00:00
}
2024-06-26 15:05:27 +00:00
2024-08-05 14:31:57 +00:00
startTimestamp + + ;
}
2024-06-26 15:05:27 +00:00
int totalRecords = allTimestamps . Count ;
if ( totalRecords < = sampleSize )
{
// If the total records are less than or equal to the sample size, return all records
Console . WriteLine ( "Start timestamp = " + start + " end timestamp = " + end ) ;
Console . WriteLine ( "SampledTimestamps = " + allTimestamps . Count ) ;
return allTimestamps ;
}
int interval = totalRecords / sampleSize ;
2024-08-02 14:15:12 +00:00
var sampledTimestamps = new List < Int64 > ( ) ;
2024-06-26 15:05:27 +00:00
for ( int i = 0 ; i < totalRecords ; i + = interval )
{
sampledTimestamps . Add ( allTimestamps [ i ] ) ;
}
// If we haven't picked enough records (due to rounding), add the latest record to ensure completeness
if ( sampledTimestamps . Count < sampleSize )
{
sampledTimestamps . Add ( allTimestamps . Last ( ) ) ;
}
Console . WriteLine ( "Start timestamp = " + start + " end timestamp = " + end ) ;
Console . WriteLine ( "TotalRecords = " + totalRecords + " interval = " + interval ) ;
Console . WriteLine ( "SampledTimestamps = " + sampledTimestamps . Count ) ;
return sampledTimestamps ;
}
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetUserById))]
2023-03-20 09:20:56 +00:00
public ActionResult < User > GetUserById ( Int64 id , Token authToken )
2023-03-16 10:16:23 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ? . User ;
if ( session = = null )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 10:16:23 +00:00
var user = Db . GetUserById ( id ) ;
2023-03-20 09:20:56 +00:00
if ( user is null | | ! session . HasAccessTo ( user ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 11:49:25 +00:00
2023-10-26 12:09:38 +00:00
return user
. HidePassword ( )
. HideParentIfUserHasNoAccessToParent ( session ) ;
2023-03-16 10:16:23 +00:00
}
2023-03-15 13:38:06 +00:00
2023-03-16 10:16:23 +00:00
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetInstallationById))]
2023-03-20 09:20:56 +00:00
public ActionResult < Installation > GetInstallationById ( Int64 id , Token authToken )
2023-03-16 10:16:23 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-16 10:16:23 +00:00
if ( user = = null )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 10:16:23 +00:00
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-07-13 07:40:04 +00:00
2023-07-05 12:31:53 +00:00
return installation
. FillOrderNumbers ( )
2023-07-13 07:40:04 +00:00
. HideParentIfUserHasNoAccessToParent ( user )
2024-04-02 12:36:43 +00:00
. HideWriteKeyIfUserIsNotAdmin ( user . UserType ) ;
2023-03-16 10:16:23 +00:00
}
2023-03-16 15:13:04 +00:00
2023-03-23 07:23:36 +00:00
[HttpGet(nameof(GetUsersWithDirectAccessToInstallation))]
public ActionResult < IEnumerable < Object > > GetUsersWithDirectAccessToInstallation ( Int64 id , Token authToken )
2023-03-16 15:13:04 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-16 15:13:04 +00:00
if ( user = = null )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 15:13:04 +00:00
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-09 11:50:21 +00:00
2023-03-23 07:23:36 +00:00
return installation
2023-10-26 12:09:38 +00:00
. UsersWithDirectAccess ( )
. Where ( u = > u . IsDescendantOf ( user ) )
. Select ( u = > u . HidePassword ( ) )
. ToList ( ) ;
2023-03-23 07:23:36 +00:00
}
2023-03-16 15:13:04 +00:00
2023-03-23 07:23:36 +00:00
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
public ActionResult < IEnumerable < Object > > GetUsersWithInheritedAccessToInstallation ( Int64 id , Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var installation = Db . GetInstallationById ( id ) ;
if ( installation is null | | ! user . HasAccessTo ( installation ) )
return Unauthorized ( ) ;
return installation
2023-10-26 12:09:38 +00:00
. Ancestors ( )
. SelectMany ( f = > f . UsersWithDirectAccess ( )
. Where ( u = > u . IsDescendantOf ( user ) )
. Select ( u = > new { folderId = f . Id , folderName = f . Name , user = u . HidePassword ( ) } ) )
. ToList ( ) ;
2023-03-16 15:13:04 +00:00
}
2023-03-23 07:23:36 +00:00
2023-03-23 07:27:29 +00:00
[HttpGet(nameof(GetUsersWithDirectAccessToFolder))]
public ActionResult < IEnumerable < Object > > GetUsersWithDirectAccessToFolder ( Int64 id , Token authToken )
2023-03-16 15:13:04 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-16 15:13:04 +00:00
if ( user = = null )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 15:13:04 +00:00
var folder = Db . GetFolderById ( id ) ;
if ( folder is null | | ! user . HasAccessTo ( folder ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 15:13:04 +00:00
return folder
2023-03-23 07:27:29 +00:00
. UsersWithDirectAccess ( )
. Where ( u = > u . IsDescendantOf ( user ) )
2023-03-30 07:27:18 +00:00
. Select ( u = > u . HidePassword ( ) )
2023-03-23 07:27:29 +00:00
. ToList ( ) ;
}
[HttpGet(nameof(GetUsersWithInheritedAccessToFolder))]
public ActionResult < IEnumerable < Object > > GetUsersWithInheritedAccessToFolder ( Int64 id , Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
var folder = Db . GetFolderById ( id ) ;
if ( folder is null | | ! user . HasAccessTo ( folder ) )
return Unauthorized ( ) ;
return folder
. Ancestors ( )
. SelectMany ( f = > f . UsersWithDirectAccess ( )
. Where ( u = > u . IsDescendantOf ( user ) )
2023-03-23 13:23:03 +00:00
. Select ( u = > new { folderId = f . Id , folderName = f . Name , user = u . HidePassword ( ) } ) )
2023-03-23 07:27:29 +00:00
. ToList ( ) ;
2023-03-16 15:13:04 +00:00
}
2023-02-22 13:46:36 +00:00
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetFolderById))]
2023-03-20 09:20:56 +00:00
public ActionResult < Folder > GetFolderById ( Int64 id , Token authToken )
2023-03-16 10:16:23 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-16 10:16:23 +00:00
if ( user = = null )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-16 10:16:23 +00:00
var folder = Db . GetFolderById ( id ) ;
if ( folder is null | | ! user . HasAccessTo ( folder ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-03-30 07:27:18 +00:00
return folder . HideParentIfUserHasNoAccessToParent ( user ) ;
2023-03-16 10:16:23 +00:00
}
2023-02-16 14:08:50 +00:00
2023-03-23 14:45:40 +00:00
[HttpGet(nameof(GetAllDirectChildUsers))]
public ActionResult < IEnumerable < User > > GetAllDirectChildUsers ( Token authToken )
2023-03-23 07:23:36 +00:00
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
2023-03-30 07:27:18 +00:00
return user . ChildUsers ( ) . Select ( u = > u . HidePassword ( ) ) . ToList ( ) ;
2023-03-23 07:23:36 +00:00
}
2023-03-23 14:46:19 +00:00
2023-03-23 14:45:40 +00:00
[HttpGet(nameof(GetAllChildUsers))]
public ActionResult < IEnumerable < User > > GetAllChildUsers ( Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user = = null )
return Unauthorized ( ) ;
2023-09-15 11:34:28 +00:00
return user
. DescendantUsers ( )
. Select ( u = > u . HidePassword ( ) )
. ToList ( ) ;
2023-03-23 14:46:19 +00:00
}
2023-03-23 14:45:40 +00:00
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetAllInstallations))]
2023-03-20 09:20:56 +00:00
public ActionResult < IEnumerable < Installation > > GetAllInstallations ( Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-20 07:33:44 +00:00
if ( user is null )
return Unauthorized ( ) ;
2023-03-30 07:27:18 +00:00
2023-06-30 06:58:50 +00:00
return user
2024-04-16 11:57:04 +00:00
. AccessibleInstallations ( product : 0 )
2024-04-02 12:36:43 +00:00
. Select ( i = > i . FillOrderNumbers ( ) . HideParentIfUserHasNoAccessToParent ( user ) . HideWriteKeyIfUserIsNotAdmin ( user . UserType ) )
2023-06-30 06:58:50 +00:00
. ToList ( ) ;
2023-02-16 12:57:06 +00:00
}
2024-04-16 11:57:04 +00:00
[HttpGet(nameof(GetAllSalidomoInstallations))]
public ActionResult < IEnumerable < Installation > > GetAllSalidomoInstallations ( Token authToken )
{
var user = Db . GetSession ( authToken ) ? . User ;
if ( user is null )
return Unauthorized ( ) ;
return user
. AccessibleInstallations ( product : 1 )
. ToList ( ) ;
}
2023-02-24 11:58:47 +00:00
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetAllFolders))]
2023-03-20 09:20:56 +00:00
public ActionResult < IEnumerable < Folder > > GetAllFolders ( Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-03-20 07:33:44 +00:00
if ( user is null )
return Unauthorized ( ) ;
2023-02-24 12:59:56 +00:00
2023-03-30 07:27:18 +00:00
return new ( user . AccessibleFolders ( ) . HideParentIfUserHasNoAccessToParent ( user ) ) ;
2023-02-24 12:59:56 +00:00
}
2023-03-15 13:38:06 +00:00
2023-03-20 07:33:44 +00:00
[HttpGet(nameof(GetAllFoldersAndInstallations))]
2024-08-12 07:48:16 +00:00
public ActionResult < IEnumerable < Object > > GetAllFoldersAndInstallations ( int productId , Token authToken )
2023-03-08 12:40:34 +00:00
{
2023-03-20 09:20:56 +00:00
var user = Db . GetSession ( authToken ) ? . User ;
2023-06-30 06:58:50 +00:00
2023-03-20 07:33:44 +00:00
if ( user is null )
return Unauthorized ( ) ;
2024-08-12 07:48:16 +00:00
2023-03-21 10:42:49 +00:00
var foldersAndInstallations = user
2024-08-12 07:48:16 +00:00
. AccessibleFoldersAndInstallations ( product : productId )
2023-03-30 07:27:18 +00:00
. Do ( o = > o . FillOrderNumbers ( ) )
. Select ( o = > o . HideParentIfUserHasNoAccessToParent ( user ) )
. OfType < Object > ( ) ; // Important! JSON serializer must see Objects otherwise
// it will just serialize the members of TreeNode %&@#!!!
2023-03-08 12:40:34 +00:00
2023-07-13 07:40:04 +00:00
// TODO Filter out write keys
2023-03-21 10:42:49 +00:00
return new ( foldersAndInstallations ) ;
2023-03-08 12:40:34 +00:00
}
2023-03-09 15:33:14 +00:00
2023-03-15 13:38:06 +00:00
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(CreateUser))]
2023-10-26 12:09:38 +00:00
public async Task < ActionResult < User > > CreateUser ( [ FromBody ] User newUser , Token authToken )
2023-03-09 15:33:14 +00:00
{
2023-11-27 15:43:42 +00:00
2023-10-23 14:25:34 +00:00
var create = Db . GetSession ( authToken ) . Create ( newUser ) ;
2023-11-27 15:43:42 +00:00
if ( create )
{
var mail_success = await Db . SendNewUserEmail ( newUser ) ;
if ( ! mail_success )
{
Db . GetSession ( authToken ) . Delete ( newUser ) ;
}
return mail_success ? newUser . HidePassword ( ) : Unauthorized ( ) ;
}
2023-10-23 14:35:43 +00:00
2023-11-27 15:43:42 +00:00
return Unauthorized ( ) ;
2023-03-09 15:33:14 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(CreateInstallation))]
2023-09-15 11:34:28 +00:00
public async Task < ActionResult < Installation > > CreateInstallation ( [ FromBody ] Installation installation , Token authToken )
2023-03-09 15:33:14 +00:00
{
2024-04-16 11:57:04 +00:00
2023-03-30 07:27:18 +00:00
var session = Db . GetSession ( authToken ) ;
if ( ! await session . Create ( installation ) )
2023-03-20 07:33:44 +00:00
return Unauthorized ( ) ;
2023-07-27 14:51:57 +00:00
return installation ;
2023-03-09 15:33:14 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(CreateFolder))]
2023-09-15 11:34:28 +00:00
public ActionResult < Folder > CreateFolder ( [ FromBody ] Folder folder , Token authToken )
2023-03-09 15:33:14 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-09 15:33:14 +00:00
2023-03-20 07:33:44 +00:00
if ( ! session . Create ( folder ) )
return Unauthorized ( ) ;
2023-03-30 07:27:18 +00:00
return folder . HideParentIfUserHasNoAccessToParent ( session ! . User ) ;
2023-03-09 15:33:14 +00:00
}
2023-03-16 12:33:51 +00:00
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(GrantUserAccessToFolder))]
2023-03-20 09:20:56 +00:00
public ActionResult GrantUserAccessToFolder ( FolderAccess folderAccess , Token authToken )
2023-03-16 12:33:51 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-16 12:33:51 +00:00
2023-03-19 16:53:00 +00:00
// TODO: automatic BadRequest when properties are null during deserialization
var folder = Db . GetFolderById ( folderAccess . FolderId ) ;
var user = Db . GetUserById ( folderAccess . UserId ) ;
return session . GrantUserAccessTo ( user , folder )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-03-16 12:33:51 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(RevokeUserAccessToFolder))]
2023-03-20 09:20:56 +00:00
public ActionResult RevokeUserAccessToFolder ( FolderAccess folderAccess , Token authToken )
2023-03-16 12:33:51 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-16 12:33:51 +00:00
2023-03-19 16:53:00 +00:00
// TODO: automatic BadRequest when properties are null during deserialization
var folder = Db . GetFolderById ( folderAccess . FolderId ) ;
var user = Db . GetUserById ( folderAccess . UserId ) ;
return session . RevokeUserAccessTo ( user , folder )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-03-16 12:33:51 +00:00
}
2023-03-19 16:53:00 +00:00
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(GrantUserAccessToInstallation))]
2023-03-20 09:20:56 +00:00
public ActionResult GrantUserAccessToInstallation ( InstallationAccess installationAccess , Token authToken )
2023-03-16 12:33:51 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-19 16:53:00 +00:00
// TODO: automatic BadRequest when properties are null during deserialization
2023-07-20 11:57:12 +00:00
var installation = Db . GetInstallationById ( installationAccess . InstallationId ) ;
2023-03-19 16:53:00 +00:00
var user = Db . GetUserById ( installationAccess . UserId ) ;
2023-03-16 12:33:51 +00:00
2023-03-19 16:53:00 +00:00
return session . GrantUserAccessTo ( user , installation )
2023-10-26 12:09:38 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-03-16 12:33:51 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpPost(nameof(RevokeUserAccessToInstallation))]
2023-03-20 09:20:56 +00:00
public ActionResult RevokeUserAccessToInstallation ( InstallationAccess installationAccess , Token authToken )
2023-03-16 12:33:51 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-19 16:53:00 +00:00
// TODO: automatic BadRequest when properties are null during deserialization
2023-09-15 11:34:28 +00:00
var installation = Db . GetInstallationById ( installationAccess . InstallationId ) ;
2023-03-19 16:53:00 +00:00
var user = Db . GetUserById ( installationAccess . UserId ) ;
return session . RevokeUserAccessTo ( user , installation )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-03-16 12:33:51 +00:00
}
2023-03-19 16:53:00 +00:00
2023-03-20 07:33:44 +00:00
[HttpPut(nameof(UpdateUser))]
2023-09-15 11:34:28 +00:00
public ActionResult < User > UpdateUser ( [ FromBody ] User updatedUser , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-15 13:38:06 +00:00
2023-03-20 07:33:44 +00:00
if ( ! session . Update ( updatedUser ) )
return Unauthorized ( ) ;
2023-03-23 13:23:03 +00:00
2023-03-30 07:27:18 +00:00
return updatedUser . HidePassword ( ) ;
2023-02-16 12:57:06 +00:00
}
2023-02-24 11:58:47 +00:00
2023-03-23 11:47:25 +00:00
[HttpPut(nameof(UpdatePassword))]
public ActionResult < User > UpdatePassword ( String newPassword , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
return session . UpdatePassword ( newPassword )
? Ok ( )
: Unauthorized ( ) ;
}
2023-03-20 07:33:44 +00:00
[HttpPut(nameof(UpdateInstallation))]
2023-09-15 11:34:28 +00:00
public ActionResult < Installation > UpdateInstallation ( [ FromBody ] Installation installation , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-20 07:33:44 +00:00
if ( ! session . Update ( installation ) )
return Unauthorized ( ) ;
2024-04-16 11:57:04 +00:00
if ( installation . Product = = 0 )
{
return installation . FillOrderNumbers ( ) . HideParentIfUserHasNoAccessToParent ( session ! . User ) . HideWriteKeyIfUserIsNotAdmin ( session . User . UserType ) ;
}
2023-02-16 12:57:06 +00:00
2024-04-16 11:57:04 +00:00
return installation . HideParentIfUserHasNoAccessToParent ( session ! . User ) ;
2023-02-16 12:57:06 +00:00
}
2023-11-22 08:35:29 +00:00
[HttpPost(nameof(AcknowledgeError))]
public ActionResult AcknowledgeError ( Int64 id , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
if ( session = = null )
return Unauthorized ( ) ;
var error = Db . Errors
. FirstOrDefault ( error = > error . Id = = id ) ;
error . Seen = true ;
return Db . Update ( error )
? Ok ( )
: Unauthorized ( ) ;
}
[HttpPost(nameof(AcknowledgeWarning))]
public ActionResult AcknowledgeWarning ( Int64 id , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
if ( session = = null )
return Unauthorized ( ) ;
var warning = Db . Warnings
. FirstOrDefault ( warning = > warning . Id = = id ) ;
warning . Seen = true ;
return Db . Update ( warning )
? Ok ( )
: Unauthorized ( ) ;
}
2023-02-23 10:26:55 +00:00
2023-03-20 07:33:44 +00:00
[HttpPut(nameof(UpdateFolder))]
2023-09-15 11:34:28 +00:00
public ActionResult < Folder > UpdateFolder ( [ FromBody ] Folder folder , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-20 07:33:44 +00:00
if ( ! session . Update ( folder ) )
return Unauthorized ( ) ;
2023-02-16 12:57:06 +00:00
2023-03-30 07:28:44 +00:00
return folder . HideParentIfUserHasNoAccessToParent ( session ! . User ) ;
2023-02-16 12:57:06 +00:00
}
2023-03-23 11:47:25 +00:00
[HttpPut(nameof(MoveInstallation))]
public ActionResult MoveInstallation ( Int64 installationId , Int64 parentId , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
return session . MoveInstallation ( installationId , parentId )
? Ok ( )
: Unauthorized ( ) ;
}
2024-03-19 15:41:36 +00:00
[HttpPost(nameof(UpdateFirmware))]
2024-04-30 12:07:50 +00:00
public async Task < ActionResult > UpdateFirmware ( Int64 batteryNode , Int64 installationId , String version , Token authToken )
2024-03-19 15:41:36 +00:00
{
var session = Db . GetSession ( authToken ) ;
var installationToUpdate = Db . GetInstallationById ( installationId ) ;
2024-06-26 15:05:27 +00:00
2024-03-19 15:41:36 +00:00
if ( installationToUpdate ! = null )
{
2024-06-27 10:01:01 +00:00
_ = session . RunScriptInBackground ( installationToUpdate . VpnIp , batteryNode , version , installationToUpdate . Product ) ;
2024-03-19 15:41:36 +00:00
}
return Ok ( ) ;
}
2024-07-11 11:56:13 +00:00
private static Dictionary < string , JobStatus > JobStatuses = new Dictionary < string , JobStatus > ( ) ;
[HttpPost("StartDownloadBatteryLog")]
public async Task < ActionResult < string > > StartDownloadBatteryLog ( long batteryNode , long installationId , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
var installationToDownload = Db . GetInstallationById ( installationId ) ;
if ( installationToDownload ! = null )
{
string jobId = Guid . NewGuid ( ) . ToString ( ) ;
_ = Task . Run ( async ( ) = >
{
await session . RunDownloadLogScript ( installationToDownload . VpnIp , batteryNode , installationToDownload . Product ) ;
string fileName = $"{installationToDownload.VpnIp}-node{batteryNode}-{DateTime.Now:dd-MM-yyyy}.bin" ;
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}" ;
if ( System . IO . File . Exists ( filePath ) )
{
SaveJobStatus ( jobId , "Completed" , fileName : fileName ) ;
}
else
{
SaveJobStatus ( jobId , "Failed" ) ;
}
} ) ;
// Store initial job status in in-memory storage
SaveJobStatus ( jobId , "Processing" ) ;
return Ok ( jobId ) ;
}
return NotFound ( ) ;
}
[HttpGet("DownloadBatteryLog")]
public async Task < ActionResult > DownloadBatteryLog ( string jobId )
{
Console . WriteLine ( "-----------------------------------Start uploading battery log-----------------------------------" ) ;
var jobStatus = JobStatuses . TryGetValue ( jobId , out var status ) ? status : null ;
if ( jobStatus = = null | | jobStatus . Status ! = "Completed" | | string . IsNullOrEmpty ( jobStatus . FileName ) )
{
return NotFound ( ) ;
}
string fileName = jobStatus . FileName ;
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}" ;
if ( ! System . IO . File . Exists ( filePath ) )
{
return NotFound ( ) ;
}
string contentType = "application/octet-stream" ;
var memory = new MemoryStream ( ) ;
await using ( var stream = new FileStream ( filePath , FileMode . Open ) )
{
await stream . CopyToAsync ( memory ) ;
}
memory . Position = 0 ;
var fileContentResult = new FileContentResult ( memory . ToArray ( ) , contentType )
{
//FileDownloadName = Path.GetFileName(filePath)
FileDownloadName = fileName
} ;
Console . WriteLine ( "-----------------------------------Stop uploading battery log-----------------------------------" ) ;
return fileContentResult ;
}
[HttpDelete("DeleteBatteryLog")]
public IActionResult DeleteBatteryLog ( string fileName )
{
Console . WriteLine ( "-----------------------------------Start deleting downloaded battery log-----------------------------------" ) ;
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}" ;
try
{
if ( System . IO . File . Exists ( filePath ) )
{
System . IO . File . Delete ( filePath ) ;
Console . WriteLine ( "-----------------------------------Stop deleting downloaded battery log-----------------------------------" ) ;
return Ok ( ) ;
}
else
{
return NotFound ( "File not found." ) ;
}
}
catch ( Exception ex )
{
return StatusCode ( 500 , $"Internal server error: {ex.Message}" ) ;
}
}
private void SaveJobStatus ( string jobId , string status , string fileName = null )
{
JobStatuses [ jobId ] = new JobStatus
{
JobId = jobId ,
Status = status ,
FileName = fileName ,
StartTime = DateTime . UtcNow // Initialize StartTime when saving
} ;
}
[HttpGet("GetJobResult")]
public ActionResult GetJobResult ( string jobId )
{
if ( string . IsNullOrEmpty ( jobId ) )
{
return BadRequest ( new { status = "Error" , message = "Job ID is required." } ) ;
}
if ( ! JobStatuses . TryGetValue ( jobId , out var jobStatus ) )
{
return NotFound ( ) ;
}
if ( jobStatus . Status = = "Completed" )
{
return Ok ( new { status = "Completed" , fileName = jobStatus . FileName } ) ;
}
else if ( jobStatus . Status = = "Failed" )
{
return StatusCode ( 500 , new { status = "Failed" , message = "Job processing failed." } ) ;
}
else if ( jobStatus . Status = = "Processing" )
{
// Check for timeout
var startTime = jobStatus . StartTime ;
var currentTime = DateTime . UtcNow ;
if ( ( currentTime - startTime ) . TotalMinutes > 60 ) //60 minutes as timeout => Running multiple tasks in parallel on a crowded backend server will increase the time each task takes to complete
{
return StatusCode ( 500 , new { status = "Failed" , message = "Job in back end timeout exceeded." } ) ;
}
return Ok ( new { status = "Processing" } ) ;
}
else
{
return BadRequest ( new { status = "Unknown" , message = "Unknown job status." } ) ;
}
}
2024-06-18 14:19:40 +00:00
[HttpPost(nameof(InsertNewAction))]
public async Task < ActionResult < IEnumerable < Object > > > InsertNewAction ( [ FromBody ] UserAction action , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
2024-07-23 12:52:10 +00:00
var actionSuccess = await session . InsertUserAction ( action ) ;
2024-06-18 14:19:40 +00:00
return actionSuccess ? Ok ( ) : Unauthorized ( ) ;
}
2024-07-18 07:37:40 +00:00
[HttpPost(nameof(UpdateAction))]
public async Task < ActionResult < IEnumerable < Object > > > UpdateAction ( [ FromBody ] UserAction action , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
var actionSuccess = await session . UpdateUserAction ( action ) ;
return actionSuccess ? Ok ( ) : Unauthorized ( ) ;
}
[HttpPost(nameof(DeleteAction))]
2024-07-18 09:38:15 +00:00
public async Task < ActionResult < IEnumerable < Object > > > DeleteAction ( Int64 actionId , Token authToken )
2024-07-18 07:37:40 +00:00
{
var session = Db . GetSession ( authToken ) ;
2024-07-18 09:38:15 +00:00
var actionSuccess = await session . DeleteUserAction ( actionId ) ;
2024-07-18 07:37:40 +00:00
return actionSuccess ? Ok ( ) : Unauthorized ( ) ;
}
2023-10-16 09:27:19 +00:00
[HttpPost(nameof(EditInstallationConfig))]
2024-06-18 14:19:40 +00:00
public async Task < ActionResult < IEnumerable < Object > > > EditInstallationConfig ( [ FromBody ] Configuration config , Int64 installationId , Token authToken )
2023-10-16 09:27:19 +00:00
{
var session = Db . GetSession ( authToken ) ;
2024-06-11 12:31:08 +00:00
// Send configuration changes
var success = await session . SendInstallationConfig ( installationId , config ) ;
2023-10-16 09:27:19 +00:00
2024-06-11 12:31:08 +00:00
// Record configuration change
if ( success )
{
2024-06-18 14:19:40 +00:00
// Create a new UserAction object
var action = new UserAction
{
InstallationId = installationId ,
Timestamp = DateTime . Now ,
Description = config . GetConfigurationString ( )
} ;
2024-07-23 12:52:10 +00:00
var actionSuccess = await session . InsertUserAction ( action ) ;
2024-06-11 12:31:08 +00:00
return actionSuccess ? Ok ( ) : Unauthorized ( ) ;
}
return Unauthorized ( ) ;
2023-10-16 09:27:19 +00:00
}
2023-03-23 11:47:25 +00:00
[HttpPut(nameof(MoveFolder))]
public ActionResult MoveFolder ( Int64 folderId , Int64 parentId , Token authToken )
{
var session = Db . GetSession ( authToken ) ;
return session . MoveFolder ( folderId , parentId )
? Ok ( )
: Unauthorized ( ) ;
}
2023-03-20 07:33:44 +00:00
[HttpDelete(nameof(DeleteUser))]
2023-03-20 09:20:56 +00:00
public ActionResult DeleteUser ( Int64 userId , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-15 13:38:06 +00:00
var user = Db . GetUserById ( userId ) ;
2023-02-24 11:58:47 +00:00
2023-03-15 13:38:06 +00:00
return session . Delete ( user )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-02-16 12:57:06 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpDelete(nameof(DeleteInstallation))]
2023-09-08 10:00:19 +00:00
public async Task < ActionResult > DeleteInstallation ( Int64 installationId , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
2023-03-15 13:38:06 +00:00
var installation = Db . GetInstallationById ( installationId ) ;
2023-02-23 10:26:55 +00:00
2023-09-08 10:00:19 +00:00
return await session . Delete ( installation )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-02-16 12:57:06 +00:00
}
2023-03-20 07:33:44 +00:00
[HttpDelete(nameof(DeleteFolder))]
2023-03-20 09:20:56 +00:00
public ActionResult DeleteFolder ( Int64 folderId , Token authToken )
2023-02-16 12:57:06 +00:00
{
2023-03-20 09:20:56 +00:00
var session = Db . GetSession ( authToken ) ;
var folder = Db . GetFolderById ( folderId ) ;
2023-03-15 13:38:06 +00:00
return session . Delete ( folder )
2023-03-20 07:33:44 +00:00
? Ok ( )
: Unauthorized ( ) ;
2023-02-16 12:57:06 +00:00
}
2023-09-15 14:45:00 +00:00
2023-09-15 14:30:41 +00:00
[HttpPost(nameof(ResetPasswordRequest))]
2023-10-26 14:38:37 +00:00
public async Task < ActionResult < IEnumerable < Object > > > ResetPasswordRequest ( String username )
2023-09-15 12:23:22 +00:00
{
2023-10-26 14:38:37 +00:00
var user = Db . GetUserByEmail ( username ) ;
2023-09-15 12:23:22 +00:00
if ( user is null )
return Unauthorized ( ) ;
var session = new Session ( user . HidePassword ( ) . HideParentIfUserHasNoAccessToParent ( user ) ) ;
2023-10-26 12:09:38 +00:00
var success = Db . Create ( session ) ;
return success & & await Db . SendPasswordResetEmail ( user , session . Token )
? Ok ( )
: Unauthorized ( ) ;
2023-09-15 12:23:22 +00:00
}
2023-10-16 09:27:19 +00:00
2023-09-15 14:30:41 +00:00
[HttpGet(nameof(ResetPassword))]
2023-10-16 09:27:19 +00:00
public ActionResult < Object > ResetPassword ( Token token )
2023-09-15 12:23:22 +00:00
{
2023-09-15 14:45:00 +00:00
var user = Db . GetSession ( token ) ? . User ;
2023-10-16 09:27:19 +00:00
2023-09-15 12:23:22 +00:00
if ( user is null )
return Unauthorized ( ) ;
2023-10-16 09:27:19 +00:00
2023-10-23 15:04:27 +00:00
Db . DeleteUserPassword ( user ) ;
2023-10-26 14:43:48 +00:00
return Redirect ( $"https://monitor.innov.energy/?username={user.Email}&reset=true" ) ; // TODO: move to settings file
2023-10-23 14:19:03 +00:00
}
2023-10-16 09:27:19 +00:00
2023-02-16 12:57:06 +00:00
}
2023-02-24 11:58:47 +00:00