Add Reminder App
This commit is contained in:
parent
5efc796edc
commit
2046fdf342
|
|
@ -1,9 +1,11 @@
|
|||
import { TextTool } from "../text/TextTool";
|
||||
import { DateHelper } from "./DateHelper";
|
||||
|
||||
export class DateFormatter
|
||||
{
|
||||
static YMD_HMS( date:Date ):string
|
||||
static YMD_HMS( date:Date = undefined ):string
|
||||
{
|
||||
date = date || DateHelper.now();
|
||||
let ye = ( date.getFullYear() + "" ).substring( 2 );
|
||||
let mo = TextTool.prependZeros( ( date.getMonth() + 1 ) );
|
||||
let da = TextTool.prependZeros( date.getDate() );
|
||||
|
|
@ -15,6 +17,17 @@ export class DateFormatter
|
|||
return `${ye}-${mo}-${da} ${h}-${m}-${s}`;
|
||||
}
|
||||
|
||||
static HMS( date:Date = undefined ):string
|
||||
{
|
||||
date = date || DateHelper.now();
|
||||
|
||||
let h = TextTool.prependZeros( date.getHours() );
|
||||
let m = TextTool.prependZeros( date.getMinutes() );
|
||||
let s = TextTool.prependZeros( date.getSeconds() );
|
||||
|
||||
return `${h}:${m}:${s}`;
|
||||
}
|
||||
|
||||
static forUsers( date:Date ):string
|
||||
{
|
||||
let ye = ( date.getFullYear() + "" );
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { DateExpressionLexer } from "../text/lexer/DateExpressionLexer";
|
||||
import { LexerQuery } from "../text/lexer/LexerQuery";
|
||||
|
||||
export class DateHelper
|
||||
{
|
||||
static now()
|
||||
|
|
@ -31,4 +34,62 @@ export class DateHelper
|
|||
|
||||
return date;
|
||||
}
|
||||
|
||||
static parseDateExpression( expression:string ):Date
|
||||
{
|
||||
let query = LexerQuery.from( expression, new DateExpressionLexer() );
|
||||
|
||||
let now = query.find( le => le.isMatcher( DateExpressionLexer.Now ) );
|
||||
|
||||
if ( now )
|
||||
{
|
||||
return DateHelper.now();
|
||||
}
|
||||
|
||||
let time = query.find( le => le.isMatcher( DateExpressionLexer.Time ) );
|
||||
let hourMinutesSeconds = ( time?.match || "9:00" ).split( ":" ).map( s => parseInt( s ) );
|
||||
|
||||
let hours = hourMinutesSeconds[ 0 ];
|
||||
let minutes = hourMinutesSeconds[ 1 ];
|
||||
let seconds = hourMinutesSeconds.length == 3 ? hourMinutesSeconds[ 2 ] : 0;
|
||||
|
||||
let year = DateHelper.now().getFullYear();
|
||||
let month = DateHelper.now().getMonth();
|
||||
let day = DateHelper.now().getDate();
|
||||
|
||||
let date = query.find( le => /date/i.test( le.type ) );
|
||||
|
||||
if ( date )
|
||||
{
|
||||
let seperator = /slash/i.test( date.type ) ? "/" : /dot/i.test( date.type ) ? "." : "-";
|
||||
|
||||
let values = date.match.replace( /(\/|\.|\-)$/, "" ).split( seperator ).map( s => parseInt( s ) );
|
||||
|
||||
if ( /reverse/i.test( date.type ) )
|
||||
{
|
||||
year = values[ 0 ];
|
||||
month = values[ 1 ] - 1;
|
||||
day = values[ 2 ];
|
||||
}
|
||||
else
|
||||
{
|
||||
day = values[ 0 ];
|
||||
month = values[ 1 ] - 1;
|
||||
|
||||
if ( values.length == 3 )
|
||||
{
|
||||
year = values[ 2 ];
|
||||
|
||||
if ( year < 100 )
|
||||
{
|
||||
year += 2000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return DateHelper.createYMD( year, month, day, hours, minutes, seconds );
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,8 @@ export class DateMath
|
|||
|
||||
static isAfterNow( d:Date )
|
||||
{
|
||||
return d.getTime() > DateHelper.now().getTime();
|
||||
// return d.getTime() > DateHelper.now().getTime();
|
||||
return DateMath.isAfter( d, DateHelper.now() );
|
||||
}
|
||||
|
||||
static isInTheFuture( d:Date )
|
||||
|
|
@ -82,6 +83,11 @@ export class DateMath
|
|||
return DateMath.isAfterNow( d );
|
||||
}
|
||||
|
||||
static isAheadInTheFuture( d:Date, duration:number )
|
||||
{
|
||||
return DateMath.isAfter( d, DateMath.addSeconds( DateHelper.now(), duration ) );
|
||||
}
|
||||
|
||||
static getDifferenceMs( a:Date, b:Date )
|
||||
{
|
||||
return a.getTime() - b.getTime();
|
||||
|
|
@ -115,16 +121,16 @@ export class DateMath
|
|||
return DateMath.addMinutes( new Date(), minutes );
|
||||
}
|
||||
|
||||
static fromNowAddSeconds( seconds:number )
|
||||
{
|
||||
return DateMath.addSeconds( new Date(), seconds );
|
||||
}
|
||||
|
||||
static addSeconds( a:Date, duration:number )
|
||||
{
|
||||
return this.addMilliseconds( a, duration * 1000 );
|
||||
}
|
||||
|
||||
static durationToHours( duration:number )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static addMinutes( a:Date, durationMinutes:number )
|
||||
{
|
||||
return this.addSeconds( a, durationMinutes * 60 );
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
export class Duration
|
||||
{
|
||||
static toMilliSeconds( duration:number )
|
||||
{
|
||||
return duration * 1000;
|
||||
}
|
||||
|
||||
static fromMilliSeconds( duration:number )
|
||||
{
|
||||
return duration / 1000;
|
||||
}
|
||||
|
||||
static toMinutes( duration:number )
|
||||
{
|
||||
return duration / 60;
|
||||
|
|
|
|||
|
|
@ -112,3 +112,19 @@ export class OrExpression<T> extends ListInputExpression<T>
|
|||
}
|
||||
}
|
||||
|
||||
export class LamdaExpression<T> extends BooleanExpression<T>
|
||||
{
|
||||
_lamda:(t:T)=>boolean;
|
||||
|
||||
constructor( lamda:(t:T)=>boolean )
|
||||
{
|
||||
super();
|
||||
this._lamda = lamda;
|
||||
}
|
||||
|
||||
evaluate( t:T )
|
||||
{
|
||||
return this._lamda( t );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { JSRandomEngine } from "./JSRandomEngine";
|
||||
|
||||
export class CryptoTool
|
||||
{
|
||||
static randomUUID():string
|
||||
{
|
||||
if ( window.location.protocol !== "https" )
|
||||
{
|
||||
let random = JSRandomEngine.$;
|
||||
|
||||
let structure = [8,4,4,4,12];
|
||||
|
||||
let id = "";
|
||||
let first = true;
|
||||
|
||||
for ( let j = 0; j < structure.length; j++ )
|
||||
{
|
||||
if ( first ){ first = false; }
|
||||
else{ id +="-"; }
|
||||
|
||||
let num = structure[ j ];
|
||||
|
||||
for ( let i = 0; i < num; i++ )
|
||||
{
|
||||
id += random.fromString( "0123456789abcdef" );
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { Lexer } from "./Lexer";
|
||||
import { LexerMatcher } from "./LexerMatcher";
|
||||
import { LexerMatcherLibrary } from "./LexerMatcherLibrary";
|
||||
|
||||
export class DateExpressionLexer extends Lexer
|
||||
{
|
||||
static readonly Now = new LexerMatcher( "Now", /now|jetzt/i );
|
||||
static readonly Time = new LexerMatcher( "Time", /\d?\d\:\d\d(\:\d\d)?\s*(am|h|uhr)?/i );
|
||||
|
||||
static readonly LongDateMinus = new LexerMatcher( "LongDateMinus", /\d?\d\-\d?\d\-\d\d\d\d/ );
|
||||
static readonly LongDateDot = new LexerMatcher( "LongDateDot", /\d?\d\.\d?\d\.\d\d\d\d/ );
|
||||
static readonly LongDateSlash = new LexerMatcher( "LongDateSlash", /\d?\d\/\d?\d\/\d\d\d\d/ );
|
||||
|
||||
static readonly ReverseLongDateMinus = new LexerMatcher( "ReverseLongDateMinus", /\d\d\d\d\-\d?\d\-\d?\d/ );
|
||||
static readonly ReverseLongDateDot = new LexerMatcher( "ReverseLongDateDot", /\d\d\d\d\.\d?\d\.\d?\d/ );
|
||||
static readonly ReverseLongDateSlash = new LexerMatcher( "ReverseLongDateSlash", /\d\d\d\d\/\d?\d\/\d?\d/ );
|
||||
|
||||
static readonly DateMinus = new LexerMatcher( "DateMinus", /\d?\d\-\d?\d\-\d\d/ );
|
||||
static readonly DateDot = new LexerMatcher( "DateDot", /\d?\d\.\d?\d\.\d\d/ );
|
||||
static readonly DateSlash = new LexerMatcher( "DateSlash", /\d?\d\/\d?\d\/\d\d/ );
|
||||
|
||||
static readonly ShortDateMinus = new LexerMatcher( "ShortDateMinus", /\d?\d\-\d?\d(\-)?/ );
|
||||
static readonly ShortDateDot = new LexerMatcher( "ShortDateDot", /\d?\d\.\d?\d(\.)?/ );
|
||||
static readonly ShortDateSlash = new LexerMatcher( "ShortDateSlash", /\d?\d\/\d?\d(\/)?/ );
|
||||
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this.addAllMatchers(
|
||||
|
||||
DateExpressionLexer.Now,
|
||||
DateExpressionLexer.Time,
|
||||
|
||||
DateExpressionLexer.LongDateMinus,
|
||||
DateExpressionLexer.LongDateDot,
|
||||
DateExpressionLexer.LongDateSlash,
|
||||
|
||||
DateExpressionLexer.ReverseLongDateMinus,
|
||||
DateExpressionLexer.ReverseLongDateDot,
|
||||
DateExpressionLexer.ReverseLongDateSlash,
|
||||
|
||||
DateExpressionLexer.DateMinus,
|
||||
DateExpressionLexer.DateDot,
|
||||
DateExpressionLexer.DateSlash,
|
||||
|
||||
DateExpressionLexer.ShortDateMinus,
|
||||
DateExpressionLexer.ShortDateDot,
|
||||
DateExpressionLexer.ShortDateSlash,
|
||||
|
||||
LexerMatcherLibrary.WHITESPACE_MATCHER,
|
||||
LexerMatcherLibrary.BREAK_MATCHER,
|
||||
LexerMatcherLibrary.ANY_SYMBOL_MATCHER
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { LexerMatcher } from "./LexerMatcher";
|
||||
import { LexerEvent } from "./LexerEvent";
|
||||
import { LexerQuery } from "./LexerQuery";
|
||||
|
||||
export class Lexer
|
||||
{
|
||||
|
|
@ -128,6 +129,14 @@ export class Lexer
|
|||
return events;
|
||||
}
|
||||
|
||||
createLexerQuery( source:string, offset:number = 0, mode:string = Lexer.defaultMode )
|
||||
{
|
||||
let query = new LexerQuery();
|
||||
query.source = source;
|
||||
query.tokens = this.lexTokens( source, offset, mode );
|
||||
return query;
|
||||
}
|
||||
|
||||
lexToList( source:string, offset:number = 0, mode:string = Lexer.defaultMode )
|
||||
{
|
||||
var list:LexerEvent[] = [];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { LexerMatcher } from "./LexerMatcher";
|
||||
|
||||
export class LexerEvent
|
||||
{
|
||||
static readonly LENGTH_ERROR_ID = -1;
|
||||
|
|
@ -41,7 +43,12 @@ export class LexerEvent
|
|||
|
||||
isType( type:string )
|
||||
{
|
||||
return this._type == type;
|
||||
return this._type === type;
|
||||
}
|
||||
|
||||
isMatcher( lexerMatcher:LexerMatcher )
|
||||
{
|
||||
return this._type === lexerMatcher.type;
|
||||
}
|
||||
|
||||
get isError(){ return this._length === LexerEvent.LENGTH_ERROR_ID; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { BooleanExpression } from "../../expressions/BooleanExpression";
|
||||
import { BooleanExpression, LamdaExpression } from "../../expressions/BooleanExpression";
|
||||
import { ClassConstructor, isClassOf } from "../../tools/TypeUtilities";
|
||||
import { Lexer } from "./Lexer";
|
||||
import { LexerEvent } from "./LexerEvent";
|
||||
|
||||
|
||||
|
|
@ -9,6 +11,11 @@ export class LexerQuery
|
|||
|
||||
_index:Map<LexerEvent,number> = new Map<LexerEvent,number>();
|
||||
|
||||
static from( source:string, lexer:Lexer )
|
||||
{
|
||||
return lexer.createLexerQuery( source );
|
||||
}
|
||||
|
||||
createTokenIndex()
|
||||
{
|
||||
for ( let i = 0; i < this.tokens.length; i++ )
|
||||
|
|
@ -258,4 +265,20 @@ export class LexerQuery
|
|||
|
||||
return nextIndex === -1 ? null : this.tokens[ nextIndex ];
|
||||
}
|
||||
|
||||
find( matcherOrPredicate:BooleanExpression<LexerEvent>|( (l:LexerEvent)=>boolean ) )
|
||||
{
|
||||
let matcher:BooleanExpression<LexerEvent> = null;
|
||||
|
||||
if ( isClassOf( matcher, BooleanExpression ) )
|
||||
{
|
||||
matcher = matcherOrPredicate as BooleanExpression<LexerEvent>;
|
||||
}
|
||||
else
|
||||
{
|
||||
matcher = new LamdaExpression<LexerEvent>( matcherOrPredicate as (l:LexerEvent)=>boolean );
|
||||
}
|
||||
|
||||
return this.searchItem( 0, true, matcher );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export class ErrorInfo
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ export abstract class RequestHandler
|
|||
{
|
||||
|
||||
_ums:UserManagementServer;
|
||||
|
||||
get ums(){ return this._ums; };
|
||||
|
||||
_type:RequestType;
|
||||
_url:string;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ export class UserDB
|
|||
return this.users.findIndex( u => u.email === email ) != -1;
|
||||
}
|
||||
|
||||
async hasUserWithID( id:string ):Promise<boolean>
|
||||
{
|
||||
return this.users.findIndex( u => u.id === id ) != -1;
|
||||
}
|
||||
|
||||
async signUp( email:string, password:string, userName:string = "User" ):Promise<UserData>
|
||||
{
|
||||
let userData = this._pendingUsers.find( u => u.email === email );
|
||||
|
|
@ -43,6 +48,8 @@ export class UserDB
|
|||
userData.email = email;
|
||||
userData.hashedPassword = await CryptIO.hash( password );
|
||||
userData.name = userName;
|
||||
userData.role = Role.User;
|
||||
userData.permissions = [];
|
||||
|
||||
this._pendingUsers.push( userData );
|
||||
|
||||
|
|
@ -208,8 +215,10 @@ export class UserDB
|
|||
return Promise.resolve( false );
|
||||
}
|
||||
|
||||
async _hasPermission( permissionID:string, permissions:Permission[] ):Promise<boolean>
|
||||
protected async _hasPermission( permissionID:string, permissions:Permission[] ):Promise<boolean>
|
||||
{
|
||||
permissions = permissions || [];
|
||||
|
||||
for ( let p of permissions )
|
||||
{
|
||||
if ( Permission.isMatching( permissionID, p ) )
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import Fastify, { FastifyHttpsOptions, FastifyInstance, FastifyListenOptions } from "fastify";
|
||||
import Fastify, { FastifyHttpsOptions, FastifyInstance, FastifyListenOptions, FastifyRequest } from "fastify";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import cors from '@fastify/cors';
|
||||
|
||||
import { EventSlot } from "../../browser/events/EventSlot";
|
||||
import { JSRandomEngine } from "../../browser/random/JSRandomEngine";
|
||||
|
||||
import { UserDB } from "./UserDB";
|
||||
import { RequestHandler } from "./RequestHandler";
|
||||
import { EmailService as EmailService } from "./email/EmailService";
|
||||
|
|
@ -16,6 +19,15 @@ import { LocationService } from "./location/LocationService";
|
|||
import { RequestRequirement } from "./requirements/RequestRequirement";
|
||||
import { NotTooManyRequests } from "./requirements/security/NotTooManyRequests";
|
||||
import { FilesSync } from "../files/FilesSync";
|
||||
import { UserData } from "./UserData";
|
||||
import { Scheduler } from "./scheduler/Scheduler";
|
||||
import { DateHelper } from "../../browser/date/DateHelper";
|
||||
import { DateFormatter } from "../../browser/date/DateFormatter";
|
||||
import { Duration } from "../../browser/date/Duration";
|
||||
import { Task } from "./scheduler/Task";
|
||||
import { UserApp } from "./apps/UserApp";
|
||||
import { UserAppFactory } from "./apps/UserAppFactory";
|
||||
import { iTaskScheduler } from "./scheduler/iTaskScheduler";
|
||||
|
||||
export class UserManagementServer
|
||||
{
|
||||
|
|
@ -43,6 +55,14 @@ export class UserManagementServer
|
|||
_globalRequirements:RequestRequirement[] = [];
|
||||
get globalRequirements(){ return this._globalRequirements; }
|
||||
|
||||
_apps:UserApp<any>[] = [];
|
||||
get apps(){ return this._apps; }
|
||||
|
||||
_scheduler:Scheduler;
|
||||
get scheduler(){ return this._scheduler; }
|
||||
|
||||
readonly onFileChanged = new EventSlot<string>();
|
||||
|
||||
|
||||
async initialize( settings:UserManagementServerSettings, mailService:EmailService, handlers:RequestHandler[], globalRequirements:RequestRequirement[] = null ):Promise<void>
|
||||
{
|
||||
|
|
@ -54,9 +74,12 @@ export class UserManagementServer
|
|||
await this._addGlobalRequirements( globalRequirements || UserManagementServer.DefaultGlobalRequirements() );
|
||||
await this._addServices( mailService );
|
||||
await this._addHandlers( handlers );
|
||||
await this._startApps();
|
||||
|
||||
await this._startServer();
|
||||
|
||||
this._update();
|
||||
|
||||
return Promise.resolve();
|
||||
|
||||
}
|
||||
|
|
@ -83,6 +106,16 @@ export class UserManagementServer
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getUser( request:FastifyRequest ):Promise<UserData>
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let tokenData = requestBody as { token:string };
|
||||
let tokenID = tokenData.token;
|
||||
|
||||
let session = this._sessions.get( tokenID );
|
||||
return this.userDB.byID( session.userID );
|
||||
}
|
||||
|
||||
async sendEmail( to:string, title:string, message:string ):Promise<void>
|
||||
{
|
||||
return this.email.send( this._settings.emailFrom, to, title, message );
|
||||
|
|
@ -115,27 +148,52 @@ export class UserManagementServer
|
|||
|
||||
this._app.register( fastifyMultipart );
|
||||
|
||||
for ( let corsURL of this._settings.corsURLs )
|
||||
if ( this._settings.isDebugMode )
|
||||
{
|
||||
RJLog.log( "Adding cors:", corsURL );
|
||||
|
||||
RJLog.log( "Setting any cors:" );
|
||||
|
||||
await this._app.register( cors,
|
||||
{
|
||||
origin: corsURL,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
|
||||
}
|
||||
);
|
||||
{
|
||||
origin: "*",
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( let corsURL of this._settings.corsURLs )
|
||||
{
|
||||
RJLog.log( "Adding cors:", corsURL );
|
||||
|
||||
await this._app.register( cors,
|
||||
{
|
||||
origin: corsURL,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
_updateDuration = 60;
|
||||
|
||||
protected _update()
|
||||
{
|
||||
let self = this;
|
||||
|
||||
let waitTimeMS = Duration.toMilliSeconds( this._updateDuration );
|
||||
|
||||
setTimeout( () =>{ self._update() }, waitTimeMS );
|
||||
}
|
||||
|
||||
async _addServices( mailService:EmailService ):Promise<void>
|
||||
{
|
||||
this._userDB = await UserDB.load( this, this._settings.userDBPath );
|
||||
|
||||
RJLog.log( "Loading roles:", this._settings.rolesPath );
|
||||
this._scheduler = new Scheduler();
|
||||
|
||||
// RJLog.log( "Loading roles:", this._settings.rolesPath );
|
||||
let rolesData = await Files.loadJSON<RolesData>( this._settings.rolesPath );
|
||||
this._roles = new Map<string,Role>();
|
||||
rolesData.roles.forEach( r => this._roles.set( r.id, r ) );
|
||||
|
|
@ -177,10 +235,10 @@ export class UserManagementServer
|
|||
port: this._settings.port
|
||||
}
|
||||
|
||||
if ( ! this._settings.isDebugMode )
|
||||
{
|
||||
// if ( ! this._settings.isDebugMode )
|
||||
// {
|
||||
listenOptions.host = "0.0.0.0";
|
||||
}
|
||||
// }
|
||||
|
||||
this.app.listen(
|
||||
|
||||
|
|
@ -200,4 +258,25 @@ export class UserManagementServer
|
|||
);
|
||||
}
|
||||
|
||||
async _startApps()
|
||||
{
|
||||
for ( let appID of this._settings.userApps )
|
||||
{
|
||||
let app = UserAppFactory.create( appID, this );
|
||||
RJLog.log( "Starting app:", app.id );
|
||||
this._apps.push( app );
|
||||
}
|
||||
|
||||
for ( let app of this._apps )
|
||||
{
|
||||
await app.initialize();
|
||||
|
||||
if ( app.schedulesTasks )
|
||||
{
|
||||
this.scheduler._taskSchedulers.push( app as any as iTaskScheduler );
|
||||
}
|
||||
}
|
||||
|
||||
await this._scheduler.update();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,10 @@ export class UserManagementServerSettings
|
|||
userDBPath:string;
|
||||
rolesPath:string;
|
||||
|
||||
// Apps
|
||||
appsPath:string;
|
||||
userApps:string[];
|
||||
|
||||
// Geo-Lite Lib
|
||||
geoLocationPath:string;
|
||||
geoAccountID:string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { EventSlot } from "../../../browser/events/EventSlot";
|
||||
import { Files } from "../../files/Files";
|
||||
import { PathFilter } from "../../files/PathFilter";
|
||||
import { UserData } from "../UserData";
|
||||
import { UserManagementServer } from "../UserManagementServer";
|
||||
|
||||
export class UserApp<AppData>
|
||||
{
|
||||
protected _id:string;
|
||||
get id(){ return this._id;}
|
||||
|
||||
protected _ums:UserManagementServer;
|
||||
get ums(){ return this._ums }
|
||||
|
||||
protected _path:string;
|
||||
get path(){ return this._path; }
|
||||
|
||||
readonly onFileChange = new EventSlot<string>();
|
||||
|
||||
constructor( id:string, ums:UserManagementServer )
|
||||
{
|
||||
this._ums = ums;
|
||||
this._id = id;
|
||||
|
||||
this._path = Files.joinPaths( [ this._ums._settings.appsPath, this._id ] );
|
||||
}
|
||||
|
||||
async initialize():Promise<void>
|
||||
{
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
get schedulesTasks():boolean
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
getUserPath( userID:string ){ return Files.joinPaths( [ this.path, userID + ".json" ] ); }
|
||||
|
||||
async iterateUserData( action:(ud:UserData,ad:AppData)=>Promise<void>):Promise<void>
|
||||
{
|
||||
await Files.forDirectChildrenIn( this.path, PathFilter.JSON,
|
||||
async ( file ) =>
|
||||
{
|
||||
let id = file.fileNameWithoutExtension;
|
||||
|
||||
if ( ! this.ums.userDB.hasUserWithID( id ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let userData = await this.ums.userDB.byID( id );
|
||||
|
||||
let appData = await Files.loadJSON<AppData>( file.absolutePath );
|
||||
|
||||
return action( userData, appData );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { UserManagementServer } from "../UserManagementServer";
|
||||
import { ReminderApp } from "./reminder/ReminderApp";
|
||||
import { UserApp } from "./UserApp";
|
||||
|
||||
export class UserAppFactory
|
||||
{
|
||||
static create( id:string, ums:UserManagementServer ):UserApp<any>
|
||||
{
|
||||
let constructors = [ ReminderApp ];
|
||||
|
||||
for ( let c of constructors )
|
||||
{
|
||||
if ( c.id === id )
|
||||
{
|
||||
return new c( ums );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { DateHelper } from "../../../../browser/date/DateHelper";
|
||||
import { DateMath } from "../../../../browser/date/DateMath";
|
||||
import { ISOTimeStamp } from "../../../../browser/date/ISOTimeStamp";
|
||||
import { PathReference } from "../../../files/PathReference";
|
||||
import { iTaskScheduler } from "../../scheduler/iTaskScheduler";
|
||||
import { Task } from "../../scheduler/Task";
|
||||
import { UserData } from "../../UserData";
|
||||
import { UserManagementServer } from "../../UserManagementServer";
|
||||
import { UserApp } from "../UserApp";
|
||||
import { MailEntry } from "./data/MailEntry";
|
||||
import { ReminderData } from "./data/ReminderData";
|
||||
|
||||
export class ReminderApp extends UserApp<ReminderData> implements iTaskScheduler
|
||||
{
|
||||
static readonly id = "reminder";
|
||||
|
||||
constructor( ums:UserManagementServer )
|
||||
{
|
||||
super( ReminderApp.id, ums );
|
||||
this.onFileChange.addListener(
|
||||
async ( filePath )=>
|
||||
{
|
||||
let pathReference = new PathReference( filePath );
|
||||
let id = pathReference.fileNameWithoutExtension;
|
||||
let user = await this.ums.userDB.byID( id );
|
||||
|
||||
if ( ! user )
|
||||
{
|
||||
console.log( "file changed:", filePath, "but no user associated" );
|
||||
return;
|
||||
}
|
||||
|
||||
let data = await pathReference.loadJSON<ReminderData>();
|
||||
|
||||
let tasks:Task[] =[];
|
||||
this.grabTasks( user, data, this.ums.scheduler.maxDate, tasks );
|
||||
this.ums.scheduler.scheduleTasks( tasks );
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async getTasksToSchedule( maxDate:Date ):Promise<Task[]>
|
||||
{
|
||||
let tasks = [];
|
||||
|
||||
await this.iterateUserData(
|
||||
( userData:UserData, reminderData:ReminderData )=>
|
||||
{
|
||||
this.grabTasks( userData, reminderData, maxDate, tasks );
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
|
||||
return Promise.resolve( tasks );
|
||||
}
|
||||
|
||||
grabTasks( userData:UserData, rd:ReminderData, maxDate:Date, tasks:Task[] )
|
||||
{
|
||||
rd.mailEntries.forEach(
|
||||
( me )=>
|
||||
{
|
||||
let date = DateHelper.parseDateExpression( me.date );
|
||||
|
||||
if ( DateMath.isBefore( date, maxDate ) )
|
||||
{
|
||||
let task = Task.createAt( date,
|
||||
()=>
|
||||
{
|
||||
this.ums.sendEmail( userData.email, me.subject, me.message );
|
||||
}
|
||||
);
|
||||
|
||||
tasks.push( task )
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
import { DateHelper } from "../../../../../browser/date/DateHelper";
|
||||
import { ISOTimeStamp } from "../../../../../browser/date/ISOTimeStamp";
|
||||
import { DateExpressionLexer } from "../../../../../browser/text/lexer/DateExpressionLexer";
|
||||
import { LexerQuery } from "../../../../../browser/text/lexer/LexerQuery";
|
||||
import { RegExpUtility } from "../../../../../browser/text/RegExpUtitlity";
|
||||
import { ReminderEntry, ReminderEntryType } from "./ReminderEntry";
|
||||
|
||||
export class MailEntry extends ReminderEntry
|
||||
{
|
||||
date:string;
|
||||
subject:string;
|
||||
message:string;
|
||||
|
||||
static create()
|
||||
{
|
||||
let me = new MailEntry();
|
||||
ReminderEntry.initialize( me );
|
||||
|
||||
return me;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { MailEntry } from "./MailEntry";
|
||||
|
||||
export class ReminderData
|
||||
{
|
||||
mailEntries:MailEntry[];
|
||||
|
||||
static create()
|
||||
{
|
||||
let rd = new ReminderData();
|
||||
rd.mailEntries = [];
|
||||
return rd;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { ISOTimeStamp } from "../../../../../browser/date/ISOTimeStamp";
|
||||
import { CryptoTool } from "../../../../../browser/random/CryptoTool";
|
||||
|
||||
export enum ReminderEntryType
|
||||
{
|
||||
Mail, Clock, Calender
|
||||
}
|
||||
|
||||
export class ReminderEntry
|
||||
{
|
||||
type:ReminderEntryType;
|
||||
id:string;
|
||||
creationTime:ISOTimeStamp;
|
||||
|
||||
static initialize( r:ReminderEntry )
|
||||
{
|
||||
r.id = CryptoTool.randomUUID();
|
||||
r.type = ReminderEntryType.Mail;
|
||||
r.creationTime = ISOTimeStamp.now();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,9 @@ import { LoginHandler } from "./_/login";
|
|||
import { LogoutHandler } from "./_/logout";
|
||||
import { RequestPasswordChangeHandler } from "./_/request-password-change";
|
||||
import { SignUpHandler } from "./_/signup";
|
||||
import { AppsCanUseHandler } from "./apps/can-use";
|
||||
import { AppsLoadHandler } from "./apps/load";
|
||||
import { AppsSaveHandler } from "./apps/save";
|
||||
|
||||
export class HandlerGroups
|
||||
{
|
||||
|
|
@ -21,7 +24,12 @@ export class HandlerGroups
|
|||
new InfoHandler(),
|
||||
|
||||
new RequestPasswordChangeHandler(),
|
||||
new ChangePasswordHandler()
|
||||
new ChangePasswordHandler(),
|
||||
|
||||
new AppsCanUseHandler(),
|
||||
new AppsSaveHandler(),
|
||||
new AppsLoadHandler(),
|
||||
|
||||
];
|
||||
|
||||
return defaultHandlers;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class ConfirmSignUpHandler extends RequestHandler
|
|||
|
||||
if ( ! result )
|
||||
{
|
||||
return this.sendError( "User signup confimration failed" );
|
||||
return this.sendError( "User signup confirmation failed" );
|
||||
}
|
||||
|
||||
return this.sendInfo( "User creation confirmed" );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { Message } from '../../../../browser/messages/Message';
|
||||
import { RequestRequirement } from '../../requirements/RequestRequirement';
|
||||
import { Permission } from '../../permissions/Permission';
|
||||
|
||||
export class UserCanUseApp extends RequestRequirement
|
||||
{
|
||||
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let appData = requestBody as { token:string, appID:string };
|
||||
|
||||
if ( ! appData )
|
||||
{
|
||||
return this.sendError( "No token data" );
|
||||
}
|
||||
|
||||
if ( this.ums._settings.userApps.indexOf( appData.appID ) == -1 )
|
||||
{
|
||||
return this.sendError( "Invalid app id: " + appData.appID + ". Defined apps:" + this.ums._settings.userApps.join( ", " ) );
|
||||
}
|
||||
|
||||
let user = await this.ums.getUser( request );
|
||||
|
||||
let appPermissionID = "apps." + appData.appID;
|
||||
|
||||
let hasPermission = await this.ums.userDB.hasPermission( user, appPermissionID );
|
||||
|
||||
if ( ! hasPermission )
|
||||
{
|
||||
return this.sendError( "Permission for app not found" );
|
||||
}
|
||||
|
||||
return this.giveOK();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserIsLoggedIn } from "../../requirements/user/UserIsLoggedIn";
|
||||
import { UserCanUseApp } from "./UserCanUseApp";
|
||||
|
||||
|
||||
export class AppsCanUseHandler extends RequestHandler
|
||||
{
|
||||
static url = "/apps/can-use";
|
||||
constructor()
|
||||
{
|
||||
super( RequestType.POST, AppsCanUseHandler.url, [ new UserIsLoggedIn(), new UserCanUseApp() ] );
|
||||
}
|
||||
|
||||
async _handle( request:FastifyRequest, reply:FastifyReply )
|
||||
{
|
||||
return this.sendInfo( "Can use" );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserIsLoggedIn } from "../../requirements/user/UserIsLoggedIn";
|
||||
import { UserCanUseApp } from "./UserCanUseApp";
|
||||
import { FilesSync } from "../../../files/FilesSync";
|
||||
import { Files } from "../../../files/Files";
|
||||
|
||||
|
||||
export class AppsLoadHandler extends RequestHandler
|
||||
{
|
||||
static url = "/apps/load";
|
||||
constructor()
|
||||
{
|
||||
super( RequestType.POST, AppsLoadHandler.url, [ new UserIsLoggedIn(), new UserCanUseApp() ] );
|
||||
}
|
||||
|
||||
async _handle( request:FastifyRequest, reply:FastifyReply )
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let appData = requestBody as { appData: any, appID:string };
|
||||
|
||||
let user = await this.getUser();
|
||||
let filePath = Files.joinPaths( [ this._ums._settings.appsPath, appData.appID, user.id + ".json" ] );
|
||||
|
||||
let exists = await Files.exists( filePath );
|
||||
|
||||
if ( ! exists )
|
||||
{
|
||||
return this.sendInfo( "No data saved" );
|
||||
}
|
||||
|
||||
let data = await Files.loadJSON<any>( filePath );
|
||||
|
||||
return this.sendDataInfo( "Data loaded", data );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserIsLoggedIn } from "../../requirements/user/UserIsLoggedIn";
|
||||
import { UserCanUseApp } from "./UserCanUseApp";
|
||||
import { FilesSync } from "../../../files/FilesSync";
|
||||
import { Files } from "../../../files/Files";
|
||||
|
||||
|
||||
export class AppsSaveHandler extends RequestHandler
|
||||
{
|
||||
static url = "/apps/save";
|
||||
constructor()
|
||||
{
|
||||
super( RequestType.POST, AppsSaveHandler.url, [ new UserIsLoggedIn(), new UserCanUseApp() ] );
|
||||
}
|
||||
|
||||
async _handle( request:FastifyRequest, reply:FastifyReply )
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let appData = requestBody as { appData: any, appID:string };
|
||||
|
||||
let user = await this.getUser();
|
||||
let filePath = Files.joinPaths( [ this._ums._settings.appsPath, appData.appID, user.id + ".json" ] );
|
||||
|
||||
await Files.ensureParentDirectoryExists( filePath );
|
||||
await Files.saveJSON( filePath, appData.appData, true );
|
||||
|
||||
let app = this._ums.apps.find( a => a.id === appData.appID );
|
||||
this._ums.onFileChanged.dispatch( filePath );
|
||||
app.onFileChange.dispatch( filePath );
|
||||
|
||||
return this.sendInfo( "Data saved" );
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@ import { Permission } from "./Permission";
|
|||
|
||||
export class Role
|
||||
{
|
||||
static readonly User = "user";
|
||||
static readonly Admin = "admin";
|
||||
|
||||
id:string;
|
||||
inherits:string;
|
||||
permissions:Permission[];
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@ import { RequestHandler } from "../RequestHandler";
|
|||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserManagementServer } from "../UserManagementServer";
|
||||
import { Message } from "../../../browser/messages/Message";
|
||||
import { ErrorInfo } from "../ErrorInfo";
|
||||
import { MessageTypes } from "../../../browser/messages/MessageType";
|
||||
|
||||
export abstract class RequestRequirement
|
||||
{
|
||||
_isGlobal:boolean = false;
|
||||
_ums:UserManagementServer;
|
||||
_handler:RequestHandler = null;
|
||||
private _isGlobal:boolean = false;
|
||||
private _ums:UserManagementServer;
|
||||
private _handler:RequestHandler = null;
|
||||
get handler(){ return this._handler; }
|
||||
get ums() { return this._isGlobal ? this._ums : this._handler._ums };
|
||||
|
||||
|
||||
initialize( handler:RequestHandler ):Promise<void>
|
||||
|
|
@ -25,6 +29,17 @@ export abstract class RequestRequirement
|
|||
}
|
||||
|
||||
|
||||
sendError( message:string, errorInfo:ErrorInfo = undefined ):Promise<Message[]>
|
||||
{
|
||||
let messages = [ Message.Error( message ) ];
|
||||
return Promise.resolve( messages );
|
||||
}
|
||||
|
||||
giveOK():Promise<Message[]>
|
||||
{
|
||||
return Promise.resolve( [] );
|
||||
}
|
||||
|
||||
abstract handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ export class NotTooManyRequests extends RequestRequirement
|
|||
|
||||
if ( ! DateMath.isExpired( new Date( blockTime + this._blockDuration ) ) )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "User blocked" ) ] );
|
||||
return this.sendError( "User blocked" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +34,10 @@ export class NotTooManyRequests extends RequestRequirement
|
|||
|
||||
if ( ! valid )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "Too many requests" ) ] );
|
||||
return this.sendError( "Too many requests" );
|
||||
}
|
||||
|
||||
return Promise.resolve( [] );
|
||||
return this.giveOK();
|
||||
}
|
||||
|
||||
async updateIP( ip:string ):Promise<boolean>
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@ export class UserHasPermission extends RequestRequirement
|
|||
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
{
|
||||
let user = await this._handler.getUser();
|
||||
let user = await this.ums.getUser( request );
|
||||
|
||||
let userDB = this._handler.userDB;
|
||||
let userDB = this.ums.userDB;
|
||||
|
||||
let hasPermission = await userDB.hasPermission( user, this._permissionID );
|
||||
|
||||
if ( ! hasPermission )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "User has no permission for " + this._permissionID ) ] );
|
||||
return this.sendError( "User has no permission for " + this._permissionID );
|
||||
}
|
||||
|
||||
return Promise.resolve( [] );
|
||||
return this.giveOK();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,27 +13,27 @@ export class UserIsLoggedIn extends RequestRequirement
|
|||
|
||||
if ( ! tokenData )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "No token data" )] );
|
||||
return this.sendError( "No token data" );
|
||||
}
|
||||
|
||||
let tokenID = tokenData.token;
|
||||
|
||||
let session = this._handler._ums._sessions.get( tokenID );
|
||||
let session = this.ums._sessions.get( tokenID );
|
||||
|
||||
if ( ! session )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "No session for token:" + tokenID )] );
|
||||
return this.sendError( "No session for token:" + tokenID );
|
||||
}
|
||||
|
||||
let token = this._handler._ums.tokenDB._tokens.get( tokenID );
|
||||
let token = this.ums.tokenDB._tokens.get( tokenID );
|
||||
|
||||
let isValid = await this._handler._ums.tokenDB.validate( token, request.ip );
|
||||
let isValid = await this.ums.tokenDB.validate( token, request.ip );
|
||||
|
||||
if ( ! isValid )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "Invalid token" + tokenID )] );
|
||||
return this.sendError( "Invalid token" + tokenID );
|
||||
}
|
||||
|
||||
return Promise.resolve( [] );
|
||||
return this.giveOK();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
import { MinPriorityQueue } from '@datastructures-js/priority-queue';
|
||||
import { Arrays } from '../../../browser/tools/Arrays';
|
||||
import { DateHelper } from '../../../browser/date/DateHelper';
|
||||
import { DateMath } from '../../../browser/date/DateMath';
|
||||
import { Task } from './Task';
|
||||
import { iTaskScheduler } from './iTaskScheduler';
|
||||
import { RJLog } from '../../log/RJLog';
|
||||
|
||||
export class Scheduler
|
||||
{
|
||||
// [ Tasks ]
|
||||
_queue:Task[] = [];
|
||||
_scheduledTasks = new Set<string>();
|
||||
|
||||
// [ Next Task ]
|
||||
_taskTimerCallback:NodeJS.Timeout;
|
||||
_nextTaskID:string;
|
||||
|
||||
// [ Settings ]
|
||||
_maxScheduleDurationDays:number = 1;
|
||||
|
||||
// [ Schedulers ]
|
||||
_taskSchedulers:iTaskScheduler[] = [];
|
||||
get taskSchedulers(){ return this._taskSchedulers; }
|
||||
|
||||
constructor()
|
||||
{
|
||||
this._queue =[];
|
||||
this._taskTimerCallback = null;
|
||||
this._nextTaskID = null;
|
||||
}
|
||||
|
||||
get maxDate():Date
|
||||
{
|
||||
return DateMath.fromNowAddDays( this._maxScheduleDurationDays );
|
||||
}
|
||||
|
||||
async update()
|
||||
{
|
||||
let maxDate = this.maxDate;
|
||||
|
||||
let allTasks:Task[] = [];
|
||||
|
||||
for ( let ts of this._taskSchedulers )
|
||||
{
|
||||
let tasks = await ts.getTasksToSchedule( maxDate );
|
||||
allTasks = allTasks.concat( tasks );
|
||||
}
|
||||
|
||||
this.scheduleTasks( allTasks );
|
||||
}
|
||||
|
||||
|
||||
scheduleTasks( tasks:Task[] )
|
||||
{
|
||||
let maxDate = DateMath.fromNowAddDays( this._maxScheduleDurationDays );
|
||||
tasks = tasks.filter( t => ! this._scheduledTasks.has( t.id ) && ! DateMath.isAfter( t.date, maxDate ) );
|
||||
|
||||
|
||||
this._queue = this._queue.concat( tasks );
|
||||
tasks.forEach( t => this._scheduledTasks.add( t.id ) );
|
||||
|
||||
this._queue.sort( ( a, b ) => { return a.date.getTime() - b.date.getTime() } );
|
||||
|
||||
let newNextTask = this._queue[ 0 ];
|
||||
|
||||
if ( this._nextTaskID && newNextTask.id != this._nextTaskID )
|
||||
{
|
||||
this._resetTimer();
|
||||
}
|
||||
|
||||
this._updateTimer();
|
||||
}
|
||||
|
||||
protected _resetTimer()
|
||||
{
|
||||
if ( this._taskTimerCallback )
|
||||
{
|
||||
clearTimeout( this._taskTimerCallback );
|
||||
this._taskTimerCallback = null;
|
||||
}
|
||||
|
||||
this._nextTaskID = null;
|
||||
}
|
||||
|
||||
protected _updateTimer()
|
||||
{
|
||||
// RJLog.log( "_updateTimer:" );
|
||||
|
||||
if ( this._taskTimerCallback || this._queue.length == 0 )
|
||||
{
|
||||
// RJLog.log( "Nothing to do. Has Timer:", this._taskTimerCallback, "Queue Length:", this._queue.length );
|
||||
return;
|
||||
}
|
||||
|
||||
let task = this._queue[ 0 ];
|
||||
this._nextTaskID = task.id;
|
||||
|
||||
let delay = Math.max( 0, task.date.getTime() - Date.now() );
|
||||
|
||||
RJLog.log( "delaying:", delay );
|
||||
|
||||
this._taskTimerCallback = setTimeout(
|
||||
() =>
|
||||
{
|
||||
this._taskTimerCallback = null;
|
||||
Arrays.remove( this._queue, task );
|
||||
this._scheduledTasks.delete( task.id );
|
||||
this._nextTaskID = null;
|
||||
|
||||
try
|
||||
{
|
||||
task.action();
|
||||
}
|
||||
catch( e )
|
||||
{
|
||||
RJLog.log( e );
|
||||
}
|
||||
|
||||
this._updateTimer();
|
||||
|
||||
},
|
||||
delay
|
||||
);
|
||||
}
|
||||
|
||||
cancelTask( id:string ): void
|
||||
{
|
||||
let index = this._queue.findIndex( e => e.id === id );
|
||||
|
||||
if ( index === -1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Arrays.removeAt( this._queue, index );
|
||||
|
||||
this._resetTimer();
|
||||
this._updateTimer();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { MinPriorityQueue } from '@datastructures-js/priority-queue';
|
||||
import { Arrays } from '../../../browser/tools/Arrays';
|
||||
import { DateHelper } from '../../../browser/date/DateHelper';
|
||||
import { DateMath } from '../../../browser/date/DateMath';
|
||||
|
||||
export class Task
|
||||
{
|
||||
id:string;
|
||||
date:Date;
|
||||
action:()=>void;
|
||||
|
||||
static createIn( duration:number, action:()=>void )
|
||||
{
|
||||
let date = DateMath.fromNowAddSeconds( duration );
|
||||
return Task.createAt( date, action );
|
||||
}
|
||||
|
||||
static createAt( date:Date, action:()=>void )
|
||||
{
|
||||
let task = new Task();
|
||||
|
||||
task.id = crypto.randomUUID();
|
||||
task.action = action;
|
||||
task.date = date;
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Scheduler } from "./Scheduler";
|
||||
import { Task } from "./Task";
|
||||
|
||||
export interface iTaskScheduler
|
||||
{
|
||||
getTasksToSchedule( maxDate:Date ):Promise<Task[]>;
|
||||
}
|
||||
Loading…
Reference in New Issue