Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
arsnova-click-v2-backend
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
ARSnova
arsnova-click-v2-backend
Commits
00cc97e1
Commit
00cc97e1
authored
Nov 27, 2019
by
Christopher Mark Fullarton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactors the backend code to make more use of the mongodb. Drops support for the legacy v1 api
parent
124c27b8
Changes
103
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
103 changed files
with
1313 additions
and
3683 deletions
+1313
-3683
src/App.ts
src/App.ts
+1
-5
src/db/AMQPConnector.ts
src/db/AMQPConnector.ts
+8
-0
src/db/AbstractDAO.ts
src/db/AbstractDAO.ts
+1
-16
src/db/AssetDAO.ts
src/db/AssetDAO.ts
+22
-42
src/db/CasDAO.ts
src/db/CasDAO.ts
+4
-3
src/db/DbDAO.ts
src/db/DbDAO.ts
+6
-85
src/db/I18nDAO.ts
src/db/I18nDAO.ts
+8
-2
src/db/MathjaxDAO.ts
src/db/MathjaxDAO.ts
+4
-3
src/db/MemberDAO.ts
src/db/MemberDAO.ts
+160
-76
src/db/MongoDBConnector.ts
src/db/MongoDBConnector.ts
+2
-1
src/db/UserDAO.ts
src/db/UserDAO.ts
+50
-93
src/db/quiz/QuizDAO.ts
src/db/quiz/QuizDAO.ts
+225
-130
src/entities/AbstractEntity.ts
src/entities/AbstractEntity.ts
+0
-13
src/entities/AssetEntity.ts
src/entities/AssetEntity.ts
+0
-65
src/entities/UserEntity.ts
src/entities/UserEntity.ts
+0
-114
src/entities/answer/AbstractAnswerEntity.ts
src/entities/answer/AbstractAnswerEntity.ts
+0
-26
src/entities/answer/AnswerValidator.ts
src/entities/answer/AnswerValidator.ts
+0
-15
src/entities/answer/DefaultAnswerEntity.ts
src/entities/answer/DefaultAnswerEntity.ts
+0
-19
src/entities/answer/FreetextAnwerEntity.ts
src/entities/answer/FreetextAnwerEntity.ts
+0
-93
src/entities/member/MemberEntity.ts
src/entities/member/MemberEntity.ts
+0
-211
src/entities/member/MemberGroupEntity.ts
src/entities/member/MemberGroupEntity.ts
+0
-35
src/entities/question/ABCDSingleChoiceQuestionEntity.ts
src/entities/question/ABCDSingleChoiceQuestionEntity.ts
+0
-19
src/entities/question/AbstractChoiceQuestionEntity.ts
src/entities/question/AbstractChoiceQuestionEntity.ts
+0
-28
src/entities/question/AbstractQuestionEntity.ts
src/entities/question/AbstractQuestionEntity.ts
+0
-86
src/entities/question/FreeTextQuestionEntity.ts
src/entities/question/FreeTextQuestionEntity.ts
+0
-21
src/entities/question/MultipleChoiceQuestionEntity.ts
src/entities/question/MultipleChoiceQuestionEntity.ts
+0
-16
src/entities/question/QuizValidator.ts
src/entities/question/QuizValidator.ts
+0
-33
src/entities/question/RangedQuestionEntity.ts
src/entities/question/RangedQuestionEntity.ts
+0
-61
src/entities/question/SingleChoiceQuestionEntity.ts
src/entities/question/SingleChoiceQuestionEntity.ts
+0
-20
src/entities/question/SurveyQuestionEntity.ts
src/entities/question/SurveyQuestionEntity.ts
+0
-34
src/entities/question/TrueFalseSingleChoiceQuestionEntity.ts
src/entities/question/TrueFalseSingleChoiceQuestionEntity.ts
+0
-15
src/entities/question/YesNoSingleChoiceQuestionEntity.ts
src/entities/question/YesNoSingleChoiceQuestionEntity.ts
+0
-15
src/entities/quiz/QuizEntity.ts
src/entities/quiz/QuizEntity.ts
+0
-412
src/entities/session-configuration/AbstractSessionConfigurationEntity.ts
...ssion-configuration/AbstractSessionConfigurationEntity.ts
+0
-126
src/entities/session-configuration/MusicSessionConfigurationEntity.ts
.../session-configuration/MusicSessionConfigurationEntity.ts
+0
-64
src/entities/session-configuration/MusicTitleSessionConfigurationEntity.ts
...ion-configuration/MusicTitleSessionConfigurationEntity.ts
+0
-50
src/entities/session-configuration/MusicVolumeSessionConfigurationEntity.ts
...on-configuration/MusicVolumeSessionConfigurationEntity.ts
+0
-74
src/entities/session-configuration/NickSessionConfigurationEntity.ts
...s/session-configuration/NickSessionConfigurationEntity.ts
+0
-127
src/entities/session-configuration/SessionConfigurationEntity.ts
...ities/session-configuration/SessionConfigurationEntity.ts
+0
-46
src/export/ExcelWorkbook.ts
src/export/ExcelWorkbook.ts
+3
-3
src/export/ExcelWorksheet.ts
src/export/ExcelWorksheet.ts
+32
-25
src/export/FreeTextExcelWorksheet.ts
src/export/FreeTextExcelWorksheet.ts
+19
-17
src/export/MultipleChoiceExcelWorksheet.ts
src/export/MultipleChoiceExcelWorksheet.ts
+17
-16
src/export/RangedExcelWorksheet.ts
src/export/RangedExcelWorksheet.ts
+18
-16
src/export/SingleChoiceExcelWorksheet.ts
src/export/SingleChoiceExcelWorksheet.ts
+19
-16
src/export/SummaryExcelWorksheet.ts
src/export/SummaryExcelWorksheet.ts
+12
-11
src/export/SurveyExcelWorksheet.ts
src/export/SurveyExcelWorksheet.ts
+10
-10
src/export/lib/excel_function_library.ts
src/export/lib/excel_function_library.ts
+7
-7
src/interfaces/IQuizStatusPayload.ts
src/interfaces/IQuizStatusPayload.ts
+2
-2
src/interfaces/answeroptions/IAnswerEntity.ts
src/interfaces/answeroptions/IAnswerEntity.ts
+1
-12
src/interfaces/answeroptions/IFreetextAnswer.ts
src/interfaces/answeroptions/IFreetextAnswer.ts
+2
-1
src/interfaces/answeroptions/IFreetextAnswerEntity.ts
src/interfaces/answeroptions/IFreetextAnswerEntity.ts
+0
-16
src/interfaces/database/IDbObject.ts
src/interfaces/database/IDbObject.ts
+0
-7
src/interfaces/database/IStorageDAO.ts
src/interfaces/database/IStorageDAO.ts
+0
-3
src/interfaces/questions/IQuestion.ts
src/interfaces/questions/IQuestion.ts
+2
-19
src/interfaces/questions/IQuestionChoice.ts
src/interfaces/questions/IQuestionChoice.ts
+2
-9
src/interfaces/questions/IQuestionFreetext.ts
src/interfaces/questions/IQuestionFreetext.ts
+6
-0
src/interfaces/questions/IQuestionRanged.ts
src/interfaces/questions/IQuestionRanged.ts
+2
-2
src/interfaces/questions/IQuestionSurvey.ts
src/interfaces/questions/IQuestionSurvey.ts
+2
-2
src/interfaces/quizzes/IQuizEntity.ts
src/interfaces/quizzes/IQuizEntity.ts
+7
-49
src/interfaces/session_configuration/ISessionConfiguration.ts
...interfaces/session_configuration/ISessionConfiguration.ts
+13
-0
src/interfaces/session_configuration/ISessionConfigurationEntity.ts
...aces/session_configuration/ISessionConfigurationEntity.ts
+0
-14
src/interfaces/session_configuration/music/IMusicSessionConfiguration.ts
...session_configuration/music/IMusicSessionConfiguration.ts
+10
-0
src/interfaces/session_configuration/music/IMusicSessionConfigurationEntity.ts
...n_configuration/music/IMusicSessionConfigurationEntity.ts
+0
-12
src/interfaces/session_configuration/music/IMusicSessionConfigurationSerialized.ts
...nfiguration/music/IMusicSessionConfigurationSerialized.ts
+0
-10
src/interfaces/session_configuration/music/ITitleMusicSessionConfiguration.ts
...on_configuration/music/ITitleMusicSessionConfiguration.ts
+1
-1
src/interfaces/session_configuration/music/ITitleMusicSessionConfigurationEntity.ts
...figuration/music/ITitleMusicSessionConfigurationEntity.ts
+0
-5
src/interfaces/session_configuration/music/IVolumeMusicSessionConfiguration.ts
...n_configuration/music/IVolumeMusicSessionConfiguration.ts
+1
-1
src/interfaces/session_configuration/music/IVolumeMusicSessionConfigurationEntity.ts
...iguration/music/IVolumeMusicSessionConfigurationEntity.ts
+0
-5
src/interfaces/session_configuration/nicks/INickSessionConfiguration.ts
.../session_configuration/nicks/INickSessionConfiguration.ts
+1
-1
src/interfaces/session_configuration/nicks/INickSessionConfigurationEntity.ts
...on_configuration/nicks/INickSessionConfigurationEntity.ts
+0
-15
src/lib/cache/assets.ts
src/lib/cache/assets.ts
+15
-19
src/lib/leaderboard/leaderboard.ts
src/lib/leaderboard/leaderboard.ts
+18
-19
src/main.ts
src/main.ts
+7
-0
src/models/AssetModel.ts
src/models/AssetModel.ts
+3
-35
src/models/UserModelItem/UserModel.ts
src/models/UserModelItem/UserModel.ts
+3
-35
src/models/member/MemberModel.ts
src/models/member/MemberModel.ts
+38
-37
src/models/quiz/QuizModelItem.ts
src/models/quiz/QuizModelItem.ts
+15
-42
src/models/session-config/MusicSessionConfigurationModelItem.ts
...dels/session-config/MusicSessionConfigurationModelItem.ts
+4
-4
src/models/session-config/NickSessionConfigurationModelItem.ts
...odels/session-config/NickSessionConfigurationModelItem.ts
+2
-2
src/models/session-config/SessionConfigurationModelItem.ts
src/models/session-config/SessionConfigurationModelItem.ts
+4
-4
src/models/session-config/TitleMusicSessionConfigurationModelItem.ts
...session-config/TitleMusicSessionConfigurationModelItem.ts
+2
-2
src/models/session-config/VolumeMusicSessionConfigurationModelItem.ts
...ession-config/VolumeMusicSessionConfigurationModelItem.ts
+2
-2
src/routers/rest/AdminRouter.ts
src/routers/rest/AdminRouter.ts
+23
-34
src/routers/rest/ApiRouter.ts
src/routers/rest/ApiRouter.ts
+2
-2
src/routers/rest/ExpiryQuizRouter.ts
src/routers/rest/ExpiryQuizRouter.ts
+13
-14
src/routers/rest/LegacyApi.ts
src/routers/rest/LegacyApi.ts
+0
-89
src/routers/rest/LibRouter.ts
src/routers/rest/LibRouter.ts
+20
-27
src/routers/rest/LobbyRouter.ts
src/routers/rest/LobbyRouter.ts
+15
-22
src/routers/rest/MemberRouter.ts
src/routers/rest/MemberRouter.ts
+62
-61
src/routers/rest/QuizRouter.ts
src/routers/rest/QuizRouter.ts
+151
-228
src/services/AuthService.ts
src/services/AuthService.ts
+8
-7
src/tests/LoadTester.ts
src/tests/LoadTester.ts
+18
-72
src/tests/app-bootstrap/app-bootstrap.test.ts
src/tests/app-bootstrap/app-bootstrap.test.ts
+7
-0
src/tests/export/export.test.ts
src/tests/export/export.test.ts
+45
-61
src/tests/fixtures.ts
src/tests/fixtures.ts
+69
-0
src/tests/routes/expiry-quiz.test.ts
src/tests/routes/expiry-quiz.test.ts
+3
-3
src/tests/routes/legacy-api.test.ts
src/tests/routes/legacy-api.test.ts
+0
-131
src/tests/routes/lib.test.ts
src/tests/routes/lib.test.ts
+15
-9
src/tests/routes/lobby.test.ts
src/tests/routes/lobby.test.ts
+10
-16
src/tests/routes/member.test.ts
src/tests/routes/member.test.ts
+47
-31
src/tests/routes/quiz.test.ts
src/tests/routes/quiz.test.ts
+11
-73
tsconfig.json
tsconfig.json
+1
-1
No files found.
src/App.ts
View file @
00cc97e1
...
...
@@ -15,7 +15,6 @@ import { AdminRouter } from './routers/rest/AdminRouter';
import
{
ApiRouter
}
from
'
./routers/rest/ApiRouter
'
;
import
{
ExpiryQuizRouter
}
from
'
./routers/rest/ExpiryQuizRouter
'
;
import
{
I18nApiRouter
}
from
'
./routers/rest/I18nApiRouter
'
;
import
{
LegacyApiRouter
}
from
'
./routers/rest/LegacyApi
'
;
import
{
LibRouter
}
from
'
./routers/rest/LibRouter
'
;
import
{
LobbyRouter
}
from
'
./routers/rest/LobbyRouter
'
;
import
{
MemberRouter
}
from
'
./routers/rest/MemberRouter
'
;
...
...
@@ -23,8 +22,6 @@ import { NicksRouter } from './routers/rest/NicksRouter';
import
{
QuizRouter
}
from
'
./routers/rest/QuizRouter
'
;
import
{
dynamicStatistics
,
staticStatistics
}
from
'
./statistics
'
;
declare
var
global
:
any
;
export
const
routingControllerOptions
:
RoutingControllersOptions
=
{
defaults
:
{
nullResultCode
:
405
,
...
...
@@ -37,7 +34,7 @@ export const routingControllerOptions: RoutingControllersOptions = {
defaultErrorHandler
:
false
,
cors
:
options
,
controllers
:
[
AdminRouter
,
ApiRouter
,
ExpiryQuizRouter
,
I18nApiRouter
,
L
egacyApiRouter
,
L
ibRouter
,
LobbyRouter
,
MemberRouter
,
NicksRouter
,
QuizRouter
,
AdminRouter
,
ApiRouter
,
ExpiryQuizRouter
,
I18nApiRouter
,
LibRouter
,
LobbyRouter
,
MemberRouter
,
NicksRouter
,
QuizRouter
,
],
middlewares
:
[
I18nMiddleware
],
};
...
...
@@ -103,7 +100,6 @@ class App {
new
Integrations
.
OnUncaughtException
(),
new
Integrations
.
OnUnhandledRejection
(),
],
enabled
:
process
.
env
.
NODE_ENV
===
'
production
'
,
debug
:
true
,
});
}
}
...
...
src/db/AMQPConnector.ts
View file @
00cc97e1
...
...
@@ -3,6 +3,7 @@ import { settings } from '../statistics';
class
AMQPConnector
{
private
static
_instance
:
AMQPConnector
;
public
readonly
globalExchange
:
string
=
'
global
'
;
private
_channel
:
Channel
;
...
...
@@ -37,6 +38,13 @@ class AMQPConnector {
this
.
initConnection
();
});
}
public
buildQuizExchange
(
quizname
:
string
):
string
{
if
(
!
quizname
)
{
throw
new
Error
(
`Could not build exchange name. Quizname '
${
quizname
}
' is not supported.`
);
}
return
encodeURI
(
`quiz_
${
quizname
.
trim
()}
`
);
}
}
export
default
AMQPConnector
.
getInstance
();
src/db/AbstractDAO.ts
View file @
00cc97e1
import
{
EventEmitter
}
from
'
events
'
;
import
{
IStorageDAO
}
from
'
../interfaces/database/IStorageDAO
'
;
export
abstract
class
AbstractDAO
<
T
>
implements
IStorageDAO
<
T
>
{
export
abstract
class
AbstractDAO
{
protected
static
instance
;
protected
_isInitialized
:
boolean
;
...
...
@@ -10,26 +9,12 @@ export abstract class AbstractDAO<T> implements IStorageDAO<T> {
return
this
.
_isInitialized
;
}
protected
_storage
:
T
;
get
storage
():
T
{
return
this
.
_storage
;
}
private
_updateEmitter
=
new
EventEmitter
();
get
updateEmitter
():
NodeJS
.
EventEmitter
{
return
this
.
_updateEmitter
;
}
protected
constructor
(
storage
:
T
)
{
this
.
_storage
=
storage
;
}
public
createDump
():
any
{
return
this
.
storage
;
}
protected
isEmptyVars
(...
variables
):
boolean
{
return
variables
.
length
>
0
&&
variables
.
filter
(
variable
=>
this
.
isEmptyVar
(
variable
)).
length
>
0
;
}
...
...
src/db/AssetDAO.ts
View file @
00cc97e1
import
{
ObjectID
,
ObjectId
}
from
'
bson
'
;
import
{
AssetEntity
}
from
'
../entities/AssetEntity
'
;
import
{
D
bCollection
,
DbEvent
}
from
'
../enums/DbOperation
'
;
import
{
IAsset
,
IAsset
Serialized
}
from
'
../interfaces/IAsset
'
;
import
LoggerService
from
'
../services/LoggerService
'
;
import
{
DeleteWriteOpResultObject
}
from
'
mongodb
'
;
import
{
D
ocument
}
from
'
mongoose
'
;
import
{
IAssetSerialized
}
from
'
../interfaces/IAsset
'
;
import
{
AssetModel
,
AssetModelItem
}
from
'
../models/AssetModel
'
;
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
import
DbDAO
from
'
./DbDAO
'
;
class
AssetDAO
extends
AbstractDAO
<
Array
<
AssetEntity
>>
{
class
AssetDAO
extends
AbstractDAO
{
constructor
()
{
super
(
[]
);
super
();
DbDAO
.
isDbAvailable
.
on
(
DbEvent
.
Connected
,
async
(
isConnected
)
=>
{
if
(
isConnected
)
{
const
cursor
=
DbDAO
.
readMany
(
DbCollection
.
Assets
,
{});
cursor
.
forEach
(
doc
=>
{
this
.
addAsset
(
doc
);
}).
then
(()
=>
LoggerService
.
info
(
`
${
this
.
constructor
.
name
}
initialized with
${
this
.
storage
.
length
}
entries`
));
}
AssetModel
.
find
().
exec
().
then
(
assets
=>
{
assets
.
forEach
(
asset
=>
this
.
addAsset
(
asset
));
});
}
...
...
@@ -29,46 +23,32 @@ class AssetDAO extends AbstractDAO<Array<AssetEntity>> {
return
this
.
instance
;
}
public
addAsset
(
document
:
IAssetSerialized
):
void
{
if
(
this
.
getAssetById
(
new
ObjectId
(
document
.
id
)))
{
throw
new
Error
(
`Duplicate asset insertion: (id:
${
document
.
id
}
, url:
${
document
.
url
}
)`
);
}
const
asset
=
new
AssetEntity
(
document
);
this
.
storage
.
push
(
asset
);
this
.
updateEmitter
.
emit
(
DbEvent
.
Create
,
asset
);
public
addAsset
(
document
:
IAssetSerialized
):
Promise
<
Document
&
AssetModelItem
>
{
return
AssetModel
.
create
(
document
);
}
public
updateAsset
(
id
:
ObjectId
,
updatedFields
:
any
):
void
{
const
asset
=
this
.
getAssetById
(
id
);
if
(
!
asset
)
{
throw
new
Error
(
`Unkown updated quiz:
${
id
.
toHexString
()}
`
);
}
Object
.
keys
(
updatedFields
).
forEach
(
key
=>
asset
[
key
]
=
updatedFields
[
key
]);
this
.
updateEmitter
.
emit
(
DbEvent
.
Change
,
asset
);
public
updateAsset
(
id
:
ObjectId
,
updatedFields
:
any
):
Promise
<
Document
&
AssetModelItem
>
{
return
AssetModel
.
findOneAndUpdate
(
id
,
updatedFields
).
exec
();
}
public
removeAllAssets
():
void
{
this
.
storage
.
forEach
(
asset
=>
this
.
updateEmitter
.
emit
(
DbEvent
.
Delete
,
asset
));
this
.
storage
.
splice
(
0
,
this
.
storage
.
length
);
public
removeAllAssets
():
Promise
<
DeleteWriteOpResultObject
[
'
result
'
]
&
{
deletedCount
?:
number
}
>
{
return
AssetModel
.
deleteMany
({}).
exec
();
}
public
removeAsset
(
id
:
ObjectId
):
void
{
this
.
storage
.
splice
(
this
.
storage
.
findIndex
(
asset
=>
asset
.
id
.
equals
(
id
)),
1
);
public
removeAsset
(
id
:
ObjectId
):
Promise
<
DeleteWriteOpResultObject
[
'
result
'
]
&
{
deletedCount
?:
number
}
>
{
return
AssetModel
.
deleteOne
({
_id
:
id
}).
exec
(
);
}
public
getAssetByDigest
(
digest
:
string
):
IAsset
{
return
this
.
storage
.
find
(
val
=>
val
.
digest
===
digest
);
public
getAssetByDigest
(
digest
:
string
):
Promise
<
AssetModelItem
>
{
return
AssetModel
.
findOne
({
digest
}).
exec
(
);
}
public
getAssetByUrl
(
url
:
string
):
IAsset
{
return
this
.
storage
.
find
(
asset
=>
asset
.
url
===
url
);
public
getAssetByUrl
(
url
:
string
):
Promise
<
AssetModelItem
>
{
return
AssetModel
.
findOne
({
url
}).
exec
(
);
}
private
getAssetById
(
id
:
ObjectID
):
IAsset
{
return
this
.
storage
.
find
(
asset
=>
asset
.
id
.
equals
(
id
)
);
private
getAssetById
(
id
:
ObjectID
):
Promise
<
AssetModelItem
>
{
return
AssetModel
.
findById
({
id
}).
exec
(
);
}
}
...
...
src/db/CasDAO.ts
View file @
00cc97e1
import
{
ICasData
}
from
'
../interfaces/users/ICasData
'
;
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
class
CasDAO
extends
AbstractDAO
<
{
[
key
:
string
]:
ICasData
}
>
{
class
CasDAO
extends
AbstractDAO
{
private
_storage
:
object
=
{};
constructor
()
{
super
({})
;
get
storage
():
object
{
return
this
.
_storage
;
}
public
static
getInstance
():
CasDAO
{
...
...
src/db/DbDAO.ts
View file @
00cc97e1
import
{
EventEmitter
}
from
'
events
'
;
import
{
Cursor
,
DeleteWriteOpResultObject
,
FilterQuery
,
FindOneOptions
,
InsertOneWriteOpResult
,
UpdateWriteOpResult
}
from
'
mongodb
'
;
import
{
Connection
}
from
'
mongoose
'
;
import
{
DbCollection
,
DbEvent
}
from
'
../enums/DbOperation
'
;
import
{
IDbObject
}
from
'
../interfaces/database/IDbObject
'
;
import
{
DbCollection
}
from
'
../enums/DbOperation
'
;
import
LoggerService
from
'
../services/LoggerService
'
;
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
import
MongoDBConnector
from
'
./MongoDBConnector
'
;
class
DbDAO
extends
AbstractDAO
<
object
>
{
class
DbDAO
extends
AbstractDAO
{
private
static
DB_RECONNECT_INTERVAL
=
1000
*
60
*
5
;
// 5 Minutes
public
readonly
DB
=
MongoDBConnector
.
dbName
;
...
...
@@ -17,86 +14,19 @@ class DbDAO extends AbstractDAO<object> {
return
this
.
_dbCon
;
}
private
_isDbAvailable
=
new
EventEmitter
();
get
isDbAvailable
():
EventEmitter
{
return
this
.
_isDbAvailable
;
}
private
_isConnected
=
false
;
constructor
(
_storage
:
object
)
{
super
(
_storage
);
constructor
()
{
super
();
this
.
connectToDb
();
}
public
static
getInstance
():
DbDAO
{
if
(
!
this
.
instance
)
{
this
.
instance
=
new
DbDAO
(
{}
);
this
.
instance
=
new
DbDAO
();
}
return
this
.
instance
;
}
public
create
(
collection
:
string
,
elem
:
IDbObject
|
object
):
Promise
<
InsertOneWriteOpResult
<
any
>>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
insertOne
(
elem
);
}
public
readOne
(
collection
:
string
,
query
:
FilterQuery
<
any
>
,
options
?:
FindOneOptions
):
Promise
<
any
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
findOne
(
query
,
options
);
}
public
readMany
(
collection
:
string
,
query
:
FilterQuery
<
any
>
):
Cursor
<
any
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
find
(
query
);
}
public
updateOne
(
collection
:
string
,
query
:
FilterQuery
<
any
>
,
update
:
object
):
Promise
<
UpdateWriteOpResult
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
updateOne
(
query
,
{
$set
:
update
});
}
public
updateMany
(
collection
:
string
,
query
:
FilterQuery
<
any
>
,
update
:
object
):
Promise
<
UpdateWriteOpResult
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
updateMany
(
query
,
{
$set
:
update
});
}
public
deleteOne
(
collection
:
string
,
query
:
FilterQuery
<
any
>
):
Promise
<
DeleteWriteOpResultObject
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
deleteOne
(
query
);
}
public
deleteMany
(
collection
:
string
,
query
:
FilterQuery
<
any
>
):
Promise
<
DeleteWriteOpResultObject
>
{
if
(
!
this
.
_isConnected
||
!
this
.
_dbCon
)
{
return
;
}
return
this
.
_dbCon
.
collection
(
collection
).
deleteMany
(
query
);
}
public
clearStorage
():
void
{
}
private
connectToDb
():
Promise
<
void
>
{
return
MongoDBConnector
.
connect
(
this
.
DB
).
then
((
db
:
Connection
)
=>
{
this
.
_dbCon
=
db
;
...
...
@@ -115,20 +45,11 @@ class DbDAO extends AbstractDAO<object> {
});
LoggerService
.
info
(
`Db connected`
);
this
.
_isConnected
=
true
;
this
.
_isDbAvailable
.
emit
(
DbEvent
.
Connected
,
true
);
db
.
on
(
'
error
'
,
()
=>
{
this
.
_isDbAvailable
.
emit
(
DbEvent
.
Connected
,
false
);
});
db
.
on
(
'
error
'
,
()
=>
{});
}).
catch
((
error
)
=>
{
LoggerService
.
error
(
`Db connection failed with error
${
error
}
, will retry in
${
DbDAO
.
DB_RECONNECT_INTERVAL
/
1000
}
seconds`
);
this
.
_isConnected
=
false
;
this
.
_isDbAvailable
.
emit
(
DbEvent
.
Connected
,
false
);
setTimeout
(
this
.
connectToDb
.
bind
(
this
),
DbDAO
.
DB_RECONNECT_INTERVAL
);
});
}
...
...
src/db/I18nDAO.ts
View file @
00cc97e1
...
...
@@ -6,14 +6,20 @@ import LoggerService from '../services/LoggerService';
import
{
availableLangs
}
from
'
../statistics
'
;
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
class
I18nDAO
extends
AbstractDAO
<
object
>
{
class
I18nDAO
extends
AbstractDAO
{
private
_storage
:
object
;
get
storage
():
object
{
return
this
.
_storage
;
}
private
readonly
mergeRequestTitle
=
'
WIP: Update i18n keys
'
;
private
readonly
commitMessage
=
'
Updates i18n keys
'
;
private
readonly
gitlabAccessToken
=
process
.
env
.
GITLAB_TOKEN
;
constructor
(
storage
:
object
)
{
super
(
storage
);
super
();
this
.
_storage
=
storage
;
}
public
static
getInstance
():
I18nDAO
{
...
...
src/db/MathjaxDAO.ts
View file @
00cc97e1
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
class
MathjaxDAO
extends
AbstractDAO
<
object
>
{
class
MathjaxDAO
extends
AbstractDAO
{
private
_storage
:
object
=
{};
constructor
()
{
super
({})
;
get
storage
():
object
{
return
this
.
_storage
;
}
public
static
getInstance
():
MathjaxDAO
{
...
...
src/db/MemberDAO.ts
View file @
00cc97e1
///<reference path="../lib/regExpEscape.ts" />
import
{
ObjectId
}
from
'
bson
'
;
import
{
MemberEntity
}
from
'
../entities/member/MemberEntity
'
;
import
{
QuizEntity
}
from
'
../entities/quiz/QuizEntity
'
;
import
{
DbCollection
,
DbEvent
}
from
'
../enums/DbOperation
'
;
import
{
Document
}
from
'
mongoose
'
;
import
{
MessageProtocol
,
StatusProtocol
}
from
'
../enums/Message
'
;
import
{
IMemberSerialized
}
from
'
../interfaces/entities/Member/IMemberSerialized
'
;
import
{
IQuiz
Entity
}
from
'
../interfaces/quizzes/IQuizEntity
'
;
import
LoggerService
from
'
../services/LoggerService
'
;
import
{
IQuiz
Response
}
from
'
../interfaces/quizzes/IQuizResponse
'
;
import
{
MemberModel
,
MemberModelItem
}
from
'
../models/member/MemberModel
'
;
import
{
AbstractDAO
}
from
'
./AbstractDAO
'
;
import
DbDAO
from
'
./DbDAO
'
;
import
AMQPConnector
from
'
./AMQPConnector
'
;
import
QuizDAO
from
'
./quiz/QuizDAO
'
;
class
MemberDAO
extends
AbstractDAO
<
Array
<
MemberEntity
>>
{
class
MemberDAO
extends
AbstractDAO
{
public
static
getInstance
():
MemberDAO
{
if
(
!
this
.
instance
)
{
...
...
@@ -21,105 +18,192 @@ class MemberDAO extends AbstractDAO<Array<MemberEntity>> {
return
this
.
instance
;
}
constructor
()
{
super
([]);
DbDAO
.
isDbAvailable
.
on
(
DbEvent
.
Connected
,
async
(
isConnected
)
=>
{
if
(
isConnected
)
{
const
cursor
=
DbDAO
.
readMany
(
DbCollection
.
Members
,
{});
cursor
.
forEach
(
doc
=>
{
this
.
addMember
(
doc
);
}).
then
(()
=>
LoggerService
.
info
(
`
${
this
.
constructor
.
name
}
initialized with
${
this
.
storage
.
length
}
entries`
));
}
});
}
public
getMemberByName
(
name
:
string
):
MemberEntity
{
return
this
.
storage
.
find
(
val
=>
val
.
name
===
name
);
public
getMemberByName
(
name
:
string
):
Promise
<
Document
&
MemberModelItem
>
{
return
MemberModel
.
findOne
({
name
}).
exec
();
}
public
a
ddMember
(
memberSerialized
:
IMemberSerialized
):
void
{
if
(
this
.
getMemberById
(
memberSerialized
.
id
))
{
public
a
sync
addMember
(
memberSerialized
:
IMemberSerialized
):
Promise
<
Document
&
MemberModelItem
>
{
if
(
memberSerialized
.
id
&&
this
.
getMemberById
(
memberSerialized
.
id
))
{
throw
new
Error
(
`Duplicate member insertion: (name:
${
memberSerialized
.
name
}
, id:
${
memberSerialized
.
id
}
)`
);
}
const
member
=
new
MemberEntity
(
memberSerialized
);
this
.
storage
.
push
(
member
);
this
.
updateEmitter
.
emit
(
DbEvent
.
Create
,
member
);
if
(
QuizDAO
.
isInitialized
)
{
this
.
notifyQuizDAO
(
member
);
}
else
{
QuizDAO
.
updateEmitter
.
once
(
DbEvent
.
Initialized
,
()
=>
this
.
notifyQuizDAO
(
member
));
}
}
public
updateMember
(
id
:
ObjectId
,
updatedFields
:
{
[
key
:
string
]:
any
}):
void
{
const
member
=
this
.
getMemberById
(
id
);
if
(
!
member
)
{
throw
new
Error
(
`Unknown updated member:
${
id
.
toHexString
()}
`
);
}
const
doc
=
await
MemberModel
.
create
(
memberSerialized
);
Object
.
keys
(
updatedFields
).
forEach
(
key
=>
member
[
key
]
=
updatedFields
[
key
]);
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
memberSerialized
.
currentQuizName
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
Added
,
payload
:
{
member
:
doc
.
toJSON
()
},
})));
this
.
updateEmitter
.
emit
(
DbEvent
.
Change
,
member
)
;
return
doc
;
}
public
removeAllMembers
():
void
{
this
.
storage
.
forEach
(
member
=>
{
this
.
updateEmitter
.
emit
(
DbEvent
.
Delete
,
member
);
QuizDAO
.
getQuizByName
(
member
.
currentQuizName
).
onMemberRemoved
(
member
);
public
async
removeAllMembers
():
Promise
<
void
>
{
const
members
=
await
MemberModel
.
find
().
exec
();
members
.
forEach
(
member
=>
{
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
member
.
currentQuizName
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
Removed
,
payload
:
{
name
:
member
.
name
},
})));
});
this
.
storage
.
splice
(
0
,
this
.
storage
.
length
);
}
public
removeMember
(
id
:
ObjectId
|
string
):
void
{
const
members
=
this
.
storage
.
splice
(
this
.
storage
.
findIndex
(
val
=>
val
.
id
.
equals
(
id
)),
1
);
await
MemberModel
.
deleteMany
({}).
exec
();
}
if
(
members
.
length
)
{
this
.
updateEmitter
.
emit
(
DbEvent
.
Delete
,
members
[
0
]
);
const
quiz
=
QuizDAO
.
getQuizByName
(
members
[
0
].
currentQuizName
);
if
(
quiz
)
{
quiz
.
onMemberRemoved
(
members
[
0
]);
}
}
public
async
removeMember
(
id
:
ObjectId
|
string
):
Promise
<
void
>
{
const
member
=
await
MemberModel
.
findByIdAndRemove
(
id
).
exec
(
);
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
member
.
currentQuizName
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
Removed
,
payload
:
{
name
:
member
.
name
},
}
)));
}
public
getMembersOfQuiz
(
quizName
:
string
):
Array
<
MemberEntity
>
{
return
this
.
storage
.
filter
(
val
=>
!!
val
.
currentQuizName
.
match
(
new
RegExp
(
`^
${
RegExp
.
escape
(
quizName
)}
$`
,
'
i
'
))
);
public
getMembersOfQuiz
(
quizName
:
string
):
Promise
<
Array
<
Document
&
MemberModelItem
>
>
{
return
MemberModel
.
find
({
currentQuizName
:
quizName
}).
exec
(
);
}
public
getMemberByToken
(
token
:
string
):
MemberEntity
{
return
this
.
storage
.
find
(
val
=>
val
.
token
===
token
);
public
getMemberByToken
(
token
:
string
):
Promise
<
Document
&
MemberModelItem
>
{
return
MemberModel
.
findOne
({
token
}).
exec
(
);
}
public
removeMembersOfQuiz
(
removedQuiz
:
QuizEntity
|
IQuizEntity
):
void
{
DbDAO
.
deleteMany
(
DbCollection
.
Members
,
{
currentQuizName
:
removedQuiz
.
name
});
public
async
removeMembersOfQuiz
(
quizName
:
string
):
Promise
<
void
>
{
const
members
=
await
MemberModel
.
find
({
currentQuizName
:
quizName
}).
exec
();
members
.
forEach
(
member
=>
{
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
member
.
currentQuizName
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
Removed
,
payload
:
{
name
:
member
.
name
},
})));
});
await
MemberModel
.
deleteMany
({
currentQuizName
:
quizName
}).
exec
();
}
public
getMemberAmountPerQuizGroup
(
name
:
string
,
groups
:
Array
<
string
>
):
object
{
public
async
getMemberAmountPerQuizGroup
(
name
:
string
,
groups
:
Array
<
string
>
):
Promise
<
object
>
{
const
result
=
{};
groups
.
forEach
(
g
=>
result
[
g
]
=
0
);
this
.
getMembersOfQuiz
(
name
).
forEach
(
member
=>
{
(
await
this
.
getMembersOfQuiz
(
name
)
).
forEach
(
member
=>
{
result
[
member
.
groupName
]
++
;
});
return
result
;
}
private
notifyQuizDAO
(
member
:
MemberEntity
):
void
{
const
quiz
=
QuizDAO
.
getQuizByName
(
member
.
currentQuizName
);
if
(
!
quiz
)
{
console
.
error
(
`The quiz '
${
member
.
currentQuizName
}
' for the member
${
member
.
name
}
could not be found. Removing member.`
);
DbDAO
.
deleteOne
(
DbCollection
.
Members
,
{
_id
:
member
.
id
});
return
;
public
resetMembersOfQuiz
(
name
:
string
,
questionAmount
:
number
):
Promise
<
any
>
{
return
MemberModel
.
updateMany
({
currentQuizName
:
name
},
{
responses
:
this
.
generateResponseForQuiz
(
questionAmount
),
}).
exec
();
}
public
async
setReadingConfirmation
(
member
:
Document
&
MemberModelItem
):
Promise
<
void
>
{
const
quiz
=
await
QuizDAO
.
getQuizByName
(
member
.
currentQuizName
);
member
.
responses
[
quiz
.
currentQuestionIndex
].
readingConfirmation
=
true
;
const
queryPath
=
`responses.
${
quiz
.
currentQuestionIndex
}
.readingConfirmation`
;
await
MemberModel
.
updateOne
({
_id
:
member
.
_id
},
{
[
queryPath
]:
true
}).
exec
();
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
quiz
.
name
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
UpdatedResponse
,
payload
:
{
nickname
:
member
.
name
,
questionIndex
:
quiz
.
currentQuestionIndex
,
update
:
{
readingConfirmation
:
true
},
},
})));
}
public
async
setConfidenceValue
(
member
:
Document
&
MemberModelItem
,
confidenceValue
:
number
):
Promise
<
void
>
{
const
quiz
=
await
QuizDAO
.
getQuizByName
(
member
.
currentQuizName
);
member
.
responses
[
quiz
.
currentQuestionIndex
].
confidence
=
confidenceValue
;
const
queryPath
=
`responses.
${
quiz
.
currentQuestionIndex
}
.confidence`
;
await
MemberModel
.
updateOne
({
_id
:
member
.
_id
},
{
[
queryPath
]:
confidenceValue
}).
exec
();
AMQPConnector
.
channel
.
publish
(
AMQPConnector
.
buildQuizExchange
(
quiz
.
name
),
'
.*
'
,
Buffer
.
from
(
JSON
.
stringify
({
status
:
StatusProtocol
.
Success
,
step
:
MessageProtocol
.
UpdatedResponse
,
payload
:
{
nickname
:
member
.
name
,
questionIndex
:
quiz
.
currentQuestionIndex
,
update
:
{
confidence
:
confidenceValue
},
},
})));