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
it4all
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
2
Issues
2
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Björn Eyselein
it4all
Commits
d1ed1734
Commit
d1ed1734
authored
Oct 07, 2020
by
Björn Eyselein
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add i18n
parent
09117bfc
Pipeline
#26949
passed with stages
in 3 minutes and 53 seconds
Changes
13
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
497 additions
and
117 deletions
+497
-117
.scalafmt.conf
.scalafmt.conf
+1
-1
app/controllers/Controller.scala
app/controllers/Controller.scala
+82
-85
conf/routes
conf/routes
+8
-7
ui/angular.json
ui/angular.json
+7
-0
ui/messages.xlf
ui/messages.xlf
+91
-0
ui/package-lock.json
ui/package-lock.json
+160
-0
ui/package.json
ui/package.json
+1
-0
ui/src/app/app.component.html
ui/src/app/app.component.html
+12
-5
ui/src/app/app.component.ts
ui/src/app/app.component.ts
+8
-1
ui/src/app/user_management/login-form/login-form.component.html
.../app/user_management/login-form/login-form.component.html
+9
-7
ui/src/app/user_management/register-form/register-form.component.html
...ser_management/register-form/register-form.component.html
+11
-11
ui/src/locales/messages.en.xlf
ui/src/locales/messages.en.xlf
+103
-0
ui/src/polyfills.ts
ui/src/polyfills.ts
+4
-0
No files found.
.scalafmt.conf
View file @
d1ed1734
version
=
2
.
7
.
0
version
=
2
.
7
.
4
align
=
more
maxColumn
=
120
...
...
app/controllers/Controller.scala
View file @
d1ed1734
...
...
@@ -4,14 +4,14 @@ import java.util.UUID
import
com.github.t3hnar.bcrypt._
import
javax.inject.
{
Inject
,
Singleton
}
import
model._
import
model.graphql.
{
GraphQLModel
,
GraphQLRequest
}
import
model.lti.BasicLtiLaunchRequest
import
model.
{
JsonProtocols
,
LoggedInUser
,
LoggedInUserWithToken
,
User
,
UserCredentials
}
import
pdi.jwt.JwtSession
import
play.api.
{
Configuration
,
Logger
}
import
play.api.http.HttpErrorHandler
import
play.api.libs.json._
import
play.api.mvc._
import
play.api.
{
Configuration
,
Logger
}
import
play.modules.reactivemongo.
{
MongoController
,
ReactiveMongoApi
,
ReactiveMongoComponents
}
import
sangria.execution.
{
ErrorWithResolver
,
Executor
,
QueryAnalysisError
}
import
sangria.marshalling.playJson._
...
...
@@ -36,109 +36,106 @@ class Controller @Inject() (
private
val
logger
=
Logger
(
classOf
[
Controller
])
def
index
:
Action
[
AnyContent
]
=
assets
.
at
(
"index.html
"
)
private
val
apiPrefix
=
configuration
.
get
[
String
](
"apiPrefix
"
)
def
graphiql
:
Action
[
AnyContent
]
=
Action
{
implicit
request
=>
Ok
(
views
.
html
.
graphiql
())
}
def
index
:
Action
[
AnyContent
]
=
Action
{
implicit
request
=>
Redirect
(
routes
.
Controller
.
langIndex
(
"de"
))
}
def
assetOrDefault
(
resource
:
String
)
:
Action
[
AnyContent
]
=
if
(
resource
.
startsWith
(
configuration
.
get
[
String
](
"apiPrefix"
)))
{
Action
.
async
(
r
=>
errorHandler
.
onClientError
(
r
,
NOT_FOUND
,
"Not found"
))
}
else
{
if
(
resource
.
contains
(
"."
))
assets
.
at
(
resource
)
else
index
}
def
langIndex
(
lang
:
String
=
"de"
)
:
Action
[
AnyContent
]
=
assets
.
at
(
s
"$lang/index.html"
)
def
graphiql
:
Action
[
AnyContent
]
=
Action
{
implicit
request
=>
Ok
(
views
.
html
.
graphiql
())
}
def
assetOrDefault
(
lang
:
String
,
resource
:
String
)
:
Action
[
AnyContent
]
=
if
(
resource
.
startsWith
(
apiPrefix
))
{
Action
.
async
(
r
=>
errorHandler
.
onClientError
(
r
,
NOT_FOUND
,
"Not found"
))
}
else
{
if
(
resource
.
contains
(
"."
))
assets
.
at
(
s
"$lang/$resource"
)
else
langIndex
(
lang
)
}
private
implicit
val
graphQLRequestFormat
:
Format
[
GraphQLRequest
]
=
Json
.
format
def
graphql
:
Action
[
GraphQLRequest
]
=
Action
.
async
(
parse
.
json
[
GraphQLRequest
])
{
implicit
request
=>
QueryParser
.
parse
(
request
.
body
.
query
)
match
{
case
Failure
(
error
)
=>
Future
.
successful
(
BadRequest
(
Json
.
obj
(
"error"
->
error
.
getMessage
)))
case
Success
(
queryAst
)
=>
Executor
.
execute
(
schema
,
queryAst
,
operationName
=
request
.
body
.
operationName
,
variables
=
request
.
body
.
variables
.
getOrElse
(
Json
.
obj
())
)
.
map
(
Ok
(
_
))
.
recover
{
case
error
:
QueryAnalysisError
=>
logger
.
error
(
"There has been a query error"
,
error
)
BadRequest
(
error
.
resolveError
)
case
error
:
ErrorWithResolver
=>
InternalServerError
(
error
.
resolveError
)
}
}
def
graphql
:
Action
[
GraphQLRequest
]
=
Action
.
async
(
parse
.
json
[
GraphQLRequest
])
{
implicit
request
=>
QueryParser
.
parse
(
request
.
body
.
query
)
match
{
case
Failure
(
error
)
=>
Future
.
successful
(
BadRequest
(
Json
.
obj
(
"error"
->
error
.
getMessage
)))
case
Success
(
queryAst
)
=>
Executor
.
execute
(
schema
,
queryAst
,
operationName
=
request
.
body
.
operationName
,
variables
=
request
.
body
.
variables
.
getOrElse
(
Json
.
obj
())
)
.
map
(
Ok
(
_
))
.
recover
{
case
error
:
QueryAnalysisError
=>
logger
.
error
(
"There has been a query error"
,
error
)
BadRequest
(
error
.
resolveError
)
case
error
:
ErrorWithResolver
=>
InternalServerError
(
error
.
resolveError
)
}
}
}
// Json Web Token session
private
val
jwtHashesToClaim
:
MutableMap
[
UUID
,
(
JwtSession
,
LoggedInUser
)]
=
MutableMap
.
empty
private
def
getOrCreateUser
(
username
:
String
)
:
Future
[
LoggedInUser
]
=
futureUserByUsername
(
username
)
.
flatMap
{
case
Some
(
u
)
=>
Future
(
u
)
case
None
=>
val
newUser
=
User
(
username
)
futureInsertUser
(
newUser
).
map
{
_
=>
newUser
}
}
.
map
{
user
=>
LoggedInUser
(
user
.
username
)
}
def
ltiHoneypot
:
Action
[
AnyContent
]
=
Action
.
async
{
request
=>
request
.
body
.
asFormUrlEncoded
match
{
case
None
=>
Future
(
BadRequest
(
"TODO!"
))
case
Some
(
data
)
=>
val
basicLtiRequest
=
BasicLtiLaunchRequest
.
fromRequest
(
data
)
private
def
getOrCreateUser
(
username
:
String
)
:
Future
[
LoggedInUser
]
=
futureUserByUsername
(
username
)
.
flatMap
{
case
Some
(
u
)
=>
Future
(
u
)
case
None
=>
val
newUser
=
User
(
username
)
futureInsertUser
(
newUser
).
map
{
_
=>
newUser
}
}
.
map
{
user
=>
LoggedInUser
(
user
.
username
)
}
getOrCreateUser
(
basicLtiRequest
.
ltiExt
.
username
)
map
{
user
=>
val
uuid
=
UUID
.
randomUUID
()
def
ltiHoneypot
:
Action
[
AnyContent
]
=
Action
.
async
{
request
=>
request
.
body
.
asFormUrlEncoded
match
{
case
None
=>
Future
(
BadRequest
(
"TODO!"
))
case
Some
(
data
)
=>
val
basicLtiRequest
=
BasicLtiLaunchRequest
.
fromRequest
(
data
)
jwtHashesToClaim
.
put
(
uuid
,
(
createJwtSession
(
user
),
user
))
getOrCreateUser
(
basicLtiRequest
.
ltiExt
.
username
)
map
{
user
=>
val
uuid
=
UUID
.
randomUUID
()
val
redirectUrl
=
s
"/lti/${uuid.toString}"
jwtHashesToClaim
.
put
(
uuid
,
(
createJwtSession
(
user
),
user
))
Redirect
(
redirectUrl
).
withNewSession
}
}
Redirect
(
s
"/lti/${uuid.toString}"
).
withNewSession
}
}
}
def
claimJsonWebToken
(
uuidStr
:
String
)
:
Action
[
AnyContent
]
=
Action
{
implicit
request
=>
jwtHashesToClaim
.
remove
(
UUID
.
fromString
(
uuidStr
))
match
{
case
None
=>
NotFound
(
""
)
case
Some
((
jwtSession
,
user
))
=>
val
loggedInUserWithToken
=
LoggedInUserWithToken
(
user
,
jwtSession
.
serialize
)
def
claimJsonWebToken
(
uuidStr
:
String
)
:
Action
[
AnyContent
]
=
Action
{
implicit
request
=>
jwtHashesToClaim
.
remove
(
UUID
.
fromString
(
uuidStr
))
match
{
case
None
=>
NotFound
(
""
)
case
Some
((
jwtSession
,
user
))
=>
val
loggedInUserWithToken
=
LoggedInUserWithToken
(
user
,
jwtSession
.
serialize
)
Ok
(
writeJsonWebToken
(
loggedInUserWithToken
))
}
Ok
(
writeJsonWebToken
(
loggedInUserWithToken
))
}
def
apiAuthenticate
:
Action
[
UserCredentials
]
=
Action
.
async
(
parse
.
json
[
UserCredentials
](
JsonProtocols
.
userCredentialsFormat
))
{
implicit
request
=>
futureUserByUsername
(
request
.
body
.
username
).
map
{
case
None
=>
BadRequest
(
"Invalid username!"
)
case
Some
(
user
)
=>
user
.
pwHash
match
{
case
None
=>
BadRequest
(
"No password found!"
)
case
Some
(
pwHash
)
=>
if
(
request
.
body
.
password
.
isBcryptedBounded
(
pwHash
))
{
val
loggedInUser
=
LoggedInUser
(
user
.
username
)
Ok
(
writeJsonWebToken
(
LoggedInUserWithToken
(
loggedInUser
,
createJwtSession
(
loggedInUser
).
serialize
)
)
}
private
implicit
val
userCredentialsFormat
:
OFormat
[
UserCredentials
]
=
JsonProtocols
.
userCredentialsFormat
def
apiAuthenticate
:
Action
[
UserCredentials
]
=
Action
.
async
(
parse
.
json
[
UserCredentials
])
{
implicit
request
=>
futureUserByUsername
(
request
.
body
.
username
).
map
{
case
None
=>
BadRequest
(
"Invalid username!"
)
case
Some
(
user
)
=>
user
.
pwHash
match
{
case
None
=>
BadRequest
(
"No password found!"
)
case
Some
(
pwHash
)
=>
if
(
request
.
body
.
password
.
isBcryptedBounded
(
pwHash
))
{
val
loggedInUser
=
LoggedInUser
(
user
.
username
)
Ok
(
writeJsonWebToken
(
LoggedInUserWithToken
(
loggedInUser
,
createJwtSession
(
loggedInUser
).
serialize
)
)
}
else
{
BadRequest
(
"Password invalid!"
)
}
}
}
)
}
else
{
BadRequest
(
"Password invalid!"
)
}
}
}
}
}
conf/routes
View file @
d1ed1734
# Client
GET / controllers.Controller.index
GET / controllers.Controller.index
GET /:lang/ controllers.Controller.langIndex(lang: String)
# LTI
POST /api/lti controllers.Controller.ltiHoneypot
GET /api/claimWebToken/:uuidStr controllers.Controller.claimJsonWebToken(uuidStr: String)
POST /api/lti
controllers.Controller.ltiHoneypot
GET /api/claimWebToken/:uuidStr
controllers.Controller.claimJsonWebToken(uuidStr: String)
# GraphQL
GET /graphiql controllers.Controller.graphiql
POST /api/graphql controllers.Controller.graphql
GET /graphiql
controllers.Controller.graphiql
POST /api/graphql
controllers.Controller.graphql
# Map static resources from the /public folder to the /assets URL path
GET /
*file controllers.Controller.assetOrDefault(file
)
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
GET /
:lang/*file controllers.Controller.assetOrDefault(lang: String, file: String
)
GET /assets/*file
controllers.Assets.versioned(path="/public", file: Asset)
ui/angular.json
View file @
d1ed1734
...
...
@@ -14,6 +14,12 @@
"root"
:
""
,
"sourceRoot"
:
"src"
,
"prefix"
:
"it4all"
,
"i18n"
:
{
"sourceLocale"
:
"de"
,
"locales"
:
{
"en"
:
"src/locales/messages.en.xlf"
}
},
"architect"
:
{
"build"
:
{
"builder"
:
"@angular-devkit/build-angular:browser"
,
...
...
@@ -23,6 +29,7 @@
"main"
:
"src/main.ts"
,
"polyfills"
:
"src/polyfills.ts"
,
"tsConfig"
:
"tsconfig.app.json"
,
"localize"
:
true
,
"aot"
:
true
,
"assets"
:
[
"src/favicon.ico"
,
...
...
ui/messages.xlf
0 → 100644
View file @
d1ed1734
<?xml version="1.0" encoding="UTF-8" ?>
<xliff
version=
"1.2"
xmlns=
"urn:oasis:names:tc:xliff:document:1.2"
>
<file
source-language=
"de"
datatype=
"plaintext"
original=
"ng2.template"
>
<body>
<trans-unit
id=
"1451a321cb66a6785cf15b8d0ada9a620071c6d9"
datatype=
"html"
>
<source>
Sprache
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
9
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"140d9a7d0795beace6491c45566f6dd295e94999"
datatype=
"html"
>
<source>
Impressum
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
16
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"c47b6066a02b6434f0cd861e792d9ceebbe59235"
datatype=
"html"
>
<source>
Datenschutz
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
19
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"bb694b49d408265c91c62799c2b3a7e3151c824d"
datatype=
"html"
>
<source>
Logout
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
25
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9"
datatype=
"html"
>
<source>
Login
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
29
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"9adf7f973ff81475f3622c2a38a1e69a7dd433af"
datatype=
"html"
>
<source>
Registrieren
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/app.component.html
</context>
<context
context-type=
"linenumber"
>
30
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"227094aed22780e90303b4157e24f11db9e2c959"
datatype=
"html"
>
<source>
Nutzername
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/login-form/login-form.component.html
</context>
<context
context-type=
"linenumber"
>
8
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"2070ebb54f893c60114f49f46d7e1a6f27b66610"
datatype=
"html"
>
<source>
Passwort
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/login-form/login-form.component.html
</context>
<context
context-type=
"linenumber"
>
16
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"2b62e7607770b87f7d55db9cf324fadc947c3194"
datatype=
"html"
>
<source>
Kombination aus Nutzername und Password ist nicht valide
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/login-form/login-form.component.html
</context>
<context
context-type=
"linenumber"
>
24
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"55de98c522f30988612c529201c4f9b94fa4bb86"
datatype=
"html"
>
<source>
Passwort wiederholen
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/register-form/register-form.component.html
</context>
<context
context-type=
"linenumber"
>
24
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"222db068c4ffb87ad38f26d295e23625cf38f859"
datatype=
"html"
>
<source>
Nutzer
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/register-form/register-form.component.html
</context>
<context
context-type=
"linenumber"
>
32
</context>
</context-group>
</trans-unit>
<trans-unit
id=
"ef39cbd36cd466586bdca3cffdb4866339f4aed2"
datatype=
"html"
>
<source>
wurde erfolgreich registriert
</source>
<context-group
purpose=
"location"
>
<context
context-type=
"sourcefile"
>
src/app/user_management/register-form/register-form.component.html
</context>
<context
context-type=
"linenumber"
>
32
</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
ui/package-lock.json
View file @
d1ed1734
...
...
@@ -834,6 +834,166 @@
"integrity": "sha512-+CsL/HWlja9mxqyvTTqP/rpxjVeuICmTHyfAKxqpq0488N7KTRRLvWXDoL5uwc+lzqhMsaXDasReMO+ATUAUrg==",
"dev": true
},
"@angular/localize": {
"version": "10.1.4",
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-10.1.4.tgz",
"integrity": "sha512-10svhkQGButoMPMqE67u4rBluucBZzeMurm8IVK1IW5G0iW+Q65w6t3Jl0L5Qga/VUEXf1ZKbbb+y09AlZGvlA==",
"dev": true,
"requires": {
"@babel/core": "7.8.3",
"glob": "7.1.2",
"yargs": "15.3.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"dev": true,
"requires": {
"@babel/highlight": "^7.10.4"
}
},
"@babel/core": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz",
"integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.8.3",
"@babel/helpers": "^7.8.3",
"@babel/parser": "^7.8.3",
"@babel/template": "^7.8.3",
"@babel/traverse": "^7.8.3",
"@babel/types": "^7.8.3",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.0",
"lodash": "^4.17.13",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
"dev": true
},
"@babel/highlight": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/types": {
"version": "7.11.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz",
"integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
},
"dependencies": {
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
}
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"yargs": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz",
"integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==",
"dev": true,
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.0"
}
}
}
},
"@angular/platform-browser": {
"version": "10.1.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-10.1.4.tgz",
...
...
ui/package.json
View file @
d1ed1734
...
...
@@ -44,6 +44,7 @@
"@angular/cli"
:
"^10.1.4"
,
"@angular/compiler-cli"
:
"~10.1.4"
,
"@angular/language-service"
:
"~10.1.4"
,
"@angular/localize"
:
"^10.1.4"
,
"@graphql-codegen/add"
:
"^1.17.7"
,
"@graphql-codegen/cli"
:
"^1.17.8"
,
"@graphql-codegen/fragment-matcher"
:
"^1.17.8"
,
...
...
ui/src/app/app.component.html
View file @
d1ed1734
...
...
@@ -5,22 +5,29 @@
<div
class=
"navbar-menu"
>
<div
class=
"navbar-end"
>
<div
class=
"navbar-item has-dropdown is-hoverable"
>
<a
class=
"navbar-link"
i18n
>
Sprache
</a>
<div
class=
"navbar-dropdown"
>
<a
*ngFor=
"let lang of langs"
class=
"navbar-item"
href=
"/{{lang}}{{getCurrentUrl()}}"
>
{{lang}}
</a>
</div>
</div>
<a
class=
"navbar-item"
target=
"_blank"
href=
"https://www.uni-wuerzburg.de/sonstiges/impressum/"
>
Impressum
<span
i18n
>
Impressum
</span>
</a>
<a
class=
"navbar-item"
target=
"_blank"
href=
"https://www.uni-wuerzburg.de/sonstiges/datenschutz/"
>
Datenschutz
<span
i18n
>
Datenschutz
</span>
</a>
<div
class=
"navbar-item"
>
<ng-container
*ngIf=
"currentUser; else noUserBlock"
>
<button
(click)=
"logout()"
class=
"button is-light"
>
Logout {{currentUser.loggedInUser.username}}
</button>
<button
(click)=
"logout()"
class=
"button is-light"
>
<span
i18n
>
Logout
</span>
{{currentUser.loggedInUser.username}}
</button>
</ng-container>
<ng-template
#noUserBlock
>
<div
class=
"buttons"
>
<a
routerLink=
"/loginForm"
class=
"button is-light"
>
Login
</a>
<a
routerLink=
"/registerForm"
class=
"button is-light"
>
Registrieren
</a>
<a
routerLink=
"/loginForm"
class=
"button is-light"
i18n
>
Login
</a>
<a
routerLink=
"/registerForm"
class=
"button is-light"
i18n
>
Registrieren
</a>
</div>
</ng-template>
</div>
...
...
ui/src/app/app.component.ts
View file @
d1ed1734
import
{
Component
}
from
'
@angular/core
'
;
import
{
AuthenticationService
}
from
'
./_services/authentication.service
'
;
import
{
LoggedInUserWithTokenFragment
}
from
'
./_services/apollo_services
'
;
import
{
Router
}
from
"
@angular/router
"
;
@
Component
({
selector
:
'
it4all-root
'
,
...
...
@@ -8,9 +9,11 @@ import {LoggedInUserWithTokenFragment} from './_services/apollo_services';
})
export
class
AppComponent
{
readonly
langs
:
string
[]
=
[
"
de
"
,
"
en
"
];
currentUser
:
LoggedInUserWithTokenFragment
;
constructor
(
private
authenticationService
:
AuthenticationService
)
{
constructor
(
private
router
:
Router
,
private
authenticationService
:
AuthenticationService
)
{
this
.
authenticationService
.
currentUser
.
subscribe
((
u
)
=>
this
.
currentUser
=
u
);
}
...
...
@@ -18,4 +21,8 @@ export class AppComponent {
this
.
authenticationService
.
logout
();
}
getCurrentUrl
():
string
{
return
this
.
router
.
url
;
}
}
ui/src/app/user_management/login-form/login-form.component.html
View file @
d1ed1734
<div
class=
"container"
>
<h1
class=
"title is-3 has-text-centered"
>
Login
</h1>
<h1
class=
"title is-3 has-text-centered"
i18n
>
Login
</h1>
<form
[formGroup]=
"loginForm"
(ngSubmit)=
"onSubmit()"
>
<div
class=
"field"
>
<label
for=
"username"
class=
"label"
>
Nutzername
</label>
<label
for=
"username"
class=
"label"
>
<span
i18n
>
Nutzername
</span>
:
</label>
<div
class=
"control"
>
<input
type=
"text"
id=
"username"
formControlName=
"username"
class=
"input"
placeholder=
"Nutzername"
autofocus
>
<input
type=
"text"
id=
"username"
formControlName=
"username"
class=
"input"
placeholder=
"Nutzername"
i18n-placeholder
autofocus
>
</div>
</div>
<div
class=
"field"
>
<label
for=
"password"
class=
"label"
>
Passwort
</label>
<label
for=
"password"
class=
"label"
>
<span
i18n
>
Passwort
</span>
:
</label>
<div
class=
"control"
>
<input
type=
"password"
formControlName=
"password"
placeholder=
"Passwort"
id=
"password"
class=
"input"
>
<input
type=
"password"
formControlName=
"password"
placeholder=
"Passwort"
i18n-placeholder
id=
"password"
class=
"input"
>
</div>
</div>
<div
class=
"notification is-danger"
*ngIf=
"submitted && inValid"
>
Kombination aus Nutzername und Password ist nicht valide
!
<span
i18n
>
Kombination aus Nutzername und Password ist nicht valide
</span>
!
</div>
<br>
<div
class=
"field"
>
<div
class=
"control"
>
<button
class=
"button is-link"
>
Login
</button>
<button
class=
"button is-link"
i18n
>
Login
</button>
</div>
</div>
...
...
ui/src/app/user_management/register-form/register-form.component.html
View file @
d1ed1734