Merge branch 'master' into exam-office
This commit is contained in:
commit
517da054b1
34
CHANGELOG.md
34
CHANGELOG.md
@ -2,6 +2,40 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [6.10.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.9.0...v6.10.0) (2019-09-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **exams:** notifications wrt. registration ([ae27ff0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/ae27ff0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [6.9.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.8.0...v6.9.0) (2019-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **users:** allow customisation of displayed email address ([2f38278](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/2f38278)), closes [#459](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/459)
|
||||||
|
* **users:** allow customisation of userDisplayName ([a85f317](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a85f317)), closes [#346](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/346)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [6.8.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.7.0...v6.8.0) (2019-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **allocations:** better explain capped allocation bounds ([a890e34](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a890e34))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **allocations:** allow changing course capacity during allocation ([83e1c94](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/83e1c94))
|
||||||
|
* **allocations:** show bounds on assignments due to allocation ([91b249e](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/91b249e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [6.7.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.6.0...v6.7.0) (2019-09-12)
|
## [6.7.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.6.0...v6.7.0) (2019-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { Utility } from '../../core/utility';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const KEYCODE_ESCAPE = 27;
|
const KEYCODE_ESCAPE = 27;
|
||||||
const Z_INDEX_MODAL = 9999;
|
|
||||||
|
|
||||||
// INTERNAL (Uni2work specific) formats for formatting dates and/or times
|
// INTERNAL (Uni2work specific) formats for formatting dates and/or times
|
||||||
const FORM_DATE_FORMAT = {
|
const FORM_DATE_FORMAT = {
|
||||||
@ -140,16 +139,6 @@ export class Datepicker {
|
|||||||
// initialize tail.datetime (datepicker) instance
|
// initialize tail.datetime (datepicker) instance
|
||||||
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig });
|
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig });
|
||||||
|
|
||||||
// append the datepicker element (dt) to the form
|
|
||||||
this._element.form.appendChild(this.datepickerInstance.dt);
|
|
||||||
|
|
||||||
// if the input element is in any open modal, increase the z-index of the datepicker and set its position to fixed to avoid repositioning on page scroll
|
|
||||||
// FIXME: instead of setting the position to fixed, use absolute and reposition (decrease left)
|
|
||||||
if (this._element.closest('.modal--open')) {
|
|
||||||
this.datepickerInstance.dt.style.zIndex = Z_INDEX_MODAL;
|
|
||||||
this.datepickerInstance.dt.style.position = 'fixed';
|
|
||||||
}
|
|
||||||
|
|
||||||
// register this datepicker instance with the formID of the given element in the datepicker collection
|
// register this datepicker instance with the formID of the given element in the datepicker collection
|
||||||
const formID = this._element.form.id;
|
const formID = this._element.form.id;
|
||||||
const elemID = this._element.id;
|
const elemID = this._element.id;
|
||||||
|
|||||||
@ -640,6 +640,7 @@ UserSchoolsTip: Sie erhalten nur institutweite Benachrichtigungen für Institute
|
|||||||
FormNotifications: Benachrichtigungen
|
FormNotifications: Benachrichtigungen
|
||||||
FormBehaviour: Verhalten
|
FormBehaviour: Verhalten
|
||||||
FormCosmetics: Oberfläche
|
FormCosmetics: Oberfläche
|
||||||
|
FormPersonalAppearance: Öffentliche Daten
|
||||||
FormFieldRequiredTip: Gekennzeichnete Pflichtfelder sind immer auszufüllen
|
FormFieldRequiredTip: Gekennzeichnete Pflichtfelder sind immer auszufüllen
|
||||||
|
|
||||||
ActiveAuthTags: Aktivierte Authorisierungsprädikate
|
ActiveAuthTags: Aktivierte Authorisierungsprädikate
|
||||||
@ -773,6 +774,15 @@ MailExamOfficeExamResultsIntro courseName@Text termDesc@Text examn@ExamName: Ein
|
|||||||
MailSubjectExamOfficeExamResultsChanged csh@CourseShorthand examn@ExamName: Ergebnisse für #{examn} in #{csh} wurden verändert
|
MailSubjectExamOfficeExamResultsChanged csh@CourseShorthand examn@ExamName: Ergebnisse für #{examn} in #{csh} wurden verändert
|
||||||
MailExamOfficeExamResultsChangedIntro courseName@Text termDesc@Text examn@ExamName: Ein Kursverwalter hat Prüfungsleistungen für #{examn} im Kurs #{courseName} (#{termDesc}) verändert.
|
MailExamOfficeExamResultsChangedIntro courseName@Text termDesc@Text examn@ExamName: Ein Kursverwalter hat Prüfungsleistungen für #{examn} im Kurs #{courseName} (#{termDesc}) verändert.
|
||||||
|
|
||||||
|
MailSubjectExamRegistrationActive csh@CourseShorthand examn@ExamName: Anmeldung für #{examn} in #{csh} ist möglich
|
||||||
|
MailExamRegistrationActiveIntro courseName@Text termDesc@Text examn@ExamName: Sie können sich nun für #{examn} im Kurs #{courseName} (#{termDesc}) anmelden.
|
||||||
|
|
||||||
|
MailSubjectExamRegistrationSoonInactive csh@CourseShorthand examn@ExamName: Anmeldung für #{examn} in #{csh} ist nur noch kurze Zeit möglich
|
||||||
|
MailExamRegistrationSoonInactiveIntro courseName@Text termDesc@Text examn@ExamName: Sie können sich bald nicht mehr für #{examn} im Kurs #{courseName} (#{termDesc}) anmelden.
|
||||||
|
|
||||||
|
MailSubjectExamDeregistrationSoonInactive csh@CourseShorthand examn@ExamName: Abmeldung für #{examn} in #{csh} ist nur noch kurze Zeit möglich
|
||||||
|
MailExamDeregistrationSoonInactiveIntro courseName@Text termDesc@Text examn@ExamName: Sie können sich bald nicht mehr von #{examn} im Kurs #{courseName} (#{termDesc}) abmelden.
|
||||||
|
|
||||||
MailSubjectSubmissionsUnassigned csh@CourseShorthand sheetName@SheetName: Abgaben zu #{sheetName} in #{csh} konnten nicht verteilt werden
|
MailSubjectSubmissionsUnassigned csh@CourseShorthand sheetName@SheetName: Abgaben zu #{sheetName} in #{csh} konnten nicht verteilt werden
|
||||||
MailSubmissionsUnassignedIntro n@Int courseName@Text termDesc@Text sheetName@SheetName: #{n} Abgaben zu #{sheetName} im Kurs #{courseName} (#{termDesc}) konnten nicht automatisiert verteilt werden.
|
MailSubmissionsUnassignedIntro n@Int courseName@Text termDesc@Text sheetName@SheetName: #{n} Abgaben zu #{sheetName} im Kurs #{courseName} (#{termDesc}) konnten nicht automatisiert verteilt werden.
|
||||||
|
|
||||||
@ -875,6 +885,9 @@ NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugetei
|
|||||||
NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Übungsblätter konnten einem Korrektor zugeteilt werden
|
NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Übungsblätter konnten einem Korrektor zugeteilt werden
|
||||||
NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert
|
NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert
|
||||||
NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert
|
NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert
|
||||||
|
NotificationTriggerExamRegistrationActive: Ich kann mich für eine Prüfung anmelden
|
||||||
|
NotificationTriggerExamRegistrationSoonInactive: Ich kann mich bald nicht mehr für eine Prüfung anmelden
|
||||||
|
NotificationTriggerExamDeregistrationSoonInactive: Ich kann mich bald nicht mehr von einer Prüfung abmelden
|
||||||
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
|
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
|
||||||
NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen
|
NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen
|
||||||
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldung-Bewerbungen für einen meiner Kurse bewerten
|
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldung-Bewerbungen für einen meiner Kurse bewerten
|
||||||
@ -1632,9 +1645,16 @@ CourseApplicationNoVeto: Kein Veto
|
|||||||
CourseApplicationNoRatingPoints: Keine Bewertung
|
CourseApplicationNoRatingPoints: Keine Bewertung
|
||||||
CourseApplicationNoRatingComment: Kein Kommentar
|
CourseApplicationNoRatingComment: Kein Kommentar
|
||||||
|
|
||||||
UserDisplayName: Voller Name
|
UserDisplayName: Angezeigter Name
|
||||||
|
UserDisplayNameInvalid: Angezeigter Name erfüllt nicht die Vorgaben
|
||||||
|
UserDisplayNameRules: Vorgaben für den angezeigten Namen
|
||||||
|
UserDisplayNameRulesBelow: Vorgaben für den angezeigten Namen finden sich weiter unten auf der Seite
|
||||||
UserMatriculation: Matrikelnummer
|
UserMatriculation: Matrikelnummer
|
||||||
|
|
||||||
|
UserDisplayEmail: Angezeigte E-Mail Adresse
|
||||||
|
UserDisplayEmailTip: Diese Adresse wird in öffentlich zugänglichen Teilen des Systems im Zusammenhang mit Ihrem Namen angezeigt. Benachrichtigungen und andere Kommunikation von Uni2work und Nutzern mit erweiterten Rechten erhalten sie stets, unabhängig von dieser Einstellung, an die in Ihren Persönlichen Daten hinterlegte primäre Adresse.
|
||||||
|
UserDisplayEmailChangeSent displayEmail@UserEmail: Anweisungen zum Ändern der angezeigten E-Mail Adresse wurden an „#{displayEmail}” versandt
|
||||||
|
|
||||||
SchoolShort: Kürzel
|
SchoolShort: Kürzel
|
||||||
SchoolName: Name
|
SchoolName: Name
|
||||||
SchoolLdapOrganisations: Assoziierte LDAP-Fragmente
|
SchoolLdapOrganisations: Assoziierte LDAP-Fragmente
|
||||||
@ -1701,6 +1721,7 @@ AdminUserFirstName: Vorname
|
|||||||
AdminUserSurname: Nachname
|
AdminUserSurname: Nachname
|
||||||
AdminUserDisplayName: Anzeige-Name
|
AdminUserDisplayName: Anzeige-Name
|
||||||
AdminUserEmail: E-Mail Addresse
|
AdminUserEmail: E-Mail Addresse
|
||||||
|
AdminUserDisplayEmail: Anzeige-E-Mail
|
||||||
AdminUserIdent: Identifikation
|
AdminUserIdent: Identifikation
|
||||||
AdminUserAuth: Authentifizierung
|
AdminUserAuth: Authentifizierung
|
||||||
AdminUserMatriculation: Matrikelnummer
|
AdminUserMatriculation: Matrikelnummer
|
||||||
@ -1708,3 +1729,17 @@ AuthKindLDAP: Campus-Kennung
|
|||||||
AuthKindPWHash: Uni2work-Kennung
|
AuthKindPWHash: Uni2work-Kennung
|
||||||
UserAdded: Benutzer erfolgreich angelegt
|
UserAdded: Benutzer erfolgreich angelegt
|
||||||
UserCollision: Benutzer konnte wegen Eindeutigkeit nicht angelegt werden
|
UserCollision: Benutzer konnte wegen Eindeutigkeit nicht angelegt werden
|
||||||
|
|
||||||
|
CourseAllocationsBounds n@Int: Voraussichtliche Zuteilungen durch #{pluralDE n "Zentralanmeldung" "Zentralanmeldungen"}
|
||||||
|
CourseAllocationsBoundCoincide numFirstChoice@Int: Vstl. #{numFirstChoice} Teilnehmer
|
||||||
|
CourseAllocationsBound numApps@Int numFirstChoice@Int: Vstl. zwischen #{numFirstChoice} und #{numApps} Teilnehmer
|
||||||
|
CourseAllocationsBoundCapped: Die obige Anzeige wurde durch die aktuell angegebene Kurskapazität reduziert.
|
||||||
|
CourseAllocationsBoundWarningOpen: Diese Informationen entsprechen nur dem aktuellen Stand der Bewerbungen und können sich noch ändern.
|
||||||
|
|
||||||
|
BtnSetDisplayEmail: E-Mail Adresse setzen
|
||||||
|
UserDisplayEmailChanged: Öffentliche E-Mail Adresse erfolgreich gesetzt
|
||||||
|
TitleChangeUserDisplayEmail: Öffentliche E-Mail Adresse setzen
|
||||||
|
|
||||||
|
MailSubjectChangeUserDisplayEmail: Diese E-Mail Adresse in Uni2work veröffentlichen
|
||||||
|
MailIntroChangeUserDisplayEmail displayEmail@UserEmail: Der oben genannte Benutzer möchte „#{displayEmail}“ als öffentliche Adresse, assoziiert mit sich selbst, angeben. Wenn Sie diese Aktion nicht selbst ausgelöst haben, ignorieren Sie diese Mitteilung bitte!
|
||||||
|
MailTitleChangeUserDisplayEmail displayName@Text: #{displayName} möchte diese E-Mail Adresse in Uni2work veröffentlichen
|
||||||
|
|||||||
@ -9,9 +9,10 @@
|
|||||||
--
|
--
|
||||||
User json -- Each Uni2work user has a corresponding row in this table; created upon first login.
|
User json -- Each Uni2work user has a corresponding row in this table; created upon first login.
|
||||||
surname UserSurname -- Display user names always through 'nameWidget displayName surname'
|
surname UserSurname -- Display user names always through 'nameWidget displayName surname'
|
||||||
displayName UserDisplayName -- we only show LDAP-DisplayName, and highlight LDAP-Surname within (appended if not contained)
|
displayName UserDisplayName
|
||||||
email (CI Text) -- Case-insensitive eMail address
|
displayEmail UserEmail
|
||||||
ident (CI Text) -- Case-insensitive user-identifier
|
email UserEmail -- Case-insensitive eMail address
|
||||||
|
ident UserIdent -- Case-insensitive user-identifier
|
||||||
authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash)
|
authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash)
|
||||||
lastAuthentication UTCTime Maybe -- last login date
|
lastAuthentication UTCTime Maybe -- last login date
|
||||||
created UTCTime default=now()
|
created UTCTime default=now()
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "6.7.0",
|
"version": "6.10.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "6.7.0",
|
"version": "6.10.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: uniworx
|
name: uniworx
|
||||||
version: 6.7.0
|
version: 6.10.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
# Due to a bug in GHC 8.0.1, we block its usage
|
# Due to a bug in GHC 8.0.1, we block its usage
|
||||||
@ -137,6 +137,7 @@ dependencies:
|
|||||||
- memory
|
- memory
|
||||||
- pqueue
|
- pqueue
|
||||||
- deepseq
|
- deepseq
|
||||||
|
- multiset
|
||||||
|
|
||||||
other-extensions:
|
other-extensions:
|
||||||
- GeneralizedNewtypeDeriving
|
- GeneralizedNewtypeDeriving
|
||||||
|
|||||||
1
routes
1
routes
@ -71,6 +71,7 @@
|
|||||||
/user ProfileR GET POST !free
|
/user ProfileR GET POST !free
|
||||||
/user/profile ProfileDataR GET !free
|
/user/profile ProfileDataR GET !free
|
||||||
/user/authpreds AuthPredsR GET POST !free
|
/user/authpreds AuthPredsR GET POST !free
|
||||||
|
/user/set-display-email SetDisplayEmailR GET POST !free
|
||||||
|
|
||||||
/exam-office ExamOfficeR !exam-office:
|
/exam-office ExamOfficeR !exam-office:
|
||||||
/ EOExamsR GET
|
/ EOExamsR GET
|
||||||
|
|||||||
@ -67,6 +67,7 @@ import qualified Control.Monad.Catch as C
|
|||||||
import Handler.Utils.StudyFeatures
|
import Handler.Utils.StudyFeatures
|
||||||
import Handler.Utils.SchoolLdap
|
import Handler.Utils.SchoolLdap
|
||||||
import Handler.Utils.ExamOffice.Exam.Auth
|
import Handler.Utils.ExamOffice.Exam.Auth
|
||||||
|
import Handler.Utils.Profile
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
import Utils.Sheet
|
import Utils.Sheet
|
||||||
import Utils.SystemMessage
|
import Utils.SystemMessage
|
||||||
@ -1803,9 +1804,10 @@ instance YesodBreadcrumbs UniWorX where
|
|||||||
breadcrumb InstanceR = return ("Identifikation", Nothing)
|
breadcrumb InstanceR = return ("Identifikation", Nothing)
|
||||||
|
|
||||||
|
|
||||||
breadcrumb ProfileR = return ("User" , Just HomeR)
|
breadcrumb ProfileR = return ("Einstellungen" , Just HomeR)
|
||||||
breadcrumb ProfileDataR = return ("Profile" , Just ProfileR)
|
breadcrumb SetDisplayEmailR = return ("Öffentliche E-Mail Adresse", Just ProfileR)
|
||||||
breadcrumb AuthPredsR = return ("Authentifizierung", Just ProfileR)
|
breadcrumb ProfileDataR = return ("Persönliche Daten", Just ProfileR)
|
||||||
|
breadcrumb AuthPredsR = return ("Authorisierung" , Just ProfileR)
|
||||||
|
|
||||||
breadcrumb TermShowR = return ("Semester" , Just HomeR)
|
breadcrumb TermShowR = return ("Semester" , Just HomeR)
|
||||||
breadcrumb TermCurrentR = return ("Aktuell" , Just TermShowR)
|
breadcrumb TermCurrentR = return ("Aktuell" , Just TermShowR)
|
||||||
@ -3142,7 +3144,7 @@ upsertCampusUser ldapData Creds{..} = do
|
|||||||
let
|
let
|
||||||
userMatrikelnummer' = fold [ v | (k, v) <- ldapData, k == ldapUserMatriculation ]
|
userMatrikelnummer' = fold [ v | (k, v) <- ldapData, k == ldapUserMatriculation ]
|
||||||
userEmail' = fold [ v | (k, v) <- ldapData, k == ldapUserEmail ]
|
userEmail' = fold [ v | (k, v) <- ldapData, k == ldapUserEmail ]
|
||||||
userDisplayName' = fold [ v | (k, v) <- ldapData, k == ldapUserDisplayName ]
|
userDisplayName'' = fold [ v | (k, v) <- ldapData, k == ldapUserDisplayName ]
|
||||||
userFirstName' = fold [ v | (k, v) <- ldapData, k == ldapUserFirstName ]
|
userFirstName' = fold [ v | (k, v) <- ldapData, k == ldapUserFirstName ]
|
||||||
userSurname' = fold [ v | (k, v) <- ldapData, k == ldapUserSurname ]
|
userSurname' = fold [ v | (k, v) <- ldapData, k == ldapUserSurname ]
|
||||||
userTitle' = fold [ v | (k, v) <- ldapData, k == ldapUserTitle ]
|
userTitle' = fold [ v | (k, v) <- ldapData, k == ldapUserTitle ]
|
||||||
@ -3158,10 +3160,10 @@ upsertCampusUser ldapData Creds{..} = do
|
|||||||
-> return $ mk userEmail
|
-> return $ mk userEmail
|
||||||
| otherwise
|
| otherwise
|
||||||
-> throwM CampusUserInvalidEmail
|
-> throwM CampusUserInvalidEmail
|
||||||
userDisplayName <- if
|
userDisplayName' <- if
|
||||||
| [bs] <- userDisplayName'
|
| [bs] <- userDisplayName''
|
||||||
, Right userDisplayName <- Text.decodeUtf8' bs
|
, Right userDisplayName' <- Text.decodeUtf8' bs
|
||||||
-> return userDisplayName
|
-> return userDisplayName'
|
||||||
| otherwise
|
| otherwise
|
||||||
-> throwM CampusUserInvalidDisplayName
|
-> throwM CampusUserInvalidDisplayName
|
||||||
userFirstName <- if
|
userFirstName <- if
|
||||||
@ -3208,17 +3210,23 @@ upsertCampusUser ldapData Creds{..} = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userCreated = now
|
, userCreated = now
|
||||||
, userLastLdapSynchronisation = Just now
|
, userLastLdapSynchronisation = Just now
|
||||||
|
, userDisplayName = userDisplayName'
|
||||||
|
, userDisplayEmail = userEmail
|
||||||
, ..
|
, ..
|
||||||
}
|
}
|
||||||
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
|
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
|
||||||
, UserDisplayName =. userDisplayName
|
-- , UserDisplayName =. userDisplayName
|
||||||
|
, UserFirstName =. userFirstName
|
||||||
, UserSurname =. userSurname
|
, UserSurname =. userSurname
|
||||||
|
, UserTitle =. userTitle
|
||||||
, UserEmail =. userEmail
|
, UserEmail =. userEmail
|
||||||
, UserLastLdapSynchronisation =. Just now
|
, UserLastLdapSynchronisation =. Just now
|
||||||
] ++
|
] ++
|
||||||
[ UserLastAuthentication =. Just now | not isDummy ]
|
[ UserLastAuthentication =. Just now | not isDummy ]
|
||||||
|
|
||||||
user@(Entity userId _) <- upsertBy (UniqueAuthentication $ mk credsIdent) newUser userUpdate
|
user@(Entity userId userRec) <- upsertBy (UniqueAuthentication $ mk credsIdent) newUser userUpdate
|
||||||
|
unless (validDisplayName userTitle userFirstName userSurname $ userDisplayName userRec) $
|
||||||
|
update userId [ UserDisplayName =. userDisplayName' ]
|
||||||
|
|
||||||
let
|
let
|
||||||
userStudyFeatures = fmap concat . forM userStudyFeatures' $ parseStudyFeatures userId now
|
userStudyFeatures = fmap concat . forM userStudyFeatures' $ parseStudyFeatures userId now
|
||||||
|
|||||||
@ -209,8 +209,8 @@ embedRenderMessage ''UniWorX ''CourseApplicationsTableCsvException id
|
|||||||
getCApplicationsR, postCApplicationsR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCApplicationsR, postCApplicationsR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
getCApplicationsR = postCApplicationsR
|
getCApplicationsR = postCApplicationsR
|
||||||
postCApplicationsR tid ssh csh = do
|
postCApplicationsR tid ssh csh = do
|
||||||
table <- runDB $ do
|
(table, allocationsBounds) <- runDB $ do
|
||||||
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
Entity cid Course{..} <- getBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
|
|
||||||
csvName <- getMessageRender <*> pure (MsgCourseApplicationsTableCsvName tid ssh csh)
|
csvName <- getMessageRender <*> pure (MsgCourseApplicationsTableCsvName tid ssh csh)
|
||||||
let
|
let
|
||||||
@ -531,10 +531,46 @@ postCApplicationsR tid ssh csh = do
|
|||||||
psValidator = def
|
psValidator = def
|
||||||
& defaultSorting [SortAscBy "user-name"]
|
& defaultSorting [SortAscBy "user-name"]
|
||||||
|
|
||||||
dbTableWidget' psValidator DBTable{..}
|
participants <- count [ CourseParticipantCourse ==. cid ]
|
||||||
|
let remainingCapacity = subtract participants <$> courseCapacity
|
||||||
|
|
||||||
|
allocationsBounds' <- E.select . E.from $ \(allocation `E.InnerJoin` allocationCourse) -> do
|
||||||
|
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
|
||||||
|
E.&&. allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
|
||||||
|
|
||||||
|
let numApps addWhere = E.sub_select . E.from $ \courseApplication -> do
|
||||||
|
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid
|
||||||
|
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
|
||||||
|
addWhere courseApplication
|
||||||
|
return E.countRows
|
||||||
|
|
||||||
|
numApps' = numApps . const $ return ()
|
||||||
|
|
||||||
|
numFirstChoice = numApps $ \courseApplication ->
|
||||||
|
E.where_ . E.not_ . E.exists . E.from $ \courseApplication' -> do
|
||||||
|
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. courseApplication' E.^. CourseApplicationAllocation
|
||||||
|
E.&&. courseApplication E.^. CourseApplicationUser E.==. courseApplication' E.^. CourseApplicationUser
|
||||||
|
E.where_ . E.not_ $ E.isNothing (courseApplication E.^. CourseApplicationAllocationPriority)
|
||||||
|
E.||. E.isNothing (courseApplication' E.^. CourseApplicationAllocationPriority)
|
||||||
|
E.where_ $ courseApplication' E.^. CourseApplicationAllocationPriority E.>. courseApplication E.^. CourseApplicationAllocationPriority
|
||||||
|
|
||||||
|
return (allocation, numApps', numFirstChoice)
|
||||||
|
|
||||||
|
let
|
||||||
|
allocationsBounds = [ (allocation, numApps', numFirstChoice', capped)
|
||||||
|
| (Entity _ allocation, E.Value numApps, E.Value numFirstChoice) <- allocationsBounds'
|
||||||
|
, let numApps' = max 0 $ maybe id min remainingCapacity numApps
|
||||||
|
numFirstChoice' = max 0 $ maybe id min remainingCapacity numFirstChoice
|
||||||
|
capped = numApps' /= numApps
|
||||||
|
|| numFirstChoice' /= numFirstChoice
|
||||||
|
]
|
||||||
|
|
||||||
|
(, allocationsBounds) <$> dbTableWidget' psValidator DBTable{..}
|
||||||
|
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
let title = prependCourseTitle tid ssh csh MsgCourseApplicationsListTitle
|
let title = prependCourseTitle tid ssh csh MsgCourseApplicationsListTitle
|
||||||
|
registrationOpen = maybe True (now <)
|
||||||
|
|
||||||
siteLayoutMsg title $ do
|
siteLayoutMsg title $ do
|
||||||
setTitleI title
|
setTitleI title
|
||||||
table
|
$(widgetFile "course/applications-list")
|
||||||
|
|||||||
@ -303,10 +303,11 @@ validateCourse = do
|
|||||||
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
|
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
|
||||||
prevAllocation <- fmap join . traverse get $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
|
prevAllocation <- fmap join . traverse get $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
|
||||||
|
|
||||||
fmap join . for prevAllocation $ \Allocation{allocationStaffRegisterTo} -> if
|
fmap join . for prevAllocation $ \Allocation{allocationStaffAllocationTo, allocationRegisterByCourse} -> if
|
||||||
| userAdmin
|
| userAdmin
|
||||||
-> return Nothing
|
-> return Nothing
|
||||||
| NTop allocationStaffRegisterTo <= NTop (Just now)
|
| NTop allocationStaffAllocationTo <= NTop (Just now)
|
||||||
|
, NTop allocationRegisterByCourse > NTop (Just now)
|
||||||
-> Just . courseCapacity <$> getJust cid
|
-> Just . courseCapacity <$> getJust cid
|
||||||
| otherwise
|
| otherwise
|
||||||
-> return Nothing
|
-> return Nothing
|
||||||
|
|||||||
@ -46,7 +46,7 @@ getCShowR tid ssh csh = do
|
|||||||
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
||||||
E.orderBy [ E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName ]
|
E.orderBy [ E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName ]
|
||||||
return ( lecturer E.^. LecturerType
|
return ( lecturer E.^. LecturerType
|
||||||
, user E.^. UserEmail, user E.^. UserDisplayName, user E.^. UserSurname)
|
, user E.^. UserDisplayEmail, user E.^. UserDisplayName, user E.^. UserSurname)
|
||||||
let partStaff :: (LecturerType, UserEmail, Text, Text) -> Either (UserEmail, Text, Text) (UserEmail, Text, Text)
|
let partStaff :: (LecturerType, UserEmail, Text, Text) -> Either (UserEmail, Text, Text) (UserEmail, Text, Text)
|
||||||
partStaff (CourseLecturer ,name,surn,mail) = Right (name,surn,mail)
|
partStaff (CourseLecturer ,name,surn,mail) = Right (name,surn,mail)
|
||||||
partStaff (_courseAssistant,name,surn,mail) = Left (name,surn,mail)
|
partStaff (_courseAssistant,name,surn,mail) = Left (name,surn,mail)
|
||||||
|
|||||||
@ -3,6 +3,8 @@ module Handler.Profile where
|
|||||||
import Import
|
import Import
|
||||||
|
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
|
import Handler.Utils.Profile
|
||||||
|
import Handler.Utils.Tokens
|
||||||
|
|
||||||
-- import Colonnade hiding (fromMaybe, singleton)
|
-- import Colonnade hiding (fromMaybe, singleton)
|
||||||
-- import Yesod.Colonnade
|
-- import Yesod.Colonnade
|
||||||
@ -16,9 +18,13 @@ import qualified Database.Esqueleto.Utils as E
|
|||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
import Jobs
|
||||||
|
|
||||||
|
|
||||||
data SettingsForm = SettingsForm
|
data SettingsForm = SettingsForm
|
||||||
{ stgMaxFavourties :: Int
|
{ stgDisplayName :: UserDisplayName
|
||||||
|
, stgDisplayEmail :: UserEmail
|
||||||
|
, stgMaxFavourties :: Int
|
||||||
, stgTheme :: Theme
|
, stgTheme :: Theme
|
||||||
, stgDateTime :: DateTimeFormat
|
, stgDateTime :: DateTimeFormat
|
||||||
, stgDate :: DateTimeFormat
|
, stgDate :: DateTimeFormat
|
||||||
@ -28,6 +34,7 @@ data SettingsForm = SettingsForm
|
|||||||
, stgSchools :: Set SchoolId
|
, stgSchools :: Set SchoolId
|
||||||
, stgNotificationSettings :: NotificationSettings
|
, stgNotificationSettings :: NotificationSettings
|
||||||
}
|
}
|
||||||
|
makeLenses_ ''SettingsForm
|
||||||
|
|
||||||
data NotificationTriggerKind
|
data NotificationTriggerKind
|
||||||
= NTKAll
|
= NTKAll
|
||||||
@ -57,7 +64,10 @@ instance RenderMessage UniWorX NotificationTriggerKind where
|
|||||||
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
|
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
|
||||||
makeSettingForm template html = do
|
makeSettingForm template html = do
|
||||||
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
||||||
<$ aformSection MsgFormCosmetics
|
<$ aformSection MsgFormPersonalAppearance
|
||||||
|
<*> areq (textField & cfStrip) (fslI MsgUserDisplayName & setTooltip MsgUserDisplayNameRulesBelow) (stgDisplayName <$> template)
|
||||||
|
<*> areq (emailField & cfStrip & cfCI) (fslI MsgUserDisplayEmail & setTooltip MsgUserDisplayEmailTip) (stgDisplayEmail <$> template)
|
||||||
|
<* aformSection MsgFormCosmetics
|
||||||
<*> areq (natFieldI $ MsgNatField "Favoriten") -- TODO: natFieldI not working here
|
<*> areq (natFieldI $ MsgNatField "Favoriten") -- TODO: natFieldI not working here
|
||||||
(fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template)
|
(fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template)
|
||||||
<*> areq (selectField . return $ mkOptionList themeList)
|
<*> areq (selectField . return $ mkOptionList themeList)
|
||||||
@ -150,30 +160,41 @@ notificationForm template = wFormToAForm $ do
|
|||||||
= apopt checkBoxField (fslI nt) (flip notificationAllowed nt <$> template)
|
= apopt checkBoxField (fslI nt) (flip notificationAllowed nt <$> template)
|
||||||
|
|
||||||
ntSection = \case
|
ntSection = \case
|
||||||
NTSubmissionRatedGraded -> Just NTKCourseParticipant
|
NTSubmissionRatedGraded -> Just NTKCourseParticipant
|
||||||
NTSubmissionRated -> Just NTKCourseParticipant
|
NTSubmissionRated -> Just NTKCourseParticipant
|
||||||
NTSheetActive -> Just NTKCourseParticipant
|
NTSheetActive -> Just NTKCourseParticipant
|
||||||
NTSheetSoonInactive -> Just NTKCourseParticipant
|
NTSheetSoonInactive -> Just NTKCourseParticipant
|
||||||
NTSheetInactive -> Just $ NTKFunctionary SchoolLecturer
|
NTSheetInactive -> Just $ NTKFunctionary SchoolLecturer
|
||||||
NTCorrectionsAssigned -> Just NTKCorrector
|
NTCorrectionsAssigned -> Just NTKCorrector
|
||||||
NTCorrectionsNotDistributed -> Just $ NTKFunctionary SchoolLecturer
|
NTCorrectionsNotDistributed -> Just $ NTKFunctionary SchoolLecturer
|
||||||
NTUserRightsUpdate -> Just NTKAll
|
NTUserRightsUpdate -> Just NTKAll
|
||||||
NTUserAuthModeUpdate -> Just NTKAll
|
NTUserAuthModeUpdate -> Just NTKAll
|
||||||
NTExamResult -> Just NTKExamParticipant
|
NTExamRegistrationActive -> Just NTKCourseParticipant
|
||||||
NTAllocationStaffRegister -> Just $ NTKFunctionary SchoolLecturer
|
NTExamRegistrationSoonInactive -> Just NTKCourseParticipant
|
||||||
NTAllocationAllocation -> Just NTKAllocationStaff
|
NTExamDeregistrationSoonInactive -> Just NTKCourseParticipant
|
||||||
NTAllocationRegister -> Just NTKAll
|
NTExamResult -> Just NTKExamParticipant
|
||||||
NTAllocationOutdatedRatings -> Just NTKAllocationStaff
|
NTAllocationStaffRegister -> Just $ NTKFunctionary SchoolLecturer
|
||||||
NTAllocationUnratedApplications -> Just NTKAllocationStaff
|
NTAllocationAllocation -> Just NTKAllocationStaff
|
||||||
NTExamOfficeExamResults -> Just $ NTKFunctionary SchoolExamOffice
|
NTAllocationRegister -> Just NTKAll
|
||||||
NTExamOfficeExamResultsChanged -> Just $ NTKFunctionary SchoolExamOffice
|
NTAllocationOutdatedRatings -> Just NTKAllocationStaff
|
||||||
-- _other -> Nothing
|
NTAllocationUnratedApplications -> Just NTKAllocationStaff
|
||||||
|
NTExamOfficeExamResults -> Just $ NTKFunctionary SchoolExamOffice
|
||||||
|
NTExamOfficeExamResultsChanged -> Just $ NTKFunctionary SchoolExamOffice
|
||||||
|
-- _other -> Nothing
|
||||||
|
|
||||||
forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate]
|
forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate]
|
||||||
|
|
||||||
aFormToWForm $ NotificationSettings <$> sectionedFuncForm ntSection nsForm (fslI MsgNotificationSettings) False
|
aFormToWForm $ NotificationSettings <$> sectionedFuncForm ntSection nsForm (fslI MsgNotificationSettings) False
|
||||||
|
|
||||||
|
|
||||||
|
validateSettings :: User -> FormValidator SettingsForm Handler ()
|
||||||
|
validateSettings User{..} = do
|
||||||
|
userDisplayName' <- use _stgDisplayName
|
||||||
|
|
||||||
|
guardValidation MsgUserDisplayNameInvalid $
|
||||||
|
validDisplayName userTitle userFirstName userSurname userDisplayName'
|
||||||
|
|
||||||
|
|
||||||
data ButtonResetTokens = BtnResetTokens
|
data ButtonResetTokens = BtnResetTokens
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||||
instance Universe ButtonResetTokens
|
instance Universe ButtonResetTokens
|
||||||
@ -196,7 +217,7 @@ nullaryPathPiece ''ProfileAnchor $ camelToPathPiece' 1
|
|||||||
getProfileR, postProfileR :: Handler Html
|
getProfileR, postProfileR :: Handler Html
|
||||||
getProfileR = postProfileR
|
getProfileR = postProfileR
|
||||||
postProfileR = do
|
postProfileR = do
|
||||||
(uid, User{..}) <- requireAuthPair
|
(uid, user@User{..}) <- requireAuthPair
|
||||||
userSchools <- fmap (setOf $ folded . _Value) . runDB . E.select . E.from $ \school -> do
|
userSchools <- fmap (setOf $ folded . _Value) . runDB . E.select . E.from $ \school -> do
|
||||||
E.where_ . E.exists . E.from $ \userSchool ->
|
E.where_ . E.exists . E.from $ \userSchool ->
|
||||||
E.where_ $ E.not_ (userSchool E.^. UserSchoolIsOptOut)
|
E.where_ $ E.not_ (userSchool E.^. UserSchoolIsOptOut)
|
||||||
@ -204,7 +225,9 @@ postProfileR = do
|
|||||||
E.&&. userSchool E.^. UserSchoolSchool E.==. school E.^. SchoolId
|
E.&&. userSchool E.^. UserSchoolSchool E.==. school E.^. SchoolId
|
||||||
return $ school E.^. SchoolId
|
return $ school E.^. SchoolId
|
||||||
let settingsTemplate = Just SettingsForm
|
let settingsTemplate = Just SettingsForm
|
||||||
{ stgMaxFavourties = userMaxFavourites
|
{ stgDisplayName = userDisplayName
|
||||||
|
, stgDisplayEmail = userDisplayEmail
|
||||||
|
, stgMaxFavourties = userMaxFavourites
|
||||||
, stgTheme = userTheme
|
, stgTheme = userTheme
|
||||||
, stgDateTime = userDateTimeFormat
|
, stgDateTime = userDateTimeFormat
|
||||||
, stgDate = userDateFormat
|
, stgDate = userDateFormat
|
||||||
@ -214,19 +237,24 @@ postProfileR = do
|
|||||||
, stgNotificationSettings = userNotificationSettings
|
, stgNotificationSettings = userNotificationSettings
|
||||||
, stgWarningDays = userWarningDays
|
, stgWarningDays = userWarningDays
|
||||||
}
|
}
|
||||||
((res,formWidget), formEnctype) <- runFormPost . identifyForm ProfileSettings $ makeSettingForm settingsTemplate
|
((res,formWidget), formEnctype) <- runFormPost . validateForm (validateSettings user) . identifyForm ProfileSettings $ makeSettingForm settingsTemplate
|
||||||
|
|
||||||
formResult res $ \SettingsForm{..} -> do
|
formResult res $ \SettingsForm{..} -> do
|
||||||
runDB $ do
|
runDBJobs $ do
|
||||||
update uid [ UserMaxFavourites =. stgMaxFavourties
|
update uid $
|
||||||
, UserTheme =. stgTheme
|
[ UserDisplayName =. stgDisplayName
|
||||||
, UserDateTimeFormat =. stgDateTime
|
, UserMaxFavourites =. stgMaxFavourties
|
||||||
, UserDateFormat =. stgDate
|
, UserTheme =. stgTheme
|
||||||
, UserTimeFormat =. stgTime
|
, UserDateTimeFormat =. stgDateTime
|
||||||
, UserDownloadFiles =. stgDownloadFiles
|
, UserDateFormat =. stgDate
|
||||||
, UserWarningDays =. stgWarningDays
|
, UserTimeFormat =. stgTime
|
||||||
, UserNotificationSettings =. stgNotificationSettings
|
, UserDownloadFiles =. stgDownloadFiles
|
||||||
]
|
, UserWarningDays =. stgWarningDays
|
||||||
|
, UserNotificationSettings =. stgNotificationSettings
|
||||||
|
] ++ [ UserDisplayEmail =. stgDisplayEmail | userDisplayEmail == stgDisplayEmail ]
|
||||||
|
when (stgDisplayEmail /= userDisplayEmail) $ do
|
||||||
|
queueDBJob $ JobChangeUserDisplayEmail uid stgDisplayEmail
|
||||||
|
addMessageI Info $ MsgUserDisplayEmailChangeSent stgDisplayEmail
|
||||||
when (stgMaxFavourties < userMaxFavourites) $ do
|
when (stgMaxFavourties < userMaxFavourites) $ do
|
||||||
-- prune Favourites to user-defined size
|
-- prune Favourites to user-defined size
|
||||||
oldFavs <- selectKeysList [ CourseFavouriteUser ==. uid]
|
oldFavs <- selectKeysList [ CourseFavouriteUser ==. uid]
|
||||||
@ -253,7 +281,7 @@ postProfileR = do
|
|||||||
}
|
}
|
||||||
[ UserSchoolIsOptOut =. True
|
[ UserSchoolIsOptOut =. True
|
||||||
]
|
]
|
||||||
addMessageI Info MsgSettingsUpdate
|
addMessageI Success MsgSettingsUpdate
|
||||||
redirect $ ProfileR :#: ProfileSettings
|
redirect $ ProfileR :#: ProfileSettings
|
||||||
|
|
||||||
((tokenRes, tokenFormWidget), tokenEnctype) <- runFormPost . identifyForm ProfileResetTokens $ buttonForm
|
((tokenRes, tokenFormWidget), tokenEnctype) <- runFormPost . identifyForm ProfileResetTokens $ buttonForm
|
||||||
@ -287,6 +315,7 @@ postProfileR = do
|
|||||||
, formAnchor = Just ProfileResetTokens
|
, formAnchor = Just ProfileResetTokens
|
||||||
}
|
}
|
||||||
tokenExplanation = $(i18nWidgetFile "profile/tokenExplanation")
|
tokenExplanation = $(i18nWidgetFile "profile/tokenExplanation")
|
||||||
|
displayNameRules = $(i18nWidgetFile "profile/displayNameRules")
|
||||||
$(widgetFile "profile/profile")
|
$(widgetFile "profile/profile")
|
||||||
|
|
||||||
|
|
||||||
@ -727,3 +756,43 @@ postUserNotificationR cID = do
|
|||||||
siteLayoutMsg (MsgNotificationSettingsHeading userDisplayName) $ do
|
siteLayoutMsg (MsgNotificationSettingsHeading userDisplayName) $ do
|
||||||
setTitleI $ MsgNotificationSettingsHeading userDisplayName
|
setTitleI $ MsgNotificationSettingsHeading userDisplayName
|
||||||
formWidget
|
formWidget
|
||||||
|
|
||||||
|
|
||||||
|
data ButtonSetDisplayEmail = BtnSetDisplayEmail
|
||||||
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
|
instance Universe ButtonSetDisplayEmail
|
||||||
|
instance Finite ButtonSetDisplayEmail
|
||||||
|
|
||||||
|
nullaryPathPiece ''ButtonSetDisplayEmail $ camelToPathPiece' 1
|
||||||
|
embedRenderMessage ''UniWorX ''ButtonSetDisplayEmail id
|
||||||
|
|
||||||
|
instance Button UniWorX ButtonSetDisplayEmail where
|
||||||
|
btnClasses _ = [BCIsButton]
|
||||||
|
|
||||||
|
|
||||||
|
getSetDisplayEmailR, postSetDisplayEmailR :: Handler Html
|
||||||
|
getSetDisplayEmailR = postSetDisplayEmailR
|
||||||
|
postSetDisplayEmailR = do
|
||||||
|
uid <- requireAuthId
|
||||||
|
mDisplayEmail <- requireCurrentTokenRestrictions
|
||||||
|
|
||||||
|
case mDisplayEmail of
|
||||||
|
Nothing -> invalidArgs ["Bearer token required"]
|
||||||
|
Just displayEmail -> do
|
||||||
|
((btnRes, btnView), btnEnc) <- runFormPost $ formEmbedJwtPost buttonForm
|
||||||
|
let btnView' = wrapForm btnView def
|
||||||
|
{ formSubmit = FormNoSubmit
|
||||||
|
, formAction = Just $ SomeRoute SetDisplayEmailR
|
||||||
|
, formEncoding = btnEnc
|
||||||
|
}
|
||||||
|
|
||||||
|
formResult btnRes $ \case
|
||||||
|
BtnSetDisplayEmail -> do
|
||||||
|
runDB $
|
||||||
|
update uid [UserDisplayEmail =. displayEmail]
|
||||||
|
addMessageI Success MsgUserDisplayEmailChanged
|
||||||
|
redirect ProfileR
|
||||||
|
|
||||||
|
siteLayoutMsg MsgTitleChangeUserDisplayEmail $ do
|
||||||
|
setTitleI MsgTitleChangeUserDisplayEmail
|
||||||
|
$(i18nWidgetFile "set-display-email")
|
||||||
|
|||||||
@ -16,6 +16,7 @@ data AdminUserForm = AdminUserForm
|
|||||||
, aufFirstName :: Text
|
, aufFirstName :: Text
|
||||||
, aufSurname :: UserSurname
|
, aufSurname :: UserSurname
|
||||||
, aufDisplayName :: UserDisplayName
|
, aufDisplayName :: UserDisplayName
|
||||||
|
, aufDisplayEmail :: UserEmail
|
||||||
, aufMatriculation :: Maybe UserMatriculation
|
, aufMatriculation :: Maybe UserMatriculation
|
||||||
, aufEmail :: UserEmail
|
, aufEmail :: UserEmail
|
||||||
, aufIdent :: UserIdent
|
, aufIdent :: UserIdent
|
||||||
@ -44,6 +45,7 @@ adminUserForm template = renderAForm FormStandard
|
|||||||
<*> areq (textField & cfStrip) (fslI MsgAdminUserFirstName) (aufFirstName <$> template)
|
<*> areq (textField & cfStrip) (fslI MsgAdminUserFirstName) (aufFirstName <$> template)
|
||||||
<*> areq (textField & cfStrip) (fslI MsgAdminUserSurname) (aufSurname <$> template)
|
<*> areq (textField & cfStrip) (fslI MsgAdminUserSurname) (aufSurname <$> template)
|
||||||
<*> areq (textField & cfStrip) (fslI MsgAdminUserDisplayName) (aufDisplayName <$> template)
|
<*> areq (textField & cfStrip) (fslI MsgAdminUserDisplayName) (aufDisplayName <$> template)
|
||||||
|
<*> areq (emailField & cfCI) (fslI MsgAdminUserDisplayEmail) (aufDisplayEmail <$> template)
|
||||||
<*> aopt (textField & cfStrip) (fslI MsgAdminUserMatriculation) (aufMatriculation <$> template)
|
<*> aopt (textField & cfStrip) (fslI MsgAdminUserMatriculation) (aufMatriculation <$> template)
|
||||||
<*> areq (emailField & cfCI) (fslI MsgAdminUserEmail) (aufEmail <$> template)
|
<*> areq (emailField & cfCI) (fslI MsgAdminUserEmail) (aufEmail <$> template)
|
||||||
<*> areq (textField & cfStrip & cfCI) (fslI MsgAdminUserIdent) (aufIdent <$> template)
|
<*> areq (textField & cfStrip & cfCI) (fslI MsgAdminUserIdent) (aufIdent <$> template)
|
||||||
@ -77,6 +79,7 @@ postAdminUserAddR = do
|
|||||||
, userLastAuthentication = Nothing
|
, userLastAuthentication = Nothing
|
||||||
, userEmail = aufEmail
|
, userEmail = aufEmail
|
||||||
, userDisplayName = aufDisplayName
|
, userDisplayName = aufDisplayName
|
||||||
|
, userDisplayEmail = aufDisplayEmail
|
||||||
, userFirstName = aufFirstName
|
, userFirstName = aufFirstName
|
||||||
, userSurname = aufSurname
|
, userSurname = aufSurname
|
||||||
, userTitle = aufTitle
|
, userTitle = aufTitle
|
||||||
|
|||||||
@ -981,15 +981,16 @@ sectionedFuncForm mkSection mkForm FieldSettings{fsName = _, fsAttrs = _, ..} is
|
|||||||
aFormToWForm $ Map.union <$> wFormToAForm (pure res) <*> acc
|
aFormToWForm $ Map.union <$> wFormToAForm (pure res) <*> acc
|
||||||
|
|
||||||
funcFieldView :: (FormResult (k -> v), Widget) -> MForm m (FormResult (k -> v), [FieldView UniWorX])
|
funcFieldView :: (FormResult (k -> v), Widget) -> MForm m (FormResult (k -> v), [FieldView UniWorX])
|
||||||
funcFieldView (res, fvInput) = do
|
funcFieldView (res, formView) = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
|
fvId <- maybe newIdent return fsId
|
||||||
let fvLabel = toHtml $ mr fsLabel
|
let fvLabel = toHtml $ mr fsLabel
|
||||||
fvTooltip = fmap (toHtml . mr) fsTooltip
|
fvTooltip = fmap (toHtml . mr) fsTooltip
|
||||||
fvRequired = isRequired
|
fvRequired = isRequired
|
||||||
fvErrors
|
fvErrors
|
||||||
| FormFailure (err:_) <- res = Just $ toHtml err
|
| FormFailure (err:_) <- res = Just $ toHtml err
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
fvId <- maybe newIdent return fsId
|
fvInput = $(widgetFile "widgets/fields/funcField")
|
||||||
return (res, pure FieldView{..})
|
return (res, pure FieldView{..})
|
||||||
-- areq nsField (fslI MsgNotificationSettings) (stgNotficationSettings <$> template)
|
-- areq nsField (fslI MsgNotificationSettings) (stgNotficationSettings <$> template)
|
||||||
|
|
||||||
|
|||||||
35
src/Handler/Utils/Profile.hs
Normal file
35
src/Handler/Utils/Profile.hs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
module Handler.Utils.Profile
|
||||||
|
( validDisplayName
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
import qualified Data.MultiSet as MultiSet
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import qualified Data.Char as Char
|
||||||
|
|
||||||
|
|
||||||
|
validDisplayName :: Maybe UserTitle
|
||||||
|
-> UserFirstName
|
||||||
|
-> UserSurname
|
||||||
|
-> UserDisplayName
|
||||||
|
-> Bool
|
||||||
|
validDisplayName (fmap Text.strip -> mTitle) (Text.strip -> fName) (Text.strip -> sName) (Text.strip -> dName)
|
||||||
|
= and [ dNameFrags `MultiSet.isSubsetOf` MultiSet.unions [titleFrags, fNameFrags, sNameFrags]
|
||||||
|
, sName `Text.isInfixOf` dName
|
||||||
|
, all ((<= 1) . Text.length) . filter (Text.any Char.isSpace) $ Text.groupBy ((==) `on` Char.isSpace) dName
|
||||||
|
, dNameLetters `Set.isSubsetOf` Set.unions [titleLetters, fNameLetters, sNameLetters, addLetters]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
titleFrags = MultiSet.fromList $ maybe [] Text.words mTitle
|
||||||
|
fNameFrags = MultiSet.fromList $ Text.words fName
|
||||||
|
sNameFrags = MultiSet.fromList $ Text.words sName
|
||||||
|
dNameFrags = MultiSet.fromList $ Text.words dName
|
||||||
|
|
||||||
|
titleLetters = Set.fromList $ maybe [] unpack mTitle
|
||||||
|
fNameLetters = Set.fromList $ unpack fName
|
||||||
|
sNameLetters = Set.fromList $ unpack sName
|
||||||
|
dNameLetters = Set.fromList $ unpack dName
|
||||||
|
addLetters = Set.fromList [' ']
|
||||||
@ -68,6 +68,7 @@ import Jobs.Handler.SendPasswordReset
|
|||||||
import Jobs.Handler.TransactionLog
|
import Jobs.Handler.TransactionLog
|
||||||
import Jobs.Handler.SynchroniseLdap
|
import Jobs.Handler.SynchroniseLdap
|
||||||
import Jobs.Handler.PruneInvitations
|
import Jobs.Handler.PruneInvitations
|
||||||
|
import Jobs.Handler.ChangeUserDisplayEmail
|
||||||
|
|
||||||
import Jobs.HealthReport
|
import Jobs.HealthReport
|
||||||
|
|
||||||
|
|||||||
@ -212,17 +212,46 @@ determineCrontab = execWriterT $ do
|
|||||||
E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam
|
E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam
|
||||||
return . E.max_ $ examResult E.^. ExamResultLastChanged
|
return . E.max_ $ examResult E.^. ExamResultLastChanged
|
||||||
|
|
||||||
case over (mapped . _Value) ((max `on` NTop) examFinished) newestResult of
|
whenIsJust examVisibleFrom $ \visibleFrom -> do
|
||||||
[E.Value (NTop (Just ts))] ->
|
case over (mapped . _Value) ((max `on` NTop) examFinished) newestResult of
|
||||||
|
[E.Value (NTop (Just ts))] ->
|
||||||
|
tell $ HashMap.singleton
|
||||||
|
(JobCtlQueue $ JobQueueNotification NotificationExamResult{..})
|
||||||
|
Cron
|
||||||
|
{ cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom ts
|
||||||
|
, cronRepeat = CronRepeatOnChange
|
||||||
|
, cronRateLimit = appNotificationRateLimit
|
||||||
|
, cronNotAfter = Left appNotificationExpiration
|
||||||
|
}
|
||||||
|
_other -> return ()
|
||||||
|
|
||||||
|
whenIsJust examRegisterFrom $ \registerFrom ->
|
||||||
tell $ HashMap.singleton
|
tell $ HashMap.singleton
|
||||||
(JobCtlQueue $ JobQueueNotification NotificationExamResult{..})
|
(JobCtlQueue $ JobQueueNotification NotificationExamRegistrationActive{..})
|
||||||
Cron
|
Cron
|
||||||
{ cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ ts
|
{ cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom registerFrom
|
||||||
, cronRepeat = CronRepeatOnChange
|
, cronRepeat = CronRepeatOnChange
|
||||||
, cronRateLimit = appNotificationRateLimit
|
, cronRateLimit = appNotificationRateLimit
|
||||||
, cronNotAfter = Left $ 14 * nominalDay
|
, cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) examRegisterTo
|
||||||
|
}
|
||||||
|
whenIsJust ((,) <$> examRegisterFrom <*> examRegisterTo) $ \(registerFrom, registerTo) ->
|
||||||
|
tell $ HashMap.singleton
|
||||||
|
(JobCtlQueue $ JobQueueNotification NotificationExamRegistrationSoonInactive{..})
|
||||||
|
Cron
|
||||||
|
{ cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) registerTo
|
||||||
|
, cronRepeat = CronRepeatOnChange
|
||||||
|
, cronRateLimit = appNotificationRateLimit
|
||||||
|
, cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ registerTo
|
||||||
|
}
|
||||||
|
whenIsJust ((,) <$> examRegisterFrom <*> examDeregisterUntil) $ \(registerFrom, deregisterUntil) ->
|
||||||
|
tell $ HashMap.singleton
|
||||||
|
(JobCtlQueue $ JobQueueNotification NotificationExamDeregistrationSoonInactive{..})
|
||||||
|
Cron
|
||||||
|
{ cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) deregisterUntil
|
||||||
|
, cronRepeat = CronRepeatOnChange
|
||||||
|
, cronRateLimit = appNotificationRateLimit
|
||||||
|
, cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ deregisterUntil
|
||||||
}
|
}
|
||||||
_other -> return ()
|
|
||||||
|
|
||||||
case examClosed of
|
case examClosed of
|
||||||
Just close -> do
|
Just close -> do
|
||||||
|
|||||||
29
src/Jobs/Handler/ChangeUserDisplayEmail.hs
Normal file
29
src/Jobs/Handler/ChangeUserDisplayEmail.hs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module Jobs.Handler.ChangeUserDisplayEmail
|
||||||
|
( dispatchJobChangeUserDisplayEmail
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Handler.Utils.Mail
|
||||||
|
import qualified Data.HashSet as HashSet
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
import Text.Hamlet
|
||||||
|
|
||||||
|
dispatchJobChangeUserDisplayEmail :: UserId -> UserEmail -> Handler ()
|
||||||
|
dispatchJobChangeUserDisplayEmail jUser jDisplayEmail = do
|
||||||
|
token <- tokenRestrict SetDisplayEmailR jDisplayEmail <$> bearerToken jUser (Just $ HashSet.singleton SetDisplayEmailR) Nothing Nothing Nothing
|
||||||
|
jwt <- encodeToken token
|
||||||
|
let
|
||||||
|
setDisplayEmailUrl :: SomeRoute UniWorX
|
||||||
|
setDisplayEmailUrl = SomeRoute (SetDisplayEmailR, [(toPathPiece GetBearer, toPathPiece jwt)])
|
||||||
|
setDisplayEmailUrl' <- toTextUrl setDisplayEmailUrl
|
||||||
|
|
||||||
|
user@User{..} <- runDB $ getJust jUser
|
||||||
|
|
||||||
|
userMailT jUser $ do
|
||||||
|
_mailTo .= pure (userAddress user & _addressEmail .~ CI.original jDisplayEmail)
|
||||||
|
replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
|
||||||
|
setSubjectI MsgMailSubjectChangeUserDisplayEmail
|
||||||
|
addAlternatives $
|
||||||
|
providePreferredAlternative ($(ihamletFile "templates/mail/changeUserDisplayEmail.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
@ -75,6 +75,29 @@ determineNotificationCandidates NotificationUserRightsUpdate{..} = do
|
|||||||
return . nub $ affectedUser <> affectedAdmins
|
return . nub $ affectedUser <> affectedAdmins
|
||||||
determineNotificationCandidates NotificationUserAuthModeUpdate{..}
|
determineNotificationCandidates NotificationUserAuthModeUpdate{..}
|
||||||
= selectList [UserId ==. nUser] []
|
= selectList [UserId ==. nUser] []
|
||||||
|
determineNotificationCandidates NotificationExamRegistrationActive{..} =
|
||||||
|
E.select . E.from $ \(exam `E.InnerJoin` courseParticipant `E.InnerJoin` user) -> do
|
||||||
|
E.on $ courseParticipant E.^. CourseParticipantUser E.==. user E.^. UserId
|
||||||
|
E.on $ courseParticipant E.^. CourseParticipantCourse E.==. exam E.^. ExamCourse
|
||||||
|
E.where_ $ exam E.^. ExamId E.==. E.val nExam
|
||||||
|
E.where_ . E.not_ . E.exists . E.from $ \examRegistration ->
|
||||||
|
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId
|
||||||
|
E.&&. examRegistration E.^. ExamRegistrationExam E.==. E.val nExam
|
||||||
|
return user
|
||||||
|
determineNotificationCandidates NotificationExamRegistrationSoonInactive{..} =
|
||||||
|
E.select . E.from $ \(exam `E.InnerJoin` courseParticipant `E.InnerJoin` user) -> do
|
||||||
|
E.on $ courseParticipant E.^. CourseParticipantUser E.==. user E.^. UserId
|
||||||
|
E.on $ courseParticipant E.^. CourseParticipantCourse E.==. exam E.^. ExamCourse
|
||||||
|
E.where_ $ exam E.^. ExamId E.==. E.val nExam
|
||||||
|
E.where_ . E.not_ . E.exists . E.from $ \examRegistration ->
|
||||||
|
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId
|
||||||
|
E.&&. examRegistration E.^. ExamRegistrationExam E.==. E.val nExam
|
||||||
|
return user
|
||||||
|
determineNotificationCandidates NotificationExamDeregistrationSoonInactive{..} =
|
||||||
|
E.select . E.from $ \(examRegistration `E.InnerJoin` user) -> do
|
||||||
|
E.on $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId
|
||||||
|
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val nExam
|
||||||
|
return user
|
||||||
determineNotificationCandidates notif@NotificationExamResult{..} = do
|
determineNotificationCandidates notif@NotificationExamResult{..} = do
|
||||||
lastExec <- fmap (fmap $ cronLastExecTime . entityVal) . getBy . UniqueCronLastExec . toJSON $ JobQueueNotification notif
|
lastExec <- fmap (fmap $ cronLastExecTime . entityVal) . getBy . UniqueCronLastExec . toJSON $ JobQueueNotification notif
|
||||||
E.select . E.from $ \(examResult `E.InnerJoin` user) -> E.distinctOnOrderBy [E.asc $ user E.^. UserId] $ do
|
E.select . E.from $ \(examResult `E.InnerJoin` user) -> E.distinctOnOrderBy [E.asc $ user E.^. UserId] $ do
|
||||||
@ -183,18 +206,21 @@ classifyNotification NotificationSubmissionRated{..} = do
|
|||||||
return $ case sheetType of
|
return $ case sheetType of
|
||||||
NotGraded -> NTSubmissionRated
|
NotGraded -> NTSubmissionRated
|
||||||
_other -> NTSubmissionRatedGraded
|
_other -> NTSubmissionRatedGraded
|
||||||
classifyNotification NotificationSheetActive{} = return NTSheetActive
|
classifyNotification NotificationSheetActive{} = return NTSheetActive
|
||||||
classifyNotification NotificationSheetSoonInactive{} = return NTSheetSoonInactive
|
classifyNotification NotificationSheetSoonInactive{} = return NTSheetSoonInactive
|
||||||
classifyNotification NotificationSheetInactive{} = return NTSheetInactive
|
classifyNotification NotificationSheetInactive{} = return NTSheetInactive
|
||||||
classifyNotification NotificationCorrectionsAssigned{} = return NTCorrectionsAssigned
|
classifyNotification NotificationCorrectionsAssigned{} = return NTCorrectionsAssigned
|
||||||
classifyNotification NotificationCorrectionsNotDistributed{} = return NTCorrectionsNotDistributed
|
classifyNotification NotificationCorrectionsNotDistributed{} = return NTCorrectionsNotDistributed
|
||||||
classifyNotification NotificationUserRightsUpdate{} = return NTUserRightsUpdate
|
classifyNotification NotificationUserRightsUpdate{} = return NTUserRightsUpdate
|
||||||
classifyNotification NotificationUserAuthModeUpdate{} = return NTUserAuthModeUpdate
|
classifyNotification NotificationUserAuthModeUpdate{} = return NTUserAuthModeUpdate
|
||||||
classifyNotification NotificationExamResult{} = return NTExamResult
|
classifyNotification NotificationExamRegistrationActive{} = return NTExamRegistrationActive
|
||||||
classifyNotification NotificationAllocationStaffRegister{} = return NTAllocationStaffRegister
|
classifyNotification NotificationExamRegistrationSoonInactive{} = return NTExamRegistrationSoonInactive
|
||||||
classifyNotification NotificationAllocationAllocation{} = return NTAllocationAllocation
|
classifyNotification NotificationExamDeregistrationSoonInactive{} = return NTExamDeregistrationSoonInactive
|
||||||
classifyNotification NotificationAllocationRegister{} = return NTAllocationRegister
|
classifyNotification NotificationExamResult{} = return NTExamResult
|
||||||
classifyNotification NotificationAllocationOutdatedRatings{} = return NTAllocationOutdatedRatings
|
classifyNotification NotificationAllocationStaffRegister{} = return NTAllocationStaffRegister
|
||||||
classifyNotification NotificationAllocationUnratedApplications{} = return NTAllocationUnratedApplications
|
classifyNotification NotificationAllocationAllocation{} = return NTAllocationAllocation
|
||||||
classifyNotification NotificationExamOfficeExamResults{} = return NTExamOfficeExamResults
|
classifyNotification NotificationAllocationRegister{} = return NTAllocationRegister
|
||||||
classifyNotification NotificationExamOfficeExamResultsChanged{} = return NTExamOfficeExamResultsChanged
|
classifyNotification NotificationAllocationOutdatedRatings{} = return NTAllocationOutdatedRatings
|
||||||
|
classifyNotification NotificationAllocationUnratedApplications{} = return NTAllocationUnratedApplications
|
||||||
|
classifyNotification NotificationExamOfficeExamResults{} = return NTExamOfficeExamResults
|
||||||
|
classifyNotification NotificationExamOfficeExamResultsChanged{} = return NTExamOfficeExamResultsChanged
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import Jobs.Handler.SendNotification.CorrectionsAssigned
|
|||||||
import Jobs.Handler.SendNotification.CorrectionsNotDistributed
|
import Jobs.Handler.SendNotification.CorrectionsNotDistributed
|
||||||
import Jobs.Handler.SendNotification.UserRightsUpdate
|
import Jobs.Handler.SendNotification.UserRightsUpdate
|
||||||
import Jobs.Handler.SendNotification.UserAuthModeUpdate
|
import Jobs.Handler.SendNotification.UserAuthModeUpdate
|
||||||
|
import Jobs.Handler.SendNotification.ExamActive
|
||||||
import Jobs.Handler.SendNotification.ExamResult
|
import Jobs.Handler.SendNotification.ExamResult
|
||||||
import Jobs.Handler.SendNotification.Allocation
|
import Jobs.Handler.SendNotification.Allocation
|
||||||
import Jobs.Handler.SendNotification.ExamOffice
|
import Jobs.Handler.SendNotification.ExamOffice
|
||||||
|
|||||||
78
src/Jobs/Handler/SendNotification/ExamActive.hs
Normal file
78
src/Jobs/Handler/SendNotification/ExamActive.hs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-} -- ihamletFile discards do results
|
||||||
|
|
||||||
|
module Jobs.Handler.SendNotification.ExamActive
|
||||||
|
( dispatchNotificationExamRegistrationActive
|
||||||
|
, dispatchNotificationExamRegistrationSoonInactive
|
||||||
|
, dispatchNotificationExamDeregistrationSoonInactive
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Handler.Utils.Mail
|
||||||
|
import Jobs.Handler.SendNotification.Utils
|
||||||
|
|
||||||
|
import Text.Hamlet
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
dispatchNotificationExamRegistrationActive :: ExamId -> UserId -> Handler ()
|
||||||
|
dispatchNotificationExamRegistrationActive nExam jRecipient = userMailT jRecipient $ do
|
||||||
|
(Course{..}, Exam{..}) <- liftHandlerT . runDB $ do
|
||||||
|
exam <- getJust nExam
|
||||||
|
course <- belongsToJust examCourse exam
|
||||||
|
return (course, exam)
|
||||||
|
replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
|
||||||
|
setSubjectI $ MsgMailSubjectExamRegistrationActive courseShorthand examName
|
||||||
|
|
||||||
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
|
tid = courseTerm
|
||||||
|
ssh = courseSchool
|
||||||
|
csh = courseShorthand
|
||||||
|
examn = examName
|
||||||
|
|
||||||
|
editNotifications <- mkEditNotifications jRecipient
|
||||||
|
|
||||||
|
addAlternatives $
|
||||||
|
providePreferredAlternative ($(ihamletFile "templates/mail/examRegistrationActive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
|
|
||||||
|
dispatchNotificationExamRegistrationSoonInactive :: ExamId -> UserId -> Handler ()
|
||||||
|
dispatchNotificationExamRegistrationSoonInactive nExam jRecipient = userMailT jRecipient $ do
|
||||||
|
(Course{..}, Exam{..}) <- liftHandlerT . runDB $ do
|
||||||
|
exam <- getJust nExam
|
||||||
|
course <- belongsToJust examCourse exam
|
||||||
|
return (course, exam)
|
||||||
|
replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
|
||||||
|
setSubjectI $ MsgMailSubjectExamRegistrationSoonInactive courseShorthand examName
|
||||||
|
|
||||||
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
|
tid = courseTerm
|
||||||
|
ssh = courseSchool
|
||||||
|
csh = courseShorthand
|
||||||
|
examn = examName
|
||||||
|
|
||||||
|
editNotifications <- mkEditNotifications jRecipient
|
||||||
|
|
||||||
|
addAlternatives $
|
||||||
|
providePreferredAlternative ($(ihamletFile "templates/mail/examRegistrationSoonInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
|
|
||||||
|
dispatchNotificationExamDeregistrationSoonInactive :: ExamId -> UserId -> Handler ()
|
||||||
|
dispatchNotificationExamDeregistrationSoonInactive nExam jRecipient = userMailT jRecipient $ do
|
||||||
|
(Course{..}, Exam{..}) <- liftHandlerT . runDB $ do
|
||||||
|
exam <- getJust nExam
|
||||||
|
course <- belongsToJust examCourse exam
|
||||||
|
return (course, exam)
|
||||||
|
replaceMailHeader "Auto-Submitted" $ Just "auto-generated"
|
||||||
|
setSubjectI $ MsgMailSubjectExamDeregistrationSoonInactive courseShorthand examName
|
||||||
|
|
||||||
|
MsgRenderer mr <- getMailMsgRenderer
|
||||||
|
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
|
||||||
|
tid = courseTerm
|
||||||
|
ssh = courseSchool
|
||||||
|
csh = courseShorthand
|
||||||
|
examn = examName
|
||||||
|
|
||||||
|
editNotifications <- mkEditNotifications jRecipient
|
||||||
|
|
||||||
|
addAlternatives $
|
||||||
|
providePreferredAlternative ($(ihamletFile "templates/mail/examDeregistrationSoonInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
|
||||||
@ -64,6 +64,9 @@ data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notifica
|
|||||||
}
|
}
|
||||||
| JobSynchroniseLdapUser { jUser :: UserId
|
| JobSynchroniseLdapUser { jUser :: UserId
|
||||||
}
|
}
|
||||||
|
| JobChangeUserDisplayEmail { jUser :: UserId
|
||||||
|
, jDisplayEmail :: UserEmail
|
||||||
|
}
|
||||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
||||||
| NotificationSheetActive { nSheet :: SheetId }
|
| NotificationSheetActive { nSheet :: SheetId }
|
||||||
@ -73,6 +76,9 @@ data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
|||||||
| NotificationCorrectionsNotDistributed { nSheet :: SheetId }
|
| NotificationCorrectionsNotDistributed { nSheet :: SheetId }
|
||||||
| NotificationUserRightsUpdate { nUser :: UserId, nOriginalRights :: Set (SchoolFunction, SchoolShorthand) }
|
| NotificationUserRightsUpdate { nUser :: UserId, nOriginalRights :: Set (SchoolFunction, SchoolShorthand) }
|
||||||
| NotificationUserAuthModeUpdate { nUser :: UserId, nOriginalAuthMode :: AuthenticationMode }
|
| NotificationUserAuthModeUpdate { nUser :: UserId, nOriginalAuthMode :: AuthenticationMode }
|
||||||
|
| NotificationExamRegistrationActive { nExam :: ExamId }
|
||||||
|
| NotificationExamRegistrationSoonInactive { nExam :: ExamId }
|
||||||
|
| NotificationExamDeregistrationSoonInactive { nExam :: ExamId }
|
||||||
| NotificationExamResult { nExam :: ExamId }
|
| NotificationExamResult { nExam :: ExamId }
|
||||||
| NotificationAllocationStaffRegister { nAllocation :: AllocationId }
|
| NotificationAllocationStaffRegister { nAllocation :: AllocationId }
|
||||||
| NotificationAllocationRegister { nAllocation :: AllocationId }
|
| NotificationAllocationRegister { nAllocation :: AllocationId }
|
||||||
|
|||||||
@ -485,6 +485,14 @@ customMigrations = Map.fromListWith (>>)
|
|||||||
DELETE FROM "invitation" WHERE "for"->'junction' = '"UserLecturer"';
|
DELETE FROM "invitation" WHERE "for"->'junction' = '"UserLecturer"';
|
||||||
|]
|
|]
|
||||||
)
|
)
|
||||||
|
, ( AppliedMigrationKey [migrationVersion|19.0.0|] [version|20.0.0|]
|
||||||
|
, whenM (tableExists "user") $ do
|
||||||
|
[executeQQ|
|
||||||
|
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "display_email" citext;
|
||||||
|
UPDATE "user" SET "display_email" = "email" WHERE "display_email" IS NULL;
|
||||||
|
ALTER TABLE "user" ALTER COLUMN "display_email" SET NOT NULL;
|
||||||
|
|]
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,10 @@ type Points = Centi
|
|||||||
|
|
||||||
type Email = Text
|
type Email = Text
|
||||||
|
|
||||||
type UserDisplayName = Text
|
type UserTitle = Text
|
||||||
|
type UserFirstName = Text
|
||||||
type UserSurname = Text
|
type UserSurname = Text
|
||||||
|
type UserDisplayName = Text
|
||||||
type UserMatriculation = Text
|
type UserMatriculation = Text
|
||||||
|
|
||||||
type StudyDegreeName = Text
|
type StudyDegreeName = Text
|
||||||
|
|||||||
@ -31,6 +31,9 @@ data NotificationTrigger
|
|||||||
| NTCorrectionsNotDistributed
|
| NTCorrectionsNotDistributed
|
||||||
| NTUserRightsUpdate
|
| NTUserRightsUpdate
|
||||||
| NTUserAuthModeUpdate
|
| NTUserAuthModeUpdate
|
||||||
|
| NTExamRegistrationActive
|
||||||
|
| NTExamRegistrationSoonInactive
|
||||||
|
| NTExamDeregistrationSoonInactive
|
||||||
| NTExamResult
|
| NTExamResult
|
||||||
| NTAllocationStaffRegister
|
| NTAllocationStaffRegister
|
||||||
| NTAllocationAllocation
|
| NTAllocationAllocation
|
||||||
@ -67,6 +70,7 @@ instance Default NotificationSettings where
|
|||||||
defaultOff :: HashSet NotificationTrigger
|
defaultOff :: HashSet NotificationTrigger
|
||||||
defaultOff = HashSet.fromList
|
defaultOff = HashSet.fromList
|
||||||
[ NTSheetSoonInactive
|
[ NTSheetSoonInactive
|
||||||
|
, NTExamRegistrationSoonInactive
|
||||||
]
|
]
|
||||||
|
|
||||||
instance ToJSON NotificationSettings where
|
instance ToJSON NotificationSettings where
|
||||||
|
|||||||
22
templates/course/applications-list.hamlet
Normal file
22
templates/course/applications-list.hamlet
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
$newline never
|
||||||
|
$if not (null allocationsBounds)
|
||||||
|
<h2>_{MsgCourseAllocationsBounds (length allocationsBounds)}
|
||||||
|
<dl .deflist>
|
||||||
|
$forall (Allocation{allocationName, allocationRegisterTo}, numApps, numFirstChoice, capped) <- allocationsBounds
|
||||||
|
<dt .deflist__dt>
|
||||||
|
#{allocationName}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<p>
|
||||||
|
$if numApps == numFirstChoice
|
||||||
|
_{MsgCourseAllocationsBoundCoincide numFirstChoice}
|
||||||
|
$else
|
||||||
|
_{MsgCourseAllocationsBound numApps numFirstChoice}
|
||||||
|
$if capped
|
||||||
|
<p .bound_explanation>
|
||||||
|
_{MsgCourseAllocationsBoundCapped}
|
||||||
|
$if registrationOpen allocationRegisterTo
|
||||||
|
<p .bound_explanation>
|
||||||
|
_{MsgCourseAllocationsBoundWarningOpen}
|
||||||
|
|
||||||
|
<h2>_{MsgMenuCourseApplications}
|
||||||
|
^{table}
|
||||||
4
templates/course/applications-list.lucius
Normal file
4
templates/course/applications-list.lucius
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.bound_explanation {
|
||||||
|
color: var(--color-fontsec);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
@ -1,5 +1,19 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
|
<dt .deflist__dt>
|
||||||
|
^{formatGregorianW 2019 09 13}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<ul>
|
||||||
|
<li>Benachrichtigungen bzgl. Klausur An- und Abmeldung
|
||||||
|
|
||||||
|
<dt .deflist__dt>
|
||||||
|
^{formatGregorianW 2019 09 12}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
<ul>
|
||||||
|
<li>Abschätzung der durch Zentralanmeldung benötigten Kurskapazität
|
||||||
|
<li>Anpassbare angezeigte Namen
|
||||||
|
<li>Anpassbare angezeigte E-Mail Adressen
|
||||||
|
|
||||||
<dt .deflist__dt>
|
<dt .deflist__dt>
|
||||||
^{formatGregorianW 2019 09 05}
|
^{formatGregorianW 2019 09 05}
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
|
|||||||
9
templates/i18n/profile/displayNameRules/de.hamlet
Normal file
9
templates/i18n/profile/displayNameRules/de.hamlet
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
$newline never
|
||||||
|
<ul>
|
||||||
|
<li>Der Nachname („#{userSurname}“) muss im angezeigten Namen vollständig enthalten sein.
|
||||||
|
$maybe title <- userTitle
|
||||||
|
<li>Der angezeigte Name muss vollständig aus Fragmenten des akademischen Titels („#{title}“), des Vornamens („#{userFirstName}“) und des Nachnamens („#{userSurname}“) bestehen.
|
||||||
|
$nothing
|
||||||
|
<li>Der angezeigte Name muss vollständig aus Fragmenten des Vornamens („#{userFirstName}“) und des Nachnamens („#{userSurname}“) bestehen.
|
||||||
|
<li>Der angezeigte Name darf keine mehrfachen Leerzeichen enthalten.
|
||||||
|
<li>Der angezeigter Name darf keine Sonderzeichen enthalten, die in keinem der Namensbestandteile vorkommen.
|
||||||
4
templates/i18n/set-display-email/de.hamlet
Normal file
4
templates/i18n/set-display-email/de.hamlet
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
$newline never
|
||||||
|
<p>
|
||||||
|
Möchten Sie die, öffentlich im Zusammenhang mit Ihrem Namen angezeigte, E-Mail Adresse wirklich auf „#{displayEmail}“ setzen?
|
||||||
|
^{btnView'}
|
||||||
19
templates/mail/changeUserDisplayEmail.hamlet
Normal file
19
templates/mail/changeUserDisplayEmail.hamlet
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
$newline never
|
||||||
|
\<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
_{MsgMailTitleChangeUserDisplayEmail userDisplayName}
|
||||||
|
<p>
|
||||||
|
_{MsgMailIntroChangeUserDisplayEmail jDisplayEmail}
|
||||||
|
<p>
|
||||||
|
<a href=#{setDisplayEmailUrl'}>
|
||||||
|
_{MsgBtnSetDisplayEmail}
|
||||||
18
templates/mail/examDeregistrationSoonInactive.hamlet
Normal file
18
templates/mail/examDeregistrationSoonInactive.hamlet
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
$newline never
|
||||||
|
\<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
_{MsgMailExamDeregistrationSoonInactiveIntro (CI.original courseName) termDesc examName}
|
||||||
|
<p>
|
||||||
|
<a href=@{CExamR tid ssh csh examn EShowR}>
|
||||||
|
#{examName}
|
||||||
|
^{editNotifications}
|
||||||
18
templates/mail/examRegistrationActive.hamlet
Normal file
18
templates/mail/examRegistrationActive.hamlet
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
$newline never
|
||||||
|
\<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
_{MsgMailExamRegistrationActiveIntro (CI.original courseName) termDesc examName}
|
||||||
|
<p>
|
||||||
|
<a href=@{CExamR tid ssh csh examn EShowR}>
|
||||||
|
#{examName}
|
||||||
|
^{editNotifications}
|
||||||
18
templates/mail/examRegistrationSoonInactive.hamlet
Normal file
18
templates/mail/examRegistrationSoonInactive.hamlet
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
$newline never
|
||||||
|
\<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
_{MsgMailExamRegistrationSoonInactiveIntro (CI.original courseName) termDesc examName}
|
||||||
|
<p>
|
||||||
|
<a href=@{CExamR tid ssh csh examn EShowR}>
|
||||||
|
#{examName}
|
||||||
|
^{editNotifications}
|
||||||
@ -1,6 +1,10 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<section>
|
<section>
|
||||||
^{settingsForm}
|
^{settingsForm}
|
||||||
|
<section>
|
||||||
|
<h3>_{MsgUserDisplayNameRules}
|
||||||
|
<p>
|
||||||
|
^{displayNameRules}
|
||||||
<section>
|
<section>
|
||||||
^{tokenExplanation}
|
^{tokenExplanation}
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
3
templates/widgets/fields/funcField.hamlet
Normal file
3
templates/widgets/fields/funcField.hamlet
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
$newline never
|
||||||
|
<div ##{fvId <> "-wrapper"}>
|
||||||
|
^{formView}
|
||||||
4
templates/widgets/fields/funcField.lucius
Normal file
4
templates/widgets/fields/funcField.lucius
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
##{fvId <> "-wrapper"} {
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
@ -93,6 +93,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Just now
|
, userTokensIssuedAfter = Just now
|
||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Nothing
|
||||||
, userEmail = "G.Kleen@campus.lmu.de"
|
, userEmail = "G.Kleen@campus.lmu.de"
|
||||||
|
, userDisplayEmail = "gregor.kleen@ifi.lmu.de"
|
||||||
, userDisplayName = "Gregor Kleen"
|
, userDisplayName = "Gregor Kleen"
|
||||||
, userSurname = "Kleen"
|
, userSurname = "Kleen"
|
||||||
, userFirstName = "Gregor Julius Arthur"
|
, userFirstName = "Gregor Julius Arthur"
|
||||||
@ -116,6 +117,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Nothing
|
||||||
, userEmail = "felix.hamann@campus.lmu.de"
|
, userEmail = "felix.hamann@campus.lmu.de"
|
||||||
|
, userDisplayEmail = "felix.hamann@campus.lmu.de"
|
||||||
, userDisplayName = "Felix Hamann"
|
, userDisplayName = "Felix Hamann"
|
||||||
, userSurname = "Hamann"
|
, userSurname = "Hamann"
|
||||||
, userFirstName = "Felix"
|
, userFirstName = "Felix"
|
||||||
@ -139,6 +141,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Nothing
|
||||||
, userEmail = "jost@tcs.ifi.lmu.de"
|
, userEmail = "jost@tcs.ifi.lmu.de"
|
||||||
|
, userDisplayEmail = "jost@tcs.ifi.lmu.de"
|
||||||
, userDisplayName = "Steffen Jost"
|
, userDisplayName = "Steffen Jost"
|
||||||
, userSurname = "Jost"
|
, userSurname = "Jost"
|
||||||
, userFirstName = "Steffen"
|
, userFirstName = "Steffen"
|
||||||
@ -162,6 +165,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Just "1299"
|
, userMatrikelnummer = Just "1299"
|
||||||
, userEmail = "max@campus.lmu.de"
|
, userEmail = "max@campus.lmu.de"
|
||||||
|
, userDisplayEmail = "max@max.com"
|
||||||
, userDisplayName = "Max Musterstudent"
|
, userDisplayName = "Max Musterstudent"
|
||||||
, userSurname = "Musterstudent"
|
, userSurname = "Musterstudent"
|
||||||
, userFirstName = "Max"
|
, userFirstName = "Max"
|
||||||
@ -185,6 +189,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Just "999"
|
, userMatrikelnummer = Just "999"
|
||||||
, userEmail = "tester@campus.lmu.de"
|
, userEmail = "tester@campus.lmu.de"
|
||||||
|
, userDisplayEmail = "tina@tester.example"
|
||||||
, userDisplayName = "Tina Tester"
|
, userDisplayName = "Tina Tester"
|
||||||
, userSurname = "von Terror"
|
, userSurname = "von Terror"
|
||||||
, userFirstName = "Sabrina"
|
, userFirstName = "Sabrina"
|
||||||
@ -208,6 +213,7 @@ fillDb = do
|
|||||||
, userTokensIssuedAfter = Nothing
|
, userTokensIssuedAfter = Nothing
|
||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Nothing
|
||||||
, userEmail = "vaupel.sarah@campus.lmu.de"
|
, userEmail = "vaupel.sarah@campus.lmu.de"
|
||||||
|
, userDisplayEmail = "vaupel.sarah@campus.lmu.de"
|
||||||
, userDisplayName = "Sarah Vaupel"
|
, userDisplayName = "Sarah Vaupel"
|
||||||
, userSurname = "Vaupel"
|
, userSurname = "Vaupel"
|
||||||
, userFirstName = "Sarah"
|
, userFirstName = "Sarah"
|
||||||
|
|||||||
@ -80,6 +80,7 @@ instance Arbitrary User where
|
|||||||
userTokensIssuedAfter <- arbitrary
|
userTokensIssuedAfter <- arbitrary
|
||||||
userMatrikelnummer <- fmap pack . assertM' (not . null) <$> listOf (elements ['0'..'9'])
|
userMatrikelnummer <- fmap pack . assertM' (not . null) <$> listOf (elements ['0'..'9'])
|
||||||
userEmail <- CI.mk . decodeUtf8 . Email.toByteString <$> arbitrary
|
userEmail <- CI.mk . decodeUtf8 . Email.toByteString <$> arbitrary
|
||||||
|
userDisplayEmail <- CI.mk . decodeUtf8 . Email.toByteString <$> arbitrary
|
||||||
|
|
||||||
names <- listOf1 $ pack . getPrintableString <$> arbitrary
|
names <- listOf1 $ pack . getPrintableString <$> arbitrary
|
||||||
userDisplayName <- unwords <$> sublistOf names
|
userDisplayName <- unwords <$> sublistOf names
|
||||||
|
|||||||
@ -116,6 +116,7 @@ createUser adjUser = do
|
|||||||
userTokensIssuedAfter = Nothing
|
userTokensIssuedAfter = Nothing
|
||||||
userIdent = "dummy@example.invalid"
|
userIdent = "dummy@example.invalid"
|
||||||
userEmail = "dummy@example.invalid"
|
userEmail = "dummy@example.invalid"
|
||||||
|
userDisplayEmail = "dummy@example.invalid"
|
||||||
userDisplayName = "Dummy Example"
|
userDisplayName = "Dummy Example"
|
||||||
userSurname = "Example"
|
userSurname = "Example"
|
||||||
userFirstName = "Dummy"
|
userFirstName = "Dummy"
|
||||||
|
|||||||
Reference in New Issue
Block a user