Before I comment on this patch, I think we should make sure that Snowdrift.coop at large is ok with this change, and that that "ok" is made real by adding the feature to OpenProject.
I want to get in the habit of doing feature-work that is in OpenProject, and only that is in OpenProject. From the designer/owner side, that ensures they get a chance to approve and prioritize work, and from the developer side, it means we have a clear understanding of new development we're responsible for. I think both aspects are equally important. Still, this is probably a good thing, so I've pushed this commit to a new branch to ease collaboration. https://git.gnu.io/snowdrift/snowdrift/tree/user-nicknames On Mon, Mar 07, 2016 at 02:13:07PM +0200, fr33domlover wrote: > From: fr33domlover <[email protected]> > > Making UserR take a Text that refers to a whole new User field is a big > change. Many handlers and templates need to be updated. Therefore please > read below carefully to understand my plan and what exactly this commit > does. > > The GOAL is to move all UserId-based routes to use UserHandle instead, > which is Text. That "handle" references a new 'userNick` field in the > User table. But since this is a huge change, this commit does a smaller > change. The rest will come in the next patches. What this commit does > is: > > 1. Update the UserR route and add a deprecated UserByIdR route > 2. Add the 'userNick' field to account creation and account update forms > 3. Require a pattern for the 'userNick' field to avoid all-digit > nicknames and other potential issues > 4. Fix all handlers and templates to pass the 'userNick' to 'UserR' > instead of the 'userId'. That includes inserting 'get404' calls in > several places just to get the User value, and other helpers that > will be removed later. They are used just so that the code builds and > works after applying this patch. > > The next patch(es) will update all the other handlers and probably > change some of the helper functions to work with the new 'userNick' > field. > > Before this gets committed, some input/review from dev and desig teams > will be great. But don't be pedantic, as the next commits will change > many things and will remove some of the ugly parts used here > temporarily. > > I suggest we make this change in 2 steps because it's so big: > > 1. Change the routes and UI as needed, and do whatever is needed to make the > code build again > 2. When done, refactor and add helpers and put all the chaos back to order > --- > config/models | 7 + > config/routes | 3 +- > src/Foundation.hs | 24 +++- > src/Handler/NewDesign.hs | 19 ++- > src/Handler/Project.hs | 20 +-- > src/Handler/User/Comment.hs | 1 + > src/Handler/User/Delete.hs | 3 +- > src/Handler/User/Edit.hs | 7 +- > src/Handler/User/NewDiscussion.hs | 1 + > src/Handler/User/Notifications.hs | 3 +- > src/Handler/User/ProjectNotifications.hs | 3 +- > src/Handler/User/SelectProject.hs | 3 +- > src/Handler/User/User.hs | 3 +- > src/Handler/User/Utils.hs | 2 +- > src/Handler/Who.hs | 10 +- > src/Model/Transaction.hs | 2 +- > src/Model/User.hs | 7 +- > src/Model/User/Internal.hs | 18 +-- > src/View/SnowdriftEvent.hs | 22 +-- > src/View/User.hs | 37 ++++- > templates/application.hamlet | 2 +- > templates/auth/create-user-form.hamlet | 12 +- > templates/comment.hamlet | 7 +- > templates/default/navbar.hamlet | 35 ++--- > templates/invite.hamlet | 8 +- > templates/navbar.hamlet | 8 +- > templates/project_patrons.hamlet | 2 +- > templates/project_transactions.hamlet | 2 +- > templates/sponsors.hamlet | 226 > +++++++++++++++---------------- > templates/tag.hamlet | 2 +- > templates/user.hamlet | 2 +- > templates/user_discuss.hamlet | 2 +- > templates/user_discussion_wrapper.hamlet | 2 +- > templates/user_tickets.hamlet | 2 +- > templates/users.hamlet | 2 +- > templates/who.hamlet | 4 +- > templates/wiki_history.hamlet | 2 +- > tests/TestImport.hs | 2 +- > 38 files changed, 295 insertions(+), 222 deletions(-) > > diff --git a/config/models b/config/models > index a592753..bf64b14 100644 > --- a/config/models > +++ b/config/models > @@ -16,6 +16,12 @@ Transaction > > User > ident Text > + -- ^ Private login name, never shown in UI, may be an email address in > the > + -- case of Persona logins. > + nick Text > default=concat('user-',floor(random()*100000)) > + -- ^ Public user unique nickname, shown in the UI. > + -- TODO the default value is meant just for initial migration, remove > it > + -- once all users get assigned auto-generated nicks > email Text Maybe > email_verified Bool default=false > createdTs UTCTime > @@ -35,6 +41,7 @@ User > discussion DiscussionId > default=nextval('discussion_id_seq'::regclass) > > UniqueUser ident > + UniqueUserNick nick > UniqueUserAccount account > UniqueUserDiscussion discussion > > diff --git a/config/routes b/config/routes > index 999f36b..7118d51 100644 > --- a/config/routes > +++ b/config/routes > @@ -74,7 +74,8 @@ > -- ## Browsing a particular user (may need fleshing out?) > -- > > -/u/#UserHandle UserR GET > +/u/!#UserId UserByIdR GET > +/u/#UserHandle UserR GET > > -- Yesod jibber jabber > /static StaticR Static appStatic > diff --git a/src/Foundation.hs b/src/Foundation.hs > index 78797fc..5648436 100644 > --- a/src/Foundation.hs > +++ b/src/Foundation.hs > @@ -12,6 +12,7 @@ import Control.Monad.Writer.Strict (WriterT, runWriterT) > import Data.Char (isSpace) > import Data.Text as T > import Network.HTTP.Conduit (Manager) > +import System.Random (randomRIO) > import Text.Blaze.Html.Renderer.Text (renderHtml) > import Text.Hamlet (hamletFile) > import Text.Jasmine (minifym) > @@ -62,9 +63,11 @@ plural _ _ y = y > -- Set up i18n messages. See the message folder. > mkMessage "App" "messages" "en" > > --- FIXME > +-- Type of project identifier path piece in URLs > type ProjectHandle = Text > -type UserHandle = UserId > + > +-- Type of user identifier path piece in URLs > +type UserHandle = Text > > -- This is where we define all of the routes in our application. For a full > -- explanation of the syntax, please see: > @@ -243,8 +246,13 @@ instance YesodAuth App where > ("hashdb", _) -> return $ UserError $ IdentifierNotFound ident > ("browserid", _) -> do > let emailStuff = Just $ NewEmail True ident > + -- Generate nickname automatically. This is ugly, but we're > + -- deprecating Persona anyway. > + num <- liftIO $ randomRIO (0, 99999 :: Int) > + let nick = "user-" <> pack (show num) > muid <- > - createUser ident Nothing Nothing emailStuff Nothing > Nothing > + createUser > + ident Nothing nick Nothing emailStuff Nothing Nothing > return $ case muid of > -- The Nothing case never really runs because > 'createUser' > -- throws an exception on failure > @@ -268,9 +276,10 @@ data NewEmail = NewEmail > , neAddr :: Text > } > > -createUser :: Text -> Maybe Text -> Maybe Text -> Maybe NewEmail -> Maybe > Text > - -> Maybe Text -> Handler (Maybe UserId) > -createUser ident passph name newEmail avatar nick = do > +createUser > + :: Text -> Maybe Text -> Text -> Maybe Text -> Maybe NewEmail -> Maybe > Text > + -> Maybe Text -> Handler (Maybe UserId) > +createUser ident passph nickname name newEmail avatar ircNick = do > langs <- mapMaybe (readMaybe . T.unpack) <$> languages > now <- liftIO getCurrentTime > handle (\DBException -> return Nothing) $ runYDB $ do > @@ -309,6 +318,7 @@ createUser ident passph name newEmail avatar nick = do > where > newUser langs now account_id discussion_id = > User { userIdent = ident > + , userNick = nickname > , userEmail = (neAddr <$> newEmail) > , userEmail_verified = (maybe False neVerified newEmail) > , userCreatedTs = now > @@ -319,7 +329,7 @@ createUser ident passph name newEmail avatar nick = do > , userAvatar = avatar > , userBlurb = Nothing > , userStatement = Nothing > - , userIrcNick = nick > + , userIrcNick = ircNick > , userLanguages = langs > , userReadNotifications = now > , userReadApplications = now > diff --git a/src/Handler/NewDesign.hs b/src/Handler/NewDesign.hs > index 05829ab..99b8622 100644 > --- a/src/Handler/NewDesign.hs > +++ b/src/Handler/NewDesign.hs > @@ -181,14 +181,15 @@ postCreateAccountR = do > ((result, form), _) <- runFormPost $ createUserForm Nothing > > case result of > - FormSuccess (ident, passph, name, memail, avatar, nick) -> do > + FormSuccess (ident, passph, nickname, name, memail, avatar, ircnick) > -> do > muser_id <- > createUser ident > (Just passph) > + nickname > name > (NewEmail False <$> memail) > avatar > - nick > + ircnick > fromMaybe (pure()) > (startEmailVerification <$> muser_id <*> memail) > case muser_id of > @@ -210,11 +211,11 @@ postCreateAccountR = do > |] > > -- | Public profile for a user. > -getUserR :: UserId -> Handler Html > -getUserR user_id = do > +getUserR :: UserHandle -> Handler Html > +getUserR nickname = do > mviewer_id <- maybeAuthId > > - user <- runYDB $ get404 user_id > + Entity user_id user <- runYDB $ getBy404 $ UniqueUserNick nickname > > projects_and_roles <- runDB (fetchUserProjectsAndRolesDB user_id) > when ( Just user_id == mviewer_id > @@ -230,6 +231,14 @@ getUserR user_id = do > alphaRewriteNotice > renderUser mviewer_id user_id user projects_and_roles > > +-- | Pick user by numeric ID, and simply redirect to 'UserR'. > +-- > +-- TODO remove this at some point? > +getUserByIdR :: UserId -> Handler Html > +getUserByIdR user_id = do > + user <- runYDB $ get404 user_id > + redirect $ UserR $ userNick user > + > getUNotificationsR :: Handler Html > getUNotificationsR = do > showArchived <- lookupGetParam "state" > diff --git a/src/Handler/Project.hs b/src/Handler/Project.hs > index 5ab1834..4c7eff9 100644 > --- a/src/Handler/Project.hs > +++ b/src/Handler/Project.hs > @@ -433,21 +433,23 @@ getInviteR project_handle = do > > outstanding_invites <- runDB $ > select $ > - from $ \invite -> do > + from $ \ (invite `InnerJoin` user) -> do > + on_ $ invite ^. InviteUser ==. user ^. UserId > where_ ( invite ^. InviteRedeemed ==. val False ) > orderBy [ desc (invite ^. InviteCreatedTs) ] > - return invite > + return (invite, user ^. UserNick) > > redeemed_invites <- runDB $ > select $ > - from $ \invite -> do > - where_ ( invite ^. InviteRedeemed ==. val True ) > + from $ \ (invite `InnerJoin` user) -> do > + on_ $ invite ^. InviteUser ==. user ^. UserId > + where_ $ invite ^. InviteRedeemed ==. val True > orderBy [ desc (invite ^. InviteCreatedTs) ] > - return invite > + return (invite, user ^. UserNick) > > - let redeemed_users = S.fromList $ mapMaybe (inviteRedeemedBy . > entityVal) redeemed_invites > - redeemed_inviters = S.fromList $ map (inviteUser . entityVal) > redeemed_invites > - outstanding_inviters = S.fromList $ map (inviteUser . entityVal) > outstanding_invites > + let redeemed_users = S.fromList $ mapMaybe (inviteRedeemedBy . entityVal > . fst) redeemed_invites > + redeemed_inviters = S.fromList $ map (inviteUser . entityVal . fst) > redeemed_invites > + outstanding_inviters = S.fromList $ map (inviteUser . entityVal . > fst) outstanding_invites > user_ids = S.toList $ redeemed_users `S.union` redeemed_inviters > `S.union` outstanding_inviters > > user_entities <- runDB $ selectList [ UserId <-. user_ids ] [] > @@ -458,7 +460,7 @@ getInviteR project_handle = do > format_user (Just user_id) = > let Entity _ user = fromMaybe (error "getInviteR: user_id not > found in users map") > (M.lookup user_id users) > - in fromMaybe (userIdent user) $ userName user > + in fromMaybe (userNick user) $ userName user > > format_inviter user_id = > userDisplayName $ fromMaybe (error "getInviteR(#2): user_id not > found in users map") > diff --git a/src/Handler/User/Comment.hs b/src/Handler/User/Comment.hs > index dd14dd9..5310bed 100644 > --- a/src/Handler/User/Comment.hs > +++ b/src/Handler/User/Comment.hs > @@ -132,6 +132,7 @@ makeUserCommentActionWidget make_comment_action_widget > user_id comment_id mods m > > userDiscussionPage :: UserId -> Widget -> Widget > userDiscussionPage user_id widget = do > + user <- handlerToWidget $ runDB $ get404 user_id > $(widgetFile "user_discussion_wrapper") > toWidget $(cassiusFile "templates/comment.cassius") > > diff --git a/src/Handler/User/Delete.hs b/src/Handler/User/Delete.hs > index c52b49b..1ab6aa7 100644 > --- a/src/Handler/User/Delete.hs > +++ b/src/Handler/User/Delete.hs > @@ -32,6 +32,7 @@ getDeleteUserR user_id = do > > postDeleteUserR :: UserId -> Handler Html > postDeleteUserR user_id = do > + user <- runDB $ get404 user_id > void $ checkEditUser user_id > startDeleteConfirmation user_id > - redirect $ UserR user_id > + redirect $ UserR $ userNick user > diff --git a/src/Handler/User/Edit.hs b/src/Handler/User/Edit.hs > index 6248d26..d126c95 100644 > --- a/src/Handler/User/Edit.hs > +++ b/src/Handler/User/Edit.hs > @@ -41,8 +41,8 @@ postEditUserR user_id = do > mcurrent_email <- runDB $ fetchUserEmail user_id > when (mcurrent_email /= Just user_email) $ > startEmailVerification user_id user_email > - runDB (updateUserDB user_id user_update) > - redirect (UserR user_id) > + runDB $ updateUserDB user_id user_update > + redirect $ UserR $ userUpdateNick user_update > > _ -> do > user <- runYDB $ get404 user_id > @@ -56,5 +56,6 @@ postEditUserR user_id = do > previewWidget form "update" $ > renderUser (Just viewer_id) user_id updated_user > mempty > _ -> do > + user <- runYDB $ get404 user_id > alertDanger "Failed to update user." > - redirect (UserR user_id) > + redirect $ UserR $ userNick user > diff --git a/src/Handler/User/NewDiscussion.hs > b/src/Handler/User/NewDiscussion.hs > index a6965c3..5f4278d 100644 > --- a/src/Handler/User/NewDiscussion.hs > +++ b/src/Handler/User/NewDiscussion.hs > @@ -14,6 +14,7 @@ getNewUserDiscussionR :: UserId -> Handler Html > getNewUserDiscussionR user_id = do > void requireAuth > let widget = commentNewTopicFormWidget > + user <- runDB $ get404 user_id > defaultLayout $(widgetFile "user_discussion_wrapper") > > postNewUserDiscussionR :: UserId -> Handler Html > diff --git a/src/Handler/User/Notifications.hs > b/src/Handler/User/Notifications.hs > index bd67096..fe1dc1c 100644 > --- a/src/Handler/User/Notifications.hs > +++ b/src/Handler/User/Notifications.hs > @@ -42,7 +42,8 @@ postUserNotificationsR user_id = do > forM_ (userNotificationPref notif_pref) $ \(ntype, ndeliv) -> > runDB $ updateUserNotificationPrefDB user_id ntype ndeliv > alertSuccess "Successfully updated the notification preferences." > - redirect $ UserR user_id > + user <- runDB $ get404 user_id > + redirect $ UserR $ userNick user > _ -> do > alertDanger $ "Failed to update the notification preferences. " > <> "Please try again." > diff --git a/src/Handler/User/ProjectNotifications.hs > b/src/Handler/User/ProjectNotifications.hs > index 7de8233..d5c90bf 100644 > --- a/src/Handler/User/ProjectNotifications.hs > +++ b/src/Handler/User/ProjectNotifications.hs > @@ -47,7 +47,8 @@ postProjectNotificationsR user_id project_id = do > runDB $ updateProjectNotificationPrefDB > user_id project_id ntype ndeliv > alertSuccess "Successfully updated the notification preferences." > - redirect (UserR user_id) > + user <- runDB $ get404 user_id > + redirect $ UserR $ userNick user > _ -> do > project <- runYDB $ get404 project_id > alertDanger "Failed to update the notification preferences." > diff --git a/src/Handler/User/SelectProject.hs > b/src/Handler/User/SelectProject.hs > index 377c8e4..fdac755 100644 > --- a/src/Handler/User/SelectProject.hs > +++ b/src/Handler/User/SelectProject.hs > @@ -26,8 +26,9 @@ getUserSelectProjectR user_id = do > > postUserSelectProjectR :: UserId -> Handler Html > postUserSelectProjectR user_id = do > + user <- runDB $ get404 user_id > void $ checkEditUser user_id > mproject_id <- lookupPostParam "project_id" > - maybe (redirect $ UserR user_id) > + maybe (redirect $ UserR $ userNick user) > (redirect . ProjectNotificationsR user_id . key . PersistInt64) > (join $ Traversable.forM mproject_id $ readMaybe . T.unpack) > diff --git a/src/Handler/User/User.hs b/src/Handler/User/User.hs > index 57020cf..2076542 100644 > --- a/src/Handler/User/User.hs > +++ b/src/Handler/User/User.hs > @@ -7,10 +7,11 @@ import Model.User > > postUserR :: UserId -> Handler Html > postUserR user_id = do > + user <- runDB $ get404 user_id > void $ checkEditUser user_id > memail <- runDB $ fetchUserEmail user_id > case memail of > Nothing -> > alertDanger "No email address is associated with your account." > Just email -> startEmailVerification user_id email > - redirect $ UserR user_id > + redirect $ UserR $ userNick user > diff --git a/src/Handler/User/Utils.hs b/src/Handler/User/Utils.hs > index baeceba..4ec1948 100644 > --- a/src/Handler/User/Utils.hs > +++ b/src/Handler/User/Utils.hs > @@ -31,7 +31,7 @@ resetPassphrase user_id user passphrase passphrase' route = > updateUserPassphraseDB user_id (userHash user') (userSalt > user') > deleteFromResetPassphrase user_id > alertSuccess "You successfully updated your passphrase." > - redirect $ UserR user_id > + redirect $ UserR $ userNick user' > else do > alertDanger "The passphrases you entered do not match." > redirect route > diff --git a/src/Handler/Who.hs b/src/Handler/Who.hs > index 2eaa54b..906e3ed 100644 > --- a/src/Handler/Who.hs > +++ b/src/Handler/Who.hs > @@ -8,17 +8,19 @@ import Handler.Utils > import Model.Markdown > > userShortName :: User -> Text > -userShortName user = fromMaybe (userIdent user) $ userName user > +userShortName user = fromMaybe (userNick user) $ userName user > > getWhoR :: Text -> Handler Html > getWhoR project_handle = do > - Entity project_id project <- runYDB $ getBy404 $ UniqueProjectHandle > project_handle > + Entity project_id project <- > + runYDB $ getBy404 $ UniqueProjectHandle project_handle > team_members <- runDB $ > select $ > from $ \(user `InnerJoin` project_user_role) -> do > on_ $ user ^. UserId ==. project_user_role ^. ProjectUserRoleUser > - where_ $ (project_user_role ^. ProjectUserRoleProject ==. val > project_id) > - &&. (project_user_role ^. ProjectUserRoleRole ==. val > TeamMember) > + where_ $ > + project_user_role ^. ProjectUserRoleProject ==. val project_id > &&. > + project_user_role ^. ProjectUserRoleRole ==. val TeamMember > return user > > let members = sortBy (compare `on` (userCreatedTs . entityVal)) > team_members > diff --git a/src/Model/Transaction.hs b/src/Model/Transaction.hs > index c9fe404..d4d0b10 100644 > --- a/src/Model/Transaction.hs > +++ b/src/Model/Transaction.hs > @@ -36,7 +36,7 @@ renderOtherAccount is_credit transaction user_accounts > project_accounts = do > > (Nothing, Just (Entity user_id user)) -> > [hamlet| > - <a href=@{UserR user_id}> > + <a href=@{UserR $ userNick user}> > #{userDisplayName (Entity user_id user)} > |] > > diff --git a/src/Model/User.hs b/src/Model/User.hs > index 629b1bb..e5ddc8c 100644 > --- a/src/Model/User.hs > +++ b/src/Model/User.hs > @@ -174,8 +174,8 @@ curUserIsEligibleEstablish = > -- | Get a User's public display name (defaults to userN if no name has > -- been set). > userDisplayName :: Entity User -> Text > -userDisplayName (Entity user_id user) = > - fromMaybe ("user" <> toPathPiece user_id) (userName user) > +userDisplayName (Entity _user_id user) = > + fromMaybe (userNick user) (userName user) > > -- | Apply a UserUpdate in memory, for preview. For this reason, > -- userUpdateNotificationPreferences doesn't need to be touched. > @@ -229,7 +229,8 @@ fetchUserWatchingProjectsDB user_id = > > updateUserDB :: UserId -> UserUpdate -> DB () > updateUserDB user_id UserUpdate{..} = update $ \u -> do > - set u [ UserName =. val userUpdateName > + set u [ UserNick =. val userUpdateNick > + , UserName =. val userUpdateName > , UserAvatar =. val userUpdateAvatar > , UserEmail =. val userUpdateEmail > , UserIrcNick =. val userUpdateIrcNick > diff --git a/src/Model/User/Internal.hs b/src/Model/User/Internal.hs > index 2bf2299..a4e4630 100644 > --- a/src/Model/User/Internal.hs > +++ b/src/Model/User/Internal.hs > @@ -8,15 +8,15 @@ import qualified Database.Persist as P > import Model.Notification > import WrappedValues > > -data UserUpdate = > - UserUpdate > - { userUpdateName :: Maybe Text > - , userUpdateAvatar :: Maybe Text > - , userUpdateEmail :: Maybe Text > - , userUpdateIrcNick :: Maybe Text > - , userUpdateBlurb :: Maybe Markdown > - , userUpdateStatement :: Maybe Markdown > - } > +data UserUpdate = UserUpdate > + { userUpdateNick :: Text > + , userUpdateName :: Maybe Text > + , userUpdateAvatar :: Maybe Text > + , userUpdateEmail :: Maybe Text > + , userUpdateIrcNick :: Maybe Text > + , userUpdateBlurb :: Maybe Markdown > + , userUpdateStatement :: Maybe Markdown > + } > > data ChangePassphrase = ChangePassphrase > { currentPassphrase :: Text > diff --git a/src/View/SnowdriftEvent.hs b/src/View/SnowdriftEvent.hs > index a9579ae..d4e92f4 100644 > --- a/src/View/SnowdriftEvent.hs > +++ b/src/View/SnowdriftEvent.hs > @@ -123,7 +123,7 @@ renderCommentPendingEvent comment_id comment user_map = do > [whamlet| > <div .event> > ^{renderTime $ commentCreatedTs comment} > - <a href=@{UserR (commentUser comment)}> #{userDisplayName > (Entity (commentUser comment) poster)} > + <a href=@{UserR $ userNick poster}> #{userDisplayName (Entity > (commentUser comment) poster)} > posted a > <a href=@{CommentDirectLinkR comment_id}> comment > awaiting moderator approval: #{commentText comment} > @@ -142,7 +142,7 @@ renderCommentRethreadedEvent Rethread{..} user_map = do > [whamlet| > <div .event> > ^{renderTime rethreadTs} > - <a href=@{UserR rethreadModerator}> #{userDisplayName (Entity > rethreadModerator user)} > + <a href=@{UserR $ userNick user}> #{userDisplayName (Entity > rethreadModerator user)} > rethreaded a comment from > <del>@{old_route} > to > @@ -163,7 +163,7 @@ renderCommentClosedEvent CommentClosing{..} user_map > ticket_map = do > [whamlet| > <div .event> > ^{renderTime commentClosingTs} > - <a href=@{UserR commentClosingClosedBy}> > #{userDisplayName (Entity commentClosingClosedBy user)} > + <a href=@{UserR $ userNick user}> #{userDisplayName > (Entity commentClosingClosedBy user)} > closed ticket > <a href=@{CommentDirectLinkR commentClosingComment}> > <div .ticket-title>SD-#{ticket_str}: #{ticketName} > @@ -173,7 +173,7 @@ renderCommentClosedEvent CommentClosing{..} user_map > ticket_map = do > [whamlet| > <div .event> > ^{renderTime commentClosingTs} > - <a href=@{UserR commentClosingClosedBy}> > #{userDisplayName (Entity commentClosingClosedBy user)} > + <a href=@{UserR $ userNick user}> #{userDisplayName > (Entity commentClosingClosedBy user)} > closed > <a href=@{CommentDirectLinkR commentClosingComment}> > comment thread > @@ -191,7 +191,7 @@ renderTicketClaimedEvent (Left (_, TicketClaiming{..})) > user_map ticket_map = do > [whamlet| > <div .event> > ^{renderTime ticketClaimingTs} > - <a href=@{UserR ticketClaimingUser}> #{userDisplayName (Entity > ticketClaimingUser user)} > + <a href=@{UserR $ userNick user}> #{userDisplayName (Entity > ticketClaimingUser user)} > claimed ticket > <a href=@{CommentDirectLinkR ticketClaimingTicket}> > <div .ticket-title>SD-#{ticket_str}: #{ticketName} > @@ -208,7 +208,7 @@ renderTicketClaimedEvent (Right (_, > TicketOldClaiming{..})) user_map ticket_map > [whamlet| > <div .event> > ^{renderTime ticketOldClaimingClaimTs} > - <a href=@{UserR ticketOldClaimingUser}> #{userDisplayName > (Entity ticketOldClaimingUser user)} > + <a href=@{UserR $ userNick user}> #{userDisplayName (Entity > ticketOldClaimingUser user)} > claimed ticket > <a href=@{CommentDirectLinkR ticketOldClaimingTicket}> > <div .ticket-title>SD-#{ticket_str}: #{ticketName} > @@ -251,7 +251,7 @@ renderWikiPageEvent project_handle wiki_page_id wiki_page > _ = do > <div .event> > ^{renderTime $ wikiPageCreatedTs wiki_page} > <!-- > - <a href=@{UserR (wikiPageUser wiki_page)}> > + <a href=@{UserR $ userNick editor}> > #{userDisplayName (Entity (wikiPageUser wiki_page) > editor)} > --> > made a new wiki page: # > @@ -265,7 +265,7 @@ renderWikiEditEvent project_handle edit_id wiki_edit > wiki_target_map user_map = > [whamlet| > <div .event> > ^{renderTime $ wikiEditTs wiki_edit} > - <a href=@{UserR (wikiEditUser wiki_edit)}> > + <a href=@{UserR $ userNick editor}> > #{userDisplayName (Entity (wikiEditUser wiki_edit) editor)} > edited the > <a href=@{WikiR project_handle (wikiTargetLanguage wiki_target) > (wikiTargetTarget wiki_target)}> #{wikiTargetTarget wiki_target} > @@ -300,7 +300,7 @@ renderNewPledgeEvent _ SharesPledged{..} user_map = do > [whamlet| > <div .event> > ^{renderTime sharesPledgedTs} > - <a href=@{UserR sharesPledgedUser}> #{userDisplayName (Entity > sharesPledgedUser pledger)} > + <a href=@{UserR $ userNick pledger}> #{userDisplayName (Entity > sharesPledgedUser pledger)} > made a new pledge of #{show mills} per patron! > |] > > @@ -315,7 +315,7 @@ renderUpdatedPledgeEvent old_shares _ SharesPledged{..} > user_map = do > [whamlet| > <div .event> > ^{renderTime sharesPledgedTs} > - <a href=@{UserR sharesPledgedUser}> #{userDisplayName (Entity > sharesPledgedUser pledger)} > + <a href=@{UserR $ userNick pledger}> #{userDisplayName (Entity > sharesPledgedUser pledger)} > #{verb} their pledge from #{show old_mills} to #{show new_mills} > per patron#{punc} > |] > > @@ -326,6 +326,6 @@ renderDeletedPledgeEvent ts user_id shares user_map = do > [whamlet| > <div .event> > ^{renderTime ts} > - <a href=@{UserR user_id}>#{userDisplayName (Entity user_id > pledger)} > + <a href=@{UserR $ userNick pledger}>#{userDisplayName (Entity > user_id pledger)} > withdrew their #{show mills} per patron pledge. > |] > diff --git a/src/View/User.hs b/src/View/User.hs > index 67de9f8..7498f8a 100644 > --- a/src/View/User.hs > +++ b/src/View/User.hs > @@ -14,9 +14,11 @@ module View.User > > import Import hiding (UserNotificationPref, ProjectNotificationPref) > > +import Data.Char (isDigit) > import Data.String (fromString) > import qualified Data.Map as M > import qualified Data.Set as S > +import qualified Data.Text as T > > import Avatar > import DeprecatedBootstrap > @@ -28,23 +30,44 @@ import Model.User.Internal > import Widgets.Markdown (snowdriftMarkdownField) > import Widgets.UserPledges > > +nickField :: Field Handler Text > +nickField = > + -- A safe and initial reasonable nickname pattern. It's much easier to > make > + -- it restrictive now and relax later, than to make it too relaxed and > have > + -- conflicts and ugly URLs and other issues later. > + let isAsciiLetter c = 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' > + first = isAsciiLetter > + rest c = isAsciiLetter c || isDigit c || c `elem` ("-._" :: String) > + validnick t = > + case T.uncons t of > + Just (c, r) -> first c && T.all rest r > + Nothing -> False > + msg :: Text > + msg = "The first character must be a letter, and every other > character\ > + \ must be a letter, a digit, ‘.’ (period) , ‘-’ (dash) or ‘_’\ > + \ (underscore)." > + in checkBool validnick msg textField > + > createUserForm :: Maybe Text > -> Form (Text > ,Text > + ,Text > ,Maybe Text > ,Maybe Text > ,Maybe Text > ,Maybe Text > ) > createUserForm ident extra = do > + > (identRes, identView) <- mreq textField "" ident > -- we use "passphrase" usually, but passwordField is Yesod term > (passph1Res, passph1View) <- mreq passwordField "" Nothing > (passph2Res, passph2View) <- mreq passwordField "" Nothing > + (nickRes, nickView) <- mreq nickField "" Nothing > (nameRes, nameView) <- mopt textField "" Nothing > (emailRes, emailView) <- mopt emailField "" Nothing > (avatarRes, avatarView) <- mopt textField "" Nothing > - (nickRes, nickView) <- mopt textField "" Nothing > + (ircNickRes, ircNickView) <- mopt textField "" Nothing > > let view = $(widgetFile "auth/create-user-form") > passphRes = case (passph1Res, passph2Res) of > @@ -54,15 +77,16 @@ createUserForm ident extra = do > (FormSuccess _, x) -> x > (x, _) -> x > > - result = (,,,,,) <$> identRes <*> passphRes <*> nameRes > - <*> emailRes <*> avatarRes <*> nickRes > + result = (,,,,,,) <$> identRes <*> passphRes <*> nickRes <*> nameRes > + <*> emailRes <*> avatarRes <*> ircNickRes > > return (result, view) > > editUserForm :: Maybe User -> Form UserUpdate > editUserForm muser = renderBootstrap3 BootstrapBasicForm $ > UserUpdate > - <$> aopt' textField "Public Name" (userName <$> muser) > + <$> areq' nickField "Public unique nickname" (userNick <$> muser) > + <*> aopt' textField "Public Name" (userName <$> muser) > <*> aopt' textField "Avatar image (link)" (userAvatar <$> muser) > <*> aopt' emailField "Email (not shown publicly)" (userEmail <$> > muser) > <*> aopt' textField "IRC nick @freenode.net" (userIrcNick <$> muser) > @@ -103,7 +127,8 @@ establishUserForm = > previewUserForm :: User -> Form UserUpdate > previewUserForm User{..} = renderBootstrap3 BootstrapBasicForm $ > UserUpdate > - <$> aopt hiddenField "" (Just userName) > + <$> areq hiddenField "" (Just userNick) > + <*> aopt hiddenField "" (Just userName) > <*> aopt hiddenField "" (Just userAvatar) > <*> aopt hiddenField "" (Just userEmail) > <*> aopt hiddenField "" (Just userIrcNick) > @@ -148,7 +173,7 @@ userNameWidget user_id = do > Nothing -> [whamlet|deleted user|] > Just user -> > [whamlet| > - <a href=@{UserR user_id}> > + <a href=@{UserR $ userNick user}> > #{userDisplayName (Entity user_id user)} > |] > > diff --git a/templates/application.hamlet b/templates/application.hamlet > index a1cc750..ddd9fb1 100644 > --- a/templates/application.hamlet > +++ b/templates/application.hamlet > @@ -4,7 +4,7 @@ > <td> > Account: > <td> > - <a href=@{UserR (volunteerApplicationUser application)}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > > <tr> > diff --git a/templates/auth/create-user-form.hamlet > b/templates/auth/create-user-form.hamlet > index 5a2972f..5698206 100644 > --- a/templates/auth/create-user-form.hamlet > +++ b/templates/auth/create-user-form.hamlet > @@ -9,7 +9,7 @@ > <tr> > <td> > <label for=#{fvId identView}> > - Account name (private, used for logging in): > + Account name (private, used only for logging in): > <td> > ^{fvInput identView} > <tr> > @@ -26,6 +26,12 @@ > ^{fvInput passph2View} > <tr> > <td> > + <label for=#{fvId nickView}> > + Unique nickname (public, required): > + <td> > + ^{fvInput nickView} > + <tr> > + <td> > <label for=#{fvId nameView}> > Name (public, optional): > <td> > @@ -44,7 +50,7 @@ > ^{fvInput avatarView} > <tr> > <td> > - <label for=#{fvId nickView}> > + <label for=#{fvId ircNickView}> > IRC Nick (irc.freenode.net, optional): > <td> > - ^{fvInput nickView} > + ^{fvInput ircNickView} > diff --git a/templates/comment.hamlet b/templates/comment.hamlet > index 97db8ce..febbb70 100644 > --- a/templates/comment.hamlet > +++ b/templates/comment.hamlet > @@ -5,14 +5,14 @@ > <span .p-name> > #{userDisplayName (Entity user_id user)} > $else > - <a .u-url .p-name href=@{UserR user_id}> > + <a .u-url .p-name href=@{UserR $ userNick user}> > $maybe author_avatar <- userAvatar user > <img .u-photo .small_avatar src=#{author_avatar}> # > #{userDisplayName (Entity user_id user)} > > $if can_establish > <div .comment-head-item> > - <a href=@{UserR user_id}> > + <a href=@{UserR $ userNick user}> > (establish user) > > $with ts <- fromMaybe (commentCreatedTs comment) (commentApprovedTs > comment) > @@ -65,7 +65,8 @@ > claimed > ^{renderTime (ticketClaimingTs claim)} > by > - <a href=@{UserR claiming_user_id}>#{userDisplayName $ Entity > claiming_user_id claiming_user}# > + <a href=@{UserR $ userNick claiming_user}> > + #{userDisplayName $ Entity claiming_user_id claiming_user}# > $maybe note <- ticketClaimingNote claim > : #{note} > > diff --git a/templates/default/navbar.hamlet b/templates/default/navbar.hamlet > index 6b4da61..4073beb 100644 > --- a/templates/default/navbar.hamlet > +++ b/templates/default/navbar.hamlet > @@ -20,22 +20,23 @@ > <button type=submit>go > <ul .right .break4> > <li> > - $maybe Entity uid user <- maybeUser > - $with name <- fromMaybe (userIdent user) $ userName user > - <div .ddown> > - <a href .ddown-toggle .user>#{name} > - <ul> > - <li> > - <a href=@{HomeR} :active HomeR:.active>Dashboard > - <li> > - <a href=@{UserR uid} :active (UserR uid):.active>Profile > - <li> > - <a href=@{UTransactionsR} :active UTransactionsR:.active> > - Transactions > - <li> > - <a href=@{UNotificationsR} :active UNotificationsR:.active> > - Notifications > - <li> > - <a href=@{AuthR LogoutR}>Log Out > + $maybe Entity _uid user <- maybeUser > + $with name <- fromMaybe (userNick user) $ userName user > + $with nick <- userNick user > + <div .ddown> > + <a href .ddown-toggle .user>#{name} > + <ul> > + <li> > + <a href=@{HomeR} :active HomeR:.active>Dashboard > + <li> > + <a href=@{UserR nick} :active (UserR nick):.active>Profile > + <li> > + <a href=@{UTransactionsR} :active UTransactionsR:.active> > + Transactions > + <li> > + <a href=@{UNotificationsR} :active UNotificationsR:.active> > + Notifications > + <li> > + <a href=@{AuthR LogoutR}>Log Out > $nothing > <a href=@{AuthR LoginR} :authActive:.active>Log In > diff --git a/templates/invite.hamlet b/templates/invite.hamlet > index 1356c39..9543aeb 100644 > --- a/templates/invite.hamlet > +++ b/templates/invite.hamlet > @@ -65,12 +65,12 @@ $else > <th>Info > <th>Role > <th>Code > - $forall Entity _ invite <- outstanding_invites > + $forall (Entity _ invite, Value nick) <- outstanding_invites > <tr> > <td>^{renderTime (inviteCreatedTs invite)} > $with user_id <- inviteUser invite > <td> > - <a href=@{UserR user_id}> > + <a href=@{UserR nick}> > #{format_inviter user_id} > <td>#{inviteTag invite} > <td>#{roleAbbrev (inviteRole invite)} > @@ -89,13 +89,13 @@ $else > <th>Info > <th>Role > <th>Code > - $forall Entity _ invite <- redeemed_invites > + $forall (Entity _ invite, Value nick) <- redeemed_invites > <tr> > <td>^{renderTime (fromMaybe now (inviteRedeemedTs invite))} > <td>#{format_user (inviteRedeemedBy invite)} > $with user_id <- inviteUser invite > <td> > - <a href=@{UserR user_id}> > + <a href=@{UserR nick}> > #{format_inviter user_id} > > <td>#{inviteTag invite} > diff --git a/templates/navbar.hamlet b/templates/navbar.hamlet > index 78dc98a..b525fff 100644 > --- a/templates/navbar.hamlet > +++ b/templates/navbar.hamlet > @@ -17,9 +17,9 @@ > $else > <span .glyphicon .glyphicon-envelope> > > - $with name <- fromMaybe (userIdent user) $ userName user > + $with name <- fromMaybe (userNick user) $ userName user > <li> > - <a href=@{UserR user_id}>#{name} > + <a href=@{UserR $ userNick user}>#{name} > <li> > <a href=@{AuthR LogoutR}>Log Out > $nothing > @@ -42,9 +42,9 @@ > #{num_unread_notifs} > $else > <span .glyphicon .glyphicon-envelope> > - $with name <- fromMaybe (userIdent user) $ userName user > + $with name <- fromMaybe (userNick user) $ userName user > <li> > - <a href=@{UserR user_id}>#{name} > + <a href=@{UserR $ userNick user}>#{name} > <li> > <a href=@{AuthR LogoutR}>Log Out > $nothing > diff --git a/templates/project_patrons.hamlet > b/templates/project_patrons.hamlet > index e739755..98cacdf 100644 > --- a/templates/project_patrons.hamlet > +++ b/templates/project_patrons.hamlet > @@ -8,7 +8,7 @@ $else > $forall (pledge, user) <- pledges > <tr> > <td> > - <a href=@{UserR (entityKey user)}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > <td> > #{show $ millMilray $ pledgeFundedShares $ entityVal pledge} > diff --git a/templates/project_transactions.hamlet > b/templates/project_transactions.hamlet > index f402a84..2f69858 100644 > --- a/templates/project_transactions.hamlet > +++ b/templates/project_transactions.hamlet > @@ -33,7 +33,7 @@ $else > $maybe other_account_id <- getOtherAccount transaction > $case M.lookup other_account_id account_map > $of Just (Right user) > - <a href=@{UserR $ entityKey user}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > > $of Just (Left project) > diff --git a/templates/sponsors.hamlet b/templates/sponsors.hamlet > index 5981a7f..1645db9 100644 > --- a/templates/sponsors.hamlet > +++ b/templates/sponsors.hamlet > @@ -24,16 +24,16 @@ > <a href="https://www.alephobjects.com/"> > Aleph Objects, Inc. > <li> > - <a href=@{UserR (toSqlKey 335)}> > + <a href=@{UserByIdR (toSqlKey 335)}> > Denver Bohling > <li> > - <a href=@{UserR (toSqlKey 213)}> > + <a href=@{UserByIdR (toSqlKey 213)}> > Philip Horger > <li> > - <a href=@{UserR (toSqlKey 380)}> > + <a href=@{UserByIdR (toSqlKey 380)}> > jamessan > <li> > - <a href=@{UserR (toSqlKey 551)}> > + <a href=@{UserByIdR (toSqlKey 551)}> > Tanu Kaskinen > <li> > <a href="http://linuxfund.org/"> > @@ -41,12 +41,12 @@ > <li> > Pat McGee > <li> > - <a href=@{UserR (toSqlKey 529)}> > + <a href=@{UserByIdR (toSqlKey 529)}> > Tomasz Muras > <li> > Paul Phillips > <li> > - <a href=@{UserR (toSqlKey 31)}> > + <a href=@{UserByIdR (toSqlKey 31)}> > Kate & Ira Pohl > > <h2 .text-center>General pre-launch donors > @@ -60,29 +60,29 @@ > <li> > Chris Aniszczyk > <li> > - <a href=@{UserR (toSqlKey 503)}> > + <a href=@{UserByIdR (toSqlKey 503)}> > Jorge Aranda > <li> > - <a href=@{UserR (toSqlKey 527)}> > + <a href=@{UserByIdR (toSqlKey 527)}> > Pierre Arlais, Bearstech > <li> > - <a href=@{UserR (toSqlKey 869)}> > + <a href=@{UserByIdR (toSqlKey 869)}> > Christopher Armstrong > <li> > - <a href=@{UserR (toSqlKey 876)}> > + <a href=@{UserByIdR (toSqlKey 876)}> > Xavier Antoviaque, OpenCraft > <li> > - <a href=@{UserR (toSqlKey 868)}> > + <a href=@{UserByIdR (toSqlKey 868)}> > Artyom > <li> > - <a href=@{UserR (toSqlKey 17)}> > + <a href=@{UserByIdR (toSqlKey 17)}> > Greg Austic > <li> > Autious > <li> > Kenneth Ballenegger > <li> > - <a href=@{UserR (toSqlKey 866)}> > + <a href=@{UserByIdR (toSqlKey 866)}> > Moritz Bartl > <li> > Brennen Bearnes > @@ -93,18 +93,18 @@ > <li> > Nick Barry > <li> > - <a href=@{UserR (toSqlKey 520)}> > + <a href=@{UserByIdR (toSqlKey 520)}> > Ingo Blechschmidt > <li> > - <a href=@{UserR (toSqlKey 560)}> > + <a href=@{UserByIdR (toSqlKey 560)}> > Eduard Bopp > <li> > - <a href=@{UserR (toSqlKey 464)}> > + <a href=@{UserByIdR (toSqlKey 464)}> > Nathan Bouscal > <li> > Jeremy Bowers > <li> > - <a href=@{UserR (toSqlKey 304)}> > + <a href=@{UserByIdR (toSqlKey 304)}> > Bret Comnes > <li> > Tim Bunce > @@ -113,21 +113,21 @@ > <li> > Lee Butts > <li> > - <a href=@{UserR (toSqlKey 883)}> > + <a href=@{UserByIdR (toSqlKey 883)}> > Javier Merino Cacho > <li> > - <a href=@{UserR (toSqlKey 762)}> > + <a href=@{UserByIdR (toSqlKey 762)}> > Scott Calvert & family > <li> > Mike Chaberski > <li> > - <a href=@{UserR (toSqlKey 863)}> > + <a href=@{UserByIdR (toSqlKey 863)}> > Jacob Chapman > <li> > - <a href=@{UserR (toSqlKey 337)}> > + <a href=@{UserByIdR (toSqlKey 337)}> > Chato > <li> > - <a href=@{UserR (toSqlKey 284)}> > + <a href=@{UserByIdR (toSqlKey 284)}> > Paul Chiusano > <li> > Jesper Cockx > @@ -142,34 +142,34 @@ > <li> > Debian KDE > <li> > - <a href=@{UserR (toSqlKey 393)}> > + <a href=@{UserByIdR (toSqlKey 393)}> > Jeroen Dekkers > <li> > Holger Dell > <li> > - <a href=@{UserR (toSqlKey 362)}> > + <a href=@{UserByIdR (toSqlKey 362)}> > Sebastian Dröge > <li> > <a href="http://andrewdurham.com/"> > Andrew Durham > <li> > - <a href=@{UserR (toSqlKey 397)}> > + <a href=@{UserByIdR (toSqlKey 397)}> > Christoph Egger > <li> > Carol Ann Emerick > <li> > - <a href=@{UserR (toSqlKey 374)}> > + <a href=@{UserByIdR (toSqlKey 374)}> > Victor Engmark > <li> > Eric Entzel > <li> > - <a href=@{UserR (toSqlKey 315)}> > + <a href=@{UserByIdR (toSqlKey 315)}> > Kent Fenwick > <li> > - <a href=@{UserR (toSqlKey 629)}> > + <a href=@{UserByIdR (toSqlKey 629)}> > John Feras > <li> > - <a href=@{UserR (toSqlKey 279)}> > + <a href=@{UserByIdR (toSqlKey 279)}> > Karl Fogel / QuestionCopyright.org > <li> > <a href="http://fossetcon.org/"> > @@ -188,28 +188,28 @@ > <li> > FTL Software > <li> > - <a href=@{UserR (toSqlKey 218)}> > + <a href=@{UserByIdR (toSqlKey 218)}> > Curtis Gagliardi > <li> > Luis Gasca > <li> > Denver Gingerich > <li> > - <a href=@{UserR (toSqlKey 369)}> > + <a href=@{UserByIdR (toSqlKey 369)}> > Daniel Glassey > <li> > Brook Heisler > <li> > Raphaël Hertzog > <li> > - <a href=@{UserR (toSqlKey 240)}> > + <a href=@{UserByIdR (toSqlKey 240)}> > Joey Hess > <li> > Joshua Hoblitt > <li> > Claudio Hoffmann > <li> > - <a href=@{UserR (toSqlKey 349)}> > + <a href=@{UserByIdR (toSqlKey 349)}> > Thomas Hochstein > <li> > Martin Höcker > @@ -218,117 +218,117 @@ > <li> > Lance Holton > <li> > - <a href=@{UserR (toSqlKey 630)}> > + <a href=@{UserByIdR (toSqlKey 630)}> > hotzeplotz > <li> > - <a href=@{UserR (toSqlKey 982)}> > + <a href=@{UserByIdR (toSqlKey 982)}> > Antonin Houska > <li> > - <a href=@{UserR (toSqlKey 623)}> > + <a href=@{UserByIdR (toSqlKey 623)}> > Karl Ove Hufthammer > <li> > Eskild Hustvedt > <li> > - <a href=@{UserR (toSqlKey 779)}> > + <a href=@{UserByIdR (toSqlKey 779)}> > Iko > <li> > Arya Irani > <li> > - <a href=@{UserR (toSqlKey 371)}> > + <a href=@{UserByIdR (toSqlKey 371)}> > Geoffrey Irving > <li> > - <a href=@{UserR (toSqlKey 543)}> > + <a href=@{UserByIdR (toSqlKey 543)}> > Seth de l'Isle > <li> > Ethan Johnson > <li> > H. Ryan Jones > <li> > - <a href=@{UserR (toSqlKey 419)}> > + <a href=@{UserByIdR (toSqlKey 419)}> > Ollie Jones > <li> > - <a href=@{UserR (toSqlKey 157)}> > + <a href=@{UserByIdR (toSqlKey 157)}> > Michiel de Jong > <li> > - <a href=@{UserR (toSqlKey 633)}> > + <a href=@{UserByIdR (toSqlKey 633)}> > Hasen el Judy | حسن الجودي | ハセン > <li> > Alan Keefer > <li> > - <a href=@{UserR (toSqlKey 247)}> > + <a href=@{UserByIdR (toSqlKey 247)}> > Ian Kelling > <li> > Andrew Klofas > <li> > - <a href=@{UserR (toSqlKey 668)}> > + <a href=@{UserByIdR (toSqlKey 668)}> > Robert Klotzner > <li> > Fabrice Knevez > <li> > Georg Kolling > <li> > - <a href=@{UserR (toSqlKey 246)}> > + <a href=@{UserByIdR (toSqlKey 246)}> > Eric Kow > <li> > Alanna Krause > <li> > - <a href=@{UserR (toSqlKey 1019)}> > + <a href=@{UserByIdR (toSqlKey 1019)}> > Ramana Kumar > <li> > - <a href=@{UserR (toSqlKey 651)}> > + <a href=@{UserByIdR (toSqlKey 651)}> > Michael F. Lamb > <li> > - <a href=@{UserR (toSqlKey 898)}> > + <a href=@{UserByIdR (toSqlKey 898)}> > Daniel Landau > <li> > - <a href=@{UserR (toSqlKey 875)}> > + <a href=@{UserByIdR (toSqlKey 875)}> > Brianna Laugher > <li> > Jean-Pierre Laurin > <li> > Alexander Lang > <li> > - <a href=@{UserR (toSqlKey 524)}> > + <a href=@{UserByIdR (toSqlKey 524)}> > Kirsten Larsen > <li> > Matt Lee > <li> > - <a href=@{UserR (toSqlKey 510)}> > + <a href=@{UserByIdR (toSqlKey 510)}> > Nathan Lee > <li> > - <a href=@{UserR (toSqlKey 28)}> > + <a href=@{UserByIdR (toSqlKey 28)}> > Randall Leeds > <li> > - <a href=@{UserR (toSqlKey 183)}> > + <a href=@{UserByIdR (toSqlKey 183)}> > Charles Lehner > <li> > - <a href=@{UserR (toSqlKey 454)}> > + <a href=@{UserByIdR (toSqlKey 454)}> > R. Diaz de Leon > <li> > Federico Leva > <li> > Aaron Levin > <li> > - <a href=@{UserR (toSqlKey 90)}> > + <a href=@{UserByIdR (toSqlKey 90)}> > Carl Lewis > <li> > - <a href=@{UserR (toSqlKey 167)}> > + <a href=@{UserByIdR (toSqlKey 167)}> > John A. Lewis > <li> > Katrin Leinweber > <li> > - <a href=@{UserR (toSqlKey 428)}> > + <a href=@{UserByIdR (toSqlKey 428)}> > Greg Lindahl > <li> > - <a href=@{UserR (toSqlKey 566)}> > + <a href=@{UserByIdR (toSqlKey 566)}> > Thorbjørn Lindeijer > <li> > - <a href=@{UserR (toSqlKey 40)}> > + <a href=@{UserByIdR (toSqlKey 40)}> > Mike Linksvayer > <li> > Yun-Mei Lo > <li> > - <a href=@{UserR (toSqlKey 532)}> > + <a href=@{UserByIdR (toSqlKey 532)}> > Brendan Long > <li> > Hans Lub > @@ -337,24 +337,24 @@ > <li> > Magan Adam > <li> > - <a href=@{UserR (toSqlKey 852)}> > + <a href=@{UserByIdR (toSqlKey 852)}> > Patrick Masson > <li> > - <a href=@{UserR (toSqlKey 564)}> > + <a href=@{UserByIdR (toSqlKey 564)}> > Martin Mauch > <li> > - <a href=@{UserR (toSqlKey 341)}> > + <a href=@{UserByIdR (toSqlKey 341)}> > Francois Marier > <li> > - <a href=@{UserR (toSqlKey 152)}> > + <a href=@{UserByIdR (toSqlKey 152)}> > Sean McGregor > <li> > - <a href=@{UserR (toSqlKey 903)}> > + <a href=@{UserByIdR (toSqlKey 903)}> > Ewen McNeill > <li> > Jan Mechtel > <li> > - <a href=@{UserR (toSqlKey 354)}> > + <a href=@{UserByIdR (toSqlKey 354)}> > Simon Michael > <li> > Florin Mihaila > @@ -365,29 +365,29 @@ > <li> > Havard Moen > <li> > - <a href=@{UserR (toSqlKey 384)}> > + <a href=@{UserByIdR (toSqlKey 384)}> > Ramakrishnan Muthukrishnan > <li> > - <a href=@{UserR (toSqlKey 556)}> > + <a href=@{UserByIdR (toSqlKey 556)}> > Lauro Gripa Neto > <li> > - <a href=@{UserR (toSqlKey 340)}> > + <a href=@{UserByIdR (toSqlKey 340)}> > Perry Nguyen > <li> > - <a href=@{UserR (toSqlKey 828)}> > + <a href=@{UserByIdR (toSqlKey 828)}> > Nmlgc > <li> > Lachlan O'Dea > <li> > - <a href=@{UserR (toSqlKey 636)}> > + <a href=@{UserByIdR (toSqlKey 636)}> > Robert Orzanna > <li> > - <a href=@{UserR (toSqlKey 467)}> > + <a href=@{UserByIdR (toSqlKey 467)}> > Étienne Vallette d'Osia > <li> > Tom Paluck > <li> > - <a href=@{UserR (toSqlKey 232)}> > + <a href=@{UserByIdR (toSqlKey 232)}> > Răzvan Panda > <li> > Fabian Peters > @@ -396,45 +396,45 @@ > <li> > Karl Pietrzak > <li> > - <a href=@{UserR (toSqlKey 347)}> > + <a href=@{UserByIdR (toSqlKey 347)}> > Steve Phillips / elimisteve > <li> > - <a href=@{UserR (toSqlKey 873)}> > + <a href=@{UserByIdR (toSqlKey 873)}> > Pini > <li> > - <a href=@{UserR (toSqlKey 877)}> > + <a href=@{UserByIdR (toSqlKey 877)}> > Josh Poimboeuf > <li> > - <a href=@{UserR (toSqlKey 230)}> > + <a href=@{UserByIdR (toSqlKey 230)}> > Timotheus Pokorra > <li> > Michał Politowski > <li> > - <a href=@{UserR (toSqlKey 507)}> > + <a href=@{UserByIdR (toSqlKey 507)}> > Shuwen Qian > <li> > Aaron Quamme > <li> > - <a href=@{UserR (toSqlKey 879)}> > + <a href=@{UserByIdR (toSqlKey 879)}> > Lane Rasberry > <li> > Olav Reinert > <li> > Dan Revel > <li> > - <a href=@{UserR (toSqlKey 446)}> > + <a href=@{UserByIdR (toSqlKey 446)}> > Kevin Riggle > <li> > - <a href=@{UserR (toSqlKey 437)}> > + <a href=@{UserByIdR (toSqlKey 437)}> > Matt Ritter > <li> > - <a href=@{UserR (toSqlKey 106)}> > + <a href=@{UserByIdR (toSqlKey 106)}> > Sage Ross > <li> > <a href="http://www.roundware.org/"> > Roundware.org > <li> > - <a href=@{UserR (toSqlKey 367)}> > + <a href=@{UserByIdR (toSqlKey 367)}> > Hugo Roy (FSFE) > <li> > Noé Rubinstein > @@ -443,63 +443,63 @@ > <li> > Marc Saegesser > <li> > - <a href=@{UserR (toSqlKey 95)}> > + <a href=@{UserByIdR (toSqlKey 95)}> > Chris Sakkas, livinglibre.com > <li> > Olli Savolainen > <li> > Tobias Schachman > <li> > - <a href=@{UserR (toSqlKey 867)}> > + <a href=@{UserByIdR (toSqlKey 867)}> > Brian Schroeder > <li> > - <a href=@{UserR (toSqlKey 459)}> > + <a href=@{UserByIdR (toSqlKey 459)}> > Sean Seefried > <li> > Paul Sexton > <li> > Kendrick Shaw > <li> > - <a href=@{UserR (toSqlKey 501)}> > + <a href=@{UserByIdR (toSqlKey 501)}> > Howard Lewis Ship > <li> > - <a href=@{UserR (toSqlKey 870)}> > + <a href=@{UserByIdR (toSqlKey 870)}> > Juan Raphael Diaz Simões > <li> > - <a href=@{UserR (toSqlKey 418)}> > + <a href=@{UserByIdR (toSqlKey 418)}> > Brandon Skari > <li> > Don Smith > <li> > - <a href=@{UserR (toSqlKey 634)}> > + <a href=@{UserByIdR (toSqlKey 634)}> > Leon P Smith > <li> > - <a href=@{UserR (toSqlKey 544)}> > + <a href=@{UserByIdR (toSqlKey 544)}> > Nick Smith > <li> > Jim Snow > <li> > Khaled Soliman > <li> > - <a href=@{UserR (toSqlKey 316)}> > + <a href=@{UserByIdR (toSqlKey 316)}> > Rodrigo Souto > <li> > - <a href=@{UserR (toSqlKey 297)}> > + <a href=@{UserByIdR (toSqlKey 297)}> > Adam Souzis > <li> > Adam Spitz > <li> > Charles Stanhope > <li> > - <a href=@{UserR (toSqlKey 606)}> > + <a href=@{UserByIdR (toSqlKey 606)}> > Stephen Starkey > <li> > - <a href=@{UserR (toSqlKey 482)}> > + <a href=@{UserByIdR (toSqlKey 482)}> > Startifact > <li> > statuszer0 > <li> > - <a href=@{UserR (toSqlKey 568)}> > + <a href=@{UserByIdR (toSqlKey 568)}> > Bob Steffes > <li> > Hugh Stimson > @@ -512,10 +512,10 @@ > <li> > Zachary Tellman > <li> > - <a href=@{UserR (toSqlKey 874)}> > + <a href=@{UserByIdR (toSqlKey 874)}> > Seth Tisue > <li> > - <a href=@{UserR (toSqlKey 263)}> > + <a href=@{UserByIdR (toSqlKey 263)}> > Frank Thomas > <li> > Greg Tomei > @@ -524,7 +524,7 @@ > <li> > Christian Uhl > <li> > - <a href=@{UserR (toSqlKey 423)}> > + <a href=@{UserByIdR (toSqlKey 423)}> > Vakranas > <li> > Timothy Vollmer > @@ -533,54 +533,54 @@ > <li> > Markus Vuorio (Maakuth) > <li> > - <a href=@{UserR (toSqlKey 590)}> > + <a href=@{UserByIdR (toSqlKey 590)}> > Tobias Gulbrandsen Waaler > <li> > - <a href=@{UserR (toSqlKey 614)}> > + <a href=@{UserByIdR (toSqlKey 614)}> > Albert Wavering > <li> > - <a href=@{UserR (toSqlKey 640)}> > + <a href=@{UserByIdR (toSqlKey 640)}> > Christopher Webber, GNU MediaGoblin > <li> > - <a href=@{UserR (toSqlKey 351)}> > + <a href=@{UserByIdR (toSqlKey 351)}> > Stephen Paul Weber (singpolyma) > <li> > - <a href=@{UserR (toSqlKey 399)}> > + <a href=@{UserByIdR (toSqlKey 399)}> > Patrick Weemeeuw > <li> > David Whitman > <li> > - <a href=@{UserR (toSqlKey 495)}> > + <a href=@{UserByIdR (toSqlKey 495)}> > Kevin Wichmann > <li> > - <a href=@{UserR (toSqlKey 458)}> > + <a href=@{UserByIdR (toSqlKey 458)}> > Alex Willemsma > <li> > Carl Witty > <li> > - <a href=@{UserR (toSqlKey 23)}> > + <a href=@{UserByIdR (toSqlKey 23)}> > Barry & Devorah Wolf > <li> > - <a href=@{UserR (toSqlKey 321)}> > + <a href=@{UserByIdR (toSqlKey 321)}> > Nicolas Wormser > <li> > - <a href=@{UserR (toSqlKey 415)}> > + <a href=@{UserByIdR (toSqlKey 415)}> > Thomas Wrenn > <li> > - <a href=@{UserR (toSqlKey 488)}> > + <a href=@{UserByIdR (toSqlKey 488)}> > Daniel Yokomizo > <li> > - <a href=@{UserR (toSqlKey 272)}> > + <a href=@{UserByIdR (toSqlKey 272)}> > Brent Yorgey > <li> > Philip Young > <li> > - <a href=@{UserR (toSqlKey 908)}> > + <a href=@{UserByIdR (toSqlKey 908)}> > Milan Zamazal > <li> > Shafiq Akram Zakaria > <li> > - <a href=@{UserR (toSqlKey 406)}> > + <a href=@{UserByIdR (toSqlKey 406)}> > Massimo Zaniboni > <li> > Jan Zernisch > diff --git a/templates/tag.hamlet b/templates/tag.hamlet > index 532be93..842dcff 100644 > --- a/templates/tag.hamlet > +++ b/templates/tag.hamlet > @@ -10,7 +10,7 @@ $else > $forall (user, votes) <- user_votes > <tr> > <td> > - <a href=@{UserR (entityKey user)}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > > <td> > diff --git a/templates/user.hamlet b/templates/user.hamlet > index db580c4..5dce7a5 100644 > --- a/templates/user.hamlet > +++ b/templates/user.hamlet > @@ -37,7 +37,7 @@ $if Just user_id == mviewer_id > $maybe email <- userEmail user > #{email} (not shown publicly) > $if isJust (userEmail user) && not (userEmail_verified user) > - <form method=POST action=@{UserR user_id}> > + <form method=POST action=@{UserR $ userNick user}> > <button>resend verification email > <br> > $maybe nick <- userIrcNick user > diff --git a/templates/user_discuss.hamlet b/templates/user_discuss.hamlet > index 9df64ed..6a7ee23 100644 > --- a/templates/user_discuss.hamlet > +++ b/templates/user_discuss.hamlet > @@ -5,7 +5,7 @@ $nothing > > <div .page-toolbox> > <div .page-tool> > - <a href=@{UserR user_id}>back to user profile > + <a href=@{UserR $ userNick user}>back to user profile > $maybe _ <- closedView > <div .page-tool> > <a href=@{UserDiscussionR user_id}> > diff --git a/templates/user_discussion_wrapper.hamlet > b/templates/user_discussion_wrapper.hamlet > index 60fc5da..f4b9843 100644 > --- a/templates/user_discussion_wrapper.hamlet > +++ b/templates/user_discussion_wrapper.hamlet > @@ -2,6 +2,6 @@ > <div .page-tool> > <a href=@{UserDiscussionR user_id}> back to full discussion > <div .page-tool> > - <a href=@{UserR user_id}> back to user page > + <a href=@{UserR $ userNick user}> back to user page > > ^{widget} > diff --git a/templates/user_tickets.hamlet b/templates/user_tickets.hamlet > index d4dd78a..303c9f6 100644 > --- a/templates/user_tickets.hamlet > +++ b/templates/user_tickets.hamlet > @@ -76,7 +76,7 @@ > > <td> > $maybe user_entity <- u > - <a href=@{UserR $ entityKey user_entity}> > + <a href=@{UserR $ userNick $ entityVal user_entity}> > #{userDisplayName user_entity} > > <td> > diff --git a/templates/users.hamlet b/templates/users.hamlet > index 56f7097..1721efe 100644 > --- a/templates/users.hamlet > +++ b/templates/users.hamlet > @@ -24,7 +24,7 @@ > $forall (_, user) <- users > <tr> > <td> > - <a href=@{UserR $ entityKey user}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > <td> > $maybe nick <- userIrcNick $ entityVal user > diff --git a/templates/who.hamlet b/templates/who.hamlet > index ead591f..65004ce 100644 > --- a/templates/who.hamlet > +++ b/templates/who.hamlet > @@ -1,11 +1,11 @@ > <h1> > <div .text-center> #{projectName project} Team > > -$forall Entity user_id user <- members > +$forall Entity _uid user <- members > <hr> > <div .row> > <div .bio_name .col-sm-3> > - <a href=@{UserR user_id}> > + <a href=@{UserR $ userNick user}> > #{userShortName user} > <br> > $maybe avatar <- userAvatar user > diff --git a/templates/wiki_history.hamlet b/templates/wiki_history.hamlet > index d6ba673..7182860 100644 > --- a/templates/wiki_history.hamlet > +++ b/templates/wiki_history.hamlet > @@ -14,7 +14,7 @@ > <td> > $with user_id <- wikiEditUser edit > $maybe user <- M.lookup user_id users > - <a href=@{UserR user_id}> > + <a href=@{UserR $ userNick $ entityVal user}> > #{userDisplayName user} > <td> > $maybe comment <- wikiEditComment edit > diff --git a/tests/TestImport.hs b/tests/TestImport.hs > index 0a3130e..79d80f7 100644 > --- a/tests/TestImport.hs > +++ b/tests/TestImport.hs > @@ -301,7 +301,7 @@ editWiki project language page content comment = do > > establish :: UserId -> YesodExample App () > establish user_id = do > - get200 $ UserR user_id > + get200 $ UserByIdR user_id > > withStatus 303 False $ request $ do > addToken > -- > 1.9.1 > > _______________________________________________ > Dev mailing list > [email protected] > https://lists.snowdrift.coop/mailman/listinfo/dev
signature.asc
Description: Digital signature
_______________________________________________ Dev mailing list [email protected] https://lists.snowdrift.coop/mailman/listinfo/dev
