Require phone number to register
This commit is contained in:
		
							parent
							
								
									33661d5716
								
							
						
					
					
						commit
						60d9706fe6
					
				| 
						 | 
				
			
			@ -21,7 +21,22 @@ emailField = Field
 | 
			
		|||
    , fieldView = \id name otherAttributes result isRequired ->
 | 
			
		||||
        let result' = either (\x -> x) renderEmail result
 | 
			
		||||
        in [whamlet|
 | 
			
		||||
               <input type="email" id="#{id}" name="#{name}" value="#{result'}" *{otherAttributes} :isRequired:required="">
 | 
			
		||||
               <input type="email" id="#{id}" name="#{name}" value="#{result'}" *{otherAttributes} :isRequired:required="true">
 | 
			
		||||
           |]
 | 
			
		||||
    , fieldEnctype = UrlEncoded
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
telephoneField :: Field Handler PhoneNumber
 | 
			
		||||
telephoneField = Field
 | 
			
		||||
    { fieldParse = \rawValues _ ->
 | 
			
		||||
        case rawValues of
 | 
			
		||||
          [] -> pure $ Right Nothing
 | 
			
		||||
          [x] -> pure $ maybe (Left "could not parse as a phone number") (Right . Just) $ toPhoneNumber x
 | 
			
		||||
          _ -> pure $ Left $ "Expected one value"
 | 
			
		||||
    , fieldView = \id name otherAttributes result isRequired ->
 | 
			
		||||
        let result' = either (\x -> x) renderPhoneNumber result
 | 
			
		||||
        in [whamlet|
 | 
			
		||||
               <input type="tel" id="#{id}" name="#{name}" value="#{result'}" *{otherAttributes} :isRequired:required="true">
 | 
			
		||||
           |]
 | 
			
		||||
    , fieldEnctype = UrlEncoded
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ query Applications {
 | 
			
		|||
    name
 | 
			
		||||
    nickname
 | 
			
		||||
    email
 | 
			
		||||
    phoneNumber
 | 
			
		||||
    homeplace
 | 
			
		||||
    birthdate
 | 
			
		||||
    application
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +86,9 @@ applicationsW applications = do
 | 
			
		|||
            <tr>
 | 
			
		||||
                <th scope="row">Sähköposti
 | 
			
		||||
                <td>#{renderEmail $ fromJust $ email}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th scope="row">Puhelinnumero
 | 
			
		||||
                <td>#{renderPhoneNumber $ phoneNumber}
 | 
			
		||||
    $maybe route <- acceptRoute
 | 
			
		||||
        <form action="@{route}" method="post" enctype="#{enctype}">
 | 
			
		||||
            ^{widget}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,16 +25,10 @@ import Data.Time (Day)
 | 
			
		|||
 | 
			
		||||
declareLocalTypesInline "schema.gql" [raw|
 | 
			
		||||
mutation Apply($name: String!, $nickname: String, $homeplace: String!,
 | 
			
		||||
    $birthdate: Date!, $email: Email!, $password: String!, $application: String!) {
 | 
			
		||||
    $birthdate: Date!, $email: Email!, $phoneNumber: PhoneNumber!, $password: String!, $application: String!) {
 | 
			
		||||
  apply(email: $email, password: $password, name: $name, nickname: $nickname,
 | 
			
		||||
      birthdate: $birthdate, homeplace: $homeplace, application: $application) {
 | 
			
		||||
      birthdate: $birthdate, homeplace: $homeplace, application: $application, phoneNumber: $phoneNumber) {
 | 
			
		||||
    id
 | 
			
		||||
    name
 | 
			
		||||
    nickname
 | 
			
		||||
    email
 | 
			
		||||
    pendingEmail
 | 
			
		||||
    homeplace
 | 
			
		||||
    birthdate
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
|]
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +40,7 @@ applyForm = renderDivs $ ApplyArgs
 | 
			
		|||
    <*> areq textField homeplaceSettings Nothing
 | 
			
		||||
    <*> areq dayField' birthdateSettings Nothing
 | 
			
		||||
    <*> areq emailField emailSettings Nothing
 | 
			
		||||
    <*> areq telephoneField phoneSettings Nothing
 | 
			
		||||
    <*> areq verifiedPasswordField "Salasana" Nothing
 | 
			
		||||
    <*> areq textareaField' applicationSettings Nothing
 | 
			
		||||
        where dayField' :: Field Handler Date
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +53,7 @@ applyForm = renderDivs $ ApplyArgs
 | 
			
		|||
              homeplaceSettings = "Kotipaikka" {fsAttrs = [("placeholder","Espoo")]}
 | 
			
		||||
              birthdateSettings = "Syntymäaika" {fsAttrs = [("placeholder","2000-01-01")]}
 | 
			
		||||
              emailSettings = "Sähköposti" {fsAttrs = [("placeholder","erkki.juhani@esimerkki.fi")]}
 | 
			
		||||
              phoneSettings = "Puhelinnumero" {fsAttrs = [("placeholder","+358 12 34567890")]}
 | 
			
		||||
              applicationSettings = "Hakemus (eli miksi olet data)"
 | 
			
		||||
                  {fsAttrs = [("placeholder","Aloitin opiskelun Otaniemen datalla vuonna 2020.")]}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,14 +32,15 @@ query ProfilePage($id: UserID) {
 | 
			
		|||
    pendingEmail
 | 
			
		||||
    homeplace
 | 
			
		||||
    birthdate
 | 
			
		||||
    phoneNumber
 | 
			
		||||
  }
 | 
			
		||||
  permissions
 | 
			
		||||
}
 | 
			
		||||
|]
 | 
			
		||||
 | 
			
		||||
declareLocalTypesInline "schema.gql" [raw|
 | 
			
		||||
mutation UpdateProfile($user: UserID, $name: String, $homeplace: String, $nickname: String, $email: Email) {
 | 
			
		||||
  update(user: $user, name: $name, homeplace: $homeplace, nickname: $nickname, email: $email) {
 | 
			
		||||
mutation UpdateProfile($user: UserID, $name: String, $homeplace: String, $nickname: String, $email: Email, $phoneNumber: PhoneNumber) {
 | 
			
		||||
  update(user: $user, name: $name, homeplace: $homeplace, nickname: $nickname, email: $email, phoneNumber: $phoneNumber) {
 | 
			
		||||
    id
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,11 +59,18 @@ passwordForm = renderDivs $ areq verifiedPasswordField "Uusi salasana" Nothing
 | 
			
		|||
 | 
			
		||||
profileForm :: (Maybe UserID) -> (Maybe ProfilePageUser) -> Form UpdateProfileArgs
 | 
			
		||||
profileForm userID user extraHtml = do
 | 
			
		||||
    (nameRes, nameView) <- mopt textField "Nimi" (Just $ maybe Nothing (\x -> Just $ name (x ::ProfilePageUser)) user)
 | 
			
		||||
    (homeRes, homeView) <- mopt textField "Kotipaikka" (Just $ maybe Nothing (\x -> Just $ homeplace (x :: ProfilePageUser)) user)
 | 
			
		||||
    (nicknameRes, nicknameView) <- mopt textField "Kutsumanimi" (Just $ maybe Nothing (\x -> Just $ nickname (x :: ProfilePageUser)) user)
 | 
			
		||||
    (emailRes, emailView) <- mopt emailField "Sähköposti" (maybe Nothing (\x -> Just $ email (x :: ProfilePageUser)) user)
 | 
			
		||||
    let profileUpdateRes = UpdateProfileArgs userID <$> nameRes <*> homeRes <*> nicknameRes <*> emailRes
 | 
			
		||||
    (nameRes, nameView) <- mopt textField "Nimi"
 | 
			
		||||
        (Just $ maybe Nothing (\x -> Just $ name (x :: ProfilePageUser)) user)
 | 
			
		||||
    (homeRes, homeView) <- mopt textField "Kotipaikka"
 | 
			
		||||
        (Just $ maybe Nothing (\x -> Just $ homeplace (x :: ProfilePageUser)) user)
 | 
			
		||||
    (nicknameRes, nicknameView) <- mopt textField "Kutsumanimi"
 | 
			
		||||
        (Just $ maybe Nothing (\x -> Just $ nickname (x :: ProfilePageUser)) user)
 | 
			
		||||
    (emailRes, emailView) <- mopt emailField "Sähköposti"
 | 
			
		||||
        (maybe Nothing (\x -> Just $ email (x :: ProfilePageUser)) user)
 | 
			
		||||
    (phoneNumberRes, phoneNumberView) <- mopt telephoneField "Puhelinnumero"
 | 
			
		||||
        (Just $ maybe Nothing (\x -> Just $ phoneNumber (x :: ProfilePageUser)) user)
 | 
			
		||||
    let profileUpdateRes = UpdateProfileArgs userID <$>
 | 
			
		||||
            nameRes <*> homeRes <*> nicknameRes <*> emailRes <*> phoneNumberRes
 | 
			
		||||
        maybePendingEmail = user >>= \x -> pendingEmail (x :: ProfilePageUser)
 | 
			
		||||
        inputField FieldView {..} = [whamlet|
 | 
			
		||||
            <label for="#{fvId}">
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +82,7 @@ profileForm userID user extraHtml = do
 | 
			
		|||
            ^{inputField nameView}
 | 
			
		||||
            ^{inputField homeView}
 | 
			
		||||
            ^{inputField nicknameView}
 | 
			
		||||
            ^{inputField phoneNumberView}
 | 
			
		||||
            ^{inputField emailView}
 | 
			
		||||
            $maybe pendingEmail <- maybePendingEmail
 | 
			
		||||
                <p>Päivitys osoitteeseen #
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,7 @@ dbUserToUser user = let id = entityToID user
 | 
			
		|||
                        { id = pure id
 | 
			
		||||
                        , email = pure dBUserEmail
 | 
			
		||||
                        , pendingEmail = pure dBUserPendingEmail
 | 
			
		||||
                        , phoneNumber = pure phoneNumber
 | 
			
		||||
                        , name = pure name
 | 
			
		||||
                        , nickname = pure $ fromMaybe (error "db contains empty name") $
 | 
			
		||||
                            maybe (viaNonEmpty head $ words $ name) Just nickname
 | 
			
		||||
| 
						 | 
				
			
			@ -150,7 +151,8 @@ updateUser user (UpdateData {..}) = do
 | 
			
		|||
    user <- updateUserData user
 | 
			
		||||
        (catMaybes [(DBUserPendingEmail =. ) . Just <$> email, verificationSecretUpdate,
 | 
			
		||||
            (DBUserPasswordCrypt =.) <$> hash])
 | 
			
		||||
        (catMaybes [SetUserName <$> name, SetUserNickname . Just <$> nickname, SetUserHomeplace <$> homeplace])
 | 
			
		||||
        (catMaybes [SetUserName <$> name, SetUserNickname . Just <$> nickname,
 | 
			
		||||
            SetUserHomeplace <$> homeplace, SetUserPhoneNumber <$> phoneNumber])
 | 
			
		||||
    when (isJust email) $ sendVerificationSecret user
 | 
			
		||||
    return user
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -249,6 +251,7 @@ data User m = User
 | 
			
		|||
    , pendingEmail :: m (Maybe Email)
 | 
			
		||||
    , name :: m Text
 | 
			
		||||
    , nickname :: m Text
 | 
			
		||||
    , phoneNumber :: m PhoneNumber
 | 
			
		||||
    , birthdate :: m Date
 | 
			
		||||
    , homeplace :: m Text
 | 
			
		||||
    , registered :: m Time
 | 
			
		||||
| 
						 | 
				
			
			@ -281,6 +284,7 @@ data Token m = Token
 | 
			
		|||
 | 
			
		||||
data ApplicationData = ApplicationData
 | 
			
		||||
    { email :: Email
 | 
			
		||||
    , phoneNumber :: PhoneNumber
 | 
			
		||||
    , password :: Text
 | 
			
		||||
    , name :: Text
 | 
			
		||||
    , nickname :: Maybe Text
 | 
			
		||||
| 
						 | 
				
			
			@ -291,6 +295,7 @@ data ApplicationData = ApplicationData
 | 
			
		|||
 | 
			
		||||
data UpdateData = UpdateData
 | 
			
		||||
    { email :: Maybe Email
 | 
			
		||||
    , phoneNumber :: Maybe PhoneNumber
 | 
			
		||||
    , password :: Maybe Text
 | 
			
		||||
    , name :: Maybe Text
 | 
			
		||||
    , nickname :: Maybe Text
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,9 +107,11 @@ updateUserData user updates memberDataUpdates = runQuery $ do
 | 
			
		|||
        updateData (SetUserName x) memberData = memberData { name = x }
 | 
			
		||||
        updateData (SetUserNickname x) memberData = memberData { nickname = x }
 | 
			
		||||
        updateData (SetUserHomeplace x) memberData = memberData { homeplace = x }
 | 
			
		||||
        updateData (SetUserPhoneNumber x) memberData = memberData { phoneNumber = x }
 | 
			
		||||
    Persist.update key (userUpdates <> updates)
 | 
			
		||||
    pure user
 | 
			
		||||
 | 
			
		||||
data UserUpdate = SetUserName Text
 | 
			
		||||
                | SetUserNickname (Maybe Text)
 | 
			
		||||
                | SetUserHomeplace Text
 | 
			
		||||
                | SetUserPhoneNumber PhoneNumber
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,8 @@ import Data.Morpheus.Types (GQLType, DecodeScalar(..), KIND, EncodeScalar(..),
 | 
			
		|||
import Data.Morpheus.Types.GQLScalar (scalarToJSON, scalarFromJSON)
 | 
			
		||||
import Data.Time (UTCTime, getCurrentTime, NominalDiffTime, addUTCTime, Day)
 | 
			
		||||
import Data.Time.Format.ISO8601 (iso8601Show, iso8601ParseM)
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
import Data.Char (isSpace)
 | 
			
		||||
import Database.Persist.Class (PersistField(..))
 | 
			
		||||
import Database.Persist.PersistValue (PersistValue(..))
 | 
			
		||||
import Database.Persist.Sql (PersistFieldSql(..), SqlBackend)
 | 
			
		||||
| 
						 | 
				
			
			@ -86,11 +88,42 @@ data MemberData = MemberData
 | 
			
		|||
    , birthdate :: Date
 | 
			
		||||
    , homeplace :: Text
 | 
			
		||||
    , application :: Text
 | 
			
		||||
    , phoneNumber :: PhoneNumber
 | 
			
		||||
    } deriving (Show, Eq, Generic)
 | 
			
		||||
 | 
			
		||||
instance FromJSON MemberData
 | 
			
		||||
instance ToJSON MemberData
 | 
			
		||||
 | 
			
		||||
newtype PhoneNumber = PhoneNumber Text deriving (Show, Generic)
 | 
			
		||||
 | 
			
		||||
renderPhoneNumber :: PhoneNumber -> Text
 | 
			
		||||
renderPhoneNumber (PhoneNumber txt) = txt
 | 
			
		||||
 | 
			
		||||
toPhoneNumber :: Text -> Maybe PhoneNumber
 | 
			
		||||
toPhoneNumber txt = do
 | 
			
		||||
    guard $ not $ T.null txt
 | 
			
		||||
    guard $ T.all (`elem` ("+ 0123456789" :: [Char])) txt
 | 
			
		||||
    pure $ PhoneNumber txt
 | 
			
		||||
 | 
			
		||||
instance Eq PhoneNumber where
 | 
			
		||||
    a == b = normalize a == normalize b
 | 
			
		||||
        where normalize (PhoneNumber txt) = case T.uncons txt of
 | 
			
		||||
                                            Just ('0', rest) -> "+358" <> T.filter (not . isSpace) rest
 | 
			
		||||
                                            Just x -> T.filter (not . isSpace) (uncurry T.cons x)
 | 
			
		||||
                                            Nothing -> error "PhoneNumber must not be empty"
 | 
			
		||||
 | 
			
		||||
instance DecodeScalar PhoneNumber where
 | 
			
		||||
    decodeScalar (String s) =
 | 
			
		||||
        maybe (Left $ "Couldn't parse \"" <> s <> "\" as a PhoneNumber") Right $ toPhoneNumber s
 | 
			
		||||
    decodeScalar _ = Left "Invalid type for PhoneNumber, should be string"
 | 
			
		||||
 | 
			
		||||
instance EncodeScalar PhoneNumber where
 | 
			
		||||
    encodeScalar = String . renderPhoneNumber
 | 
			
		||||
 | 
			
		||||
instance GQLType PhoneNumber where type KIND PhoneNumber = SCALAR
 | 
			
		||||
instance ToJSON PhoneNumber where toJSON = scalarToJSON
 | 
			
		||||
instance FromJSON PhoneNumber where parseJSON = scalarFromJSON <=< parseJSON
 | 
			
		||||
 | 
			
		||||
newtype UserID = UserID Int64 deriving (Eq, Show, Generic, Ord, Read)
 | 
			
		||||
 | 
			
		||||
instance DecodeScalar UserID where
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@ scalar Email
 | 
			
		|||
 | 
			
		||||
scalar KeyID
 | 
			
		||||
 | 
			
		||||
scalar PhoneNumber
 | 
			
		||||
 | 
			
		||||
scalar Time
 | 
			
		||||
 | 
			
		||||
scalar TokenID
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +45,7 @@ type User {
 | 
			
		|||
  pendingEmail: Email
 | 
			
		||||
  name: String!
 | 
			
		||||
  nickname: String!
 | 
			
		||||
  phoneNumber: PhoneNumber!
 | 
			
		||||
  birthdate: Date!
 | 
			
		||||
  homeplace: String!
 | 
			
		||||
  registered: Time!
 | 
			
		||||
| 
						 | 
				
			
			@ -64,10 +67,10 @@ type Query {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type Mutation {
 | 
			
		||||
  apply(email: Email!, password: String!, name: String!, nickname: String, birthdate: Date!, homeplace: String!, application: String!): User!
 | 
			
		||||
  apply(email: Email!, phoneNumber: PhoneNumber!, password: String!, name: String!, nickname: String, birthdate: Date!, homeplace: String!, application: String!): User!
 | 
			
		||||
  verifyEmail(secret: String!): Unit!
 | 
			
		||||
  resendVerificationEmail(user: UserID): Unit!
 | 
			
		||||
  update(email: Email, password: String, name: String, nickname: String, homeplace: String, user: UserID): User!
 | 
			
		||||
  update(email: Email, phoneNumber: PhoneNumber, password: String, name: String, nickname: String, homeplace: String, user: UserID): User!
 | 
			
		||||
  newToken(comment: String, name: String, permissions: String): Token!
 | 
			
		||||
  newKey(comment: String, keyData: Base64!, expires: Time): PGPKey!
 | 
			
		||||
  accept(user: UserID!): Unit!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue