# Run `./lila.sh playRoutes` after modifying this file

# Lobby
GET   /                                controllers.Lobby.home
GET   /lobby/seeks                     controllers.Lobby.seeks

# 2-letter routes, then lang route
GET   /tv                              controllers.Tv.index
GET   /$lang<\w\w>                     controllers.Lobby.homeLang(lang: Language)

# Highest hit count
GET   /account/info                    controllers.Account.info

# Timeline
GET   /timeline                        controllers.Timeline.home
GET   /api/timeline                    controllers.Timeline.api
POST  /timeline/unsub/:channel         controllers.Timeline.unsub(channel)

# Search
GET   /games/search                    controllers.Search.index(page: Int ?= 1)

# Game export
POST  /games/export/_ids               controllers.Game.exportByIds
POST  /api/games/export/_ids           controllers.Game.exportByIds
GET   /games/export/:username          controllers.Game.exportByUser(username: UserStr)

# Bookmark
POST  /bookmark/$gameId<\w{8}>         controllers.Game.bookmark(gameId: GameId)

# TV
GET   /$lang<\w\w\w?>/tv               controllers.Tv.indexLang(lang: Language)
GET   /tv/frame                        controllers.Tv.frameDefault
GET   /tv/feed                         controllers.Tv.feedDefault
GET   /tv/channels                     controllers.Main.movedPermanently(to = "/api/tv/channels")
GET   /tv/:chanKey                     controllers.Tv.onChannel(chanKey)
GET   /tv/:chanKey/frame               controllers.Tv.frame(chanKey)
GET   /tv/:chanKey/feed                controllers.Tv.feed(chanKey)
GET   /tv/$gameId<\w{8}>/:color/sides controllers.Tv.sides(gameId: GameId, color: Color)
GET   /games                           controllers.Tv.games
GET   /games/:chanKey                  controllers.Tv.gamesChannel(chanKey)
GET   /games/:chanKey/replacement/$gameId<\w{8}> controllers.Tv.gameChannelReplacement(chanKey, gameId: GameId, exclude: List[GameId])
GET   /api/tv/channels                 controllers.Tv.channels
GET   /api/tv/feed                     controllers.Tv.feedDefault
GET   /api/tv/:chanKey/feed            controllers.Tv.feed(chanKey)
GET   /api/tv/:chanKey                 controllers.Tv.apiGamesChannel(chanKey)

# Relation
GET   /api/rel/following               controllers.Relation.apiFollowing
POST  /api/rel/follow/:user            controllers.Relation.follow(user: UserStr)
POST  /api/rel/unfollow/:user          controllers.Relation.unfollow(user: UserStr)
POST  /api/rel/block/:userId           controllers.Relation.block(userId: UserStr)
POST  /api/rel/unblock/:userId         controllers.Relation.unblock(userId: UserStr)
# lichobile BC
POST  /rel/unfollow/:user              controllers.Relation.unfollowBc(user: UserStr)
POST  /rel/follow/:user                controllers.Relation.followBc(user: UserStr)
# end lichobile BC
GET   /@/:username/following           controllers.Relation.following(username: UserStr, page: Int ?= 1)
GET   /rel/blocks                      controllers.Relation.blocks(page: Int ?= 1)
POST  /rel/:action/:user               controllers.Relation.bcAction(action: String, user: UserStr)

# Insight
POST  /insights/refresh/:username      controllers.Insight.refresh(username: UserStr)
POST  /insights/data/:username         controllers.Insight.json(username: UserStr)
GET   /insights/:username              controllers.Insight.index(username: UserStr)
GET   /insights/:username/:metric/:dimension controllers.Insight.path(username: UserStr, metric, dimension, filters = "")
GET   /insights/:username/:metric/:dimension/*filters controllers.Insight.path(username: UserStr, metric, dimension, filters)

# User subpages
GET   /@/:username/tournaments/:path   controllers.UserTournament.path(username: UserStr, path, page: Int ?= 1)
GET   /@/:username/simuls/hosted       controllers.Simul.byUser(username: UserStr, page: Int ?= 1)

# User
GET   /api/stream/:username/mod        controllers.User.mod(username: UserStr)
POST  /@/:username/note                controllers.User.writeNote(username: UserStr)
POST  /note/delete/:id                 controllers.User.deleteNote(id)
POST  /note/setDox/:id/:v              controllers.User.setDoxNote(id, v: Boolean)
GET   /@/:username/mini                controllers.User.showMini(username: UserStr)
GET   /@/:username/tv                  controllers.User.tv(username: UserStr)
GET   /@/:username/perf/:perfKey       controllers.User.perfStat(username: UserStr, perfKey: PerfKey)
GET   /@/:username/all                 controllers.User.gamesAll(username: UserStr, page: Int ?= 1)
GET   /@/:username/download            controllers.User.download(username: UserStr)
GET   /@/:username/blog                controllers.Ublog.index(username: UserStr, page: Int ?= 1)
GET   /@/:username/blog.atom           controllers.Ublog.userAtom(username: UserStr)
GET   /@/:username/:filterName         controllers.User.games(username: UserStr, filterName, page: Int ?= 1)
GET   /@/:username                     controllers.User.show(username: UserStr)
GET   /player/myself                   controllers.User.myself
GET   /player/opponents                controllers.User.opponents
GET   /player/search/:term             controllers.User.search(term: String)
GET   /player                          controllers.User.list
GET   /player/top/:nb/:perfKey         controllers.User.topBcRedirect(nb: Int, perfKey: PerfKey)
GET   /player/top/:perfKey             controllers.User.top(perfKey: PerfKey, page: Int ?= 1)
GET   /api/player/top/:nb/:perfKey     controllers.User.topNbApi(nb: Int, perfKey: PerfKey)
GET   /player/online                   controllers.User.online
GET   /api/player/autocomplete         controllers.User.autocomplete
GET   /api/player                      controllers.User.apiList

# FIDE Player
GET   /fide                            controllers.Fide.index(page: Int ?= 1, q: Option[String] ?= None)
GET   /fide/federation                 controllers.Fide.federations(page: Int ?= 1)
GET   /fide/federation/:name           controllers.Fide.federation(name, page: Int ?= 1)
GET   /fide/:fideId/:name              controllers.Fide.show(fideId: chess.FideId, name, page: Int ?= 1)
POST  /fide/:fideId/follow             controllers.Fide.follow(fideId: chess.FideId, follow: Boolean)
GET   /api/fide/player/:id             controllers.Fide.apiShow(id: chess.FideId)
GET   /api/fide/player/:id/ratings     controllers.Fide.apiRatings(id: chess.FideId)
GET   /api/fide/player                 controllers.Fide.apiSearch(q: String)
POST  /upload/image/fide/player/:id    controllers.Fide.playerPhoto(id: chess.FideId)
POST  /fide/player/:id                 controllers.Fide.playerUpdate(id: chess.FideId)

GET   /dasher                          controllers.Dasher.get


# Blog/Ublog
GET   /blog                            controllers.Main.movedPermanently(to = "/@/Lichess/blog")
GET   /blog/topic                      controllers.Ublog.topics
GET   /blog/topic/:topic               controllers.Ublog.topic(topic, filter: Option[BlogQualityFilter] ?= None, by: BlogsBy ?= BlogsBy.newest, page: Int ?= 1)
GET   /blog/monthly                    controllers.Ublog.thisMonth(filter: Option[BlogQualityFilter] ?= None, by: BlogsBy ?= BlogsBy.newest, page: Int ?= 1)
GET   /blog/monthly/:year/:month       controllers.Ublog.byMonth(year: Int, month: Int, filter: Option[BlogQualityFilter] ?= None, by: BlogsBy ?= BlogsBy.newest, page: Int ?= 1)
GET   /blog.atom                       controllers.Main.movedPermanently(to = "/@/Lichess/blog.atom")
GET   /api/blog/carousel               controllers.Ublog.apiCarousel
GET   /blog/friends                    controllers.Ublog.friends(page: Int ?= 1)
GET   /blog/liked                      controllers.Ublog.liked(page: Int ?= 1)
GET   /blog/search                     controllers.Ublog.search(text ?= "", by: BlogsBy ?= BlogsBy.score, page: Int ?= 1)
GET   /blog/community                  controllers.Ublog.communityAll(filter: Option[BlogQualityFilter] ?= None, page: Int ?= 1)
GET   /$lang<\w\w\w?>/blog/community   controllers.Ublog.communityLang(lang: Language, filter: Option[BlogQualityFilter] ?= None, page: Int ?= 1)
GET   /blog/community.atom             controllers.Ublog.communityAtom(lang: Language ?= Language("all"))
GET   /blog/community/$lang<[\w-]{2,6}>.atom controllers.Ublog.communityAtom(lang: Language)
GET   /blog/:id/:slug                  controllers.Ublog.historicalBlogPost(id, slug)
GET   /@/:username/blog/:slug/:id      controllers.Ublog.post(username: UserStr, slug, id: UblogPostId)
GET   /@/:username/blog/drafts         controllers.Ublog.drafts(username: UserStr, page: Int ?= 1)
GET   /@/:username/blog/new            controllers.Ublog.form(username: UserStr)
POST  /@/:username/blog/new            controllers.Ublog.create(username: UserStr)
GET   /ublog/$id<\w{8}>/discuss        controllers.Ublog.discuss(id: UblogPostId)
GET   /ublog/$id<\w{8}>/redirect       controllers.Ublog.redirect(id: UblogPostId)
GET   /ublog/$id<\w{8}>/edit           controllers.Ublog.edit(id: UblogPostId)
POST  /ublog/$id<\w{8}>/edit           controllers.Ublog.update(id: UblogPostId)
POST  /ublog/$id<\w{8}>/del            controllers.Ublog.delete(id: UblogPostId)
POST  /ublog/$id<\w{8}>/like           controllers.Ublog.like(id: UblogPostId, v: Boolean)
POST  /ublog/:blogId/tier              controllers.Ublog.modBlog(blogId: String)
POST  /ublog/$id<\w{8}>/adjust         controllers.Ublog.modPost(id: UblogPostId)
POST  /ublog/$id<\w{8}>/pull           controllers.Ublog.modPull(id: UblogPostId)
POST  /ublog/$id<\w{8}>/assess         controllers.Ublog.modAssess(id: UblogPostId)
POST  /ublog/carousel/size             controllers.Ublog.modSetCarouselSize
GET   /ublog/carousel                  controllers.Ublog.modShowCarousel
POST  /upload/image/ublog/$id<\w{8}>   controllers.Ublog.image(id: UblogPostId)

# Feed
GET   /feed                            controllers.Feed.index(page: Int ?= 1)
GET   /feed/new                        controllers.Feed.createForm
POST  /feed/new                        controllers.Feed.create
GET   /feed/:id/edit                   controllers.Feed.edit(id)
POST  /feed/:id/edit                   controllers.Feed.update(id)
POST  /feed/:id/delete                 controllers.Feed.delete(id)
GET   /feed.atom                       controllers.Feed.atom

# Opening
GET   /opening                         controllers.Opening.index(q: Option[String] ?= None)
POST  /opening/config/:key             controllers.Opening.config(key)
POST  /opening/wiki/:key/:moves        controllers.Opening.wikiWrite(key, moves)
GET   /opening/tree                    controllers.Opening.tree
GET   /opening/:key                    controllers.Opening.byKeyAndMoves(key, moves = "")
GET   /opening/:key/:moves             controllers.Opening.byKeyAndMoves(key, moves)

# Training - Coordinate
GET   /training/coordinate             controllers.Coordinate.home
POST  /training/coordinate/score       controllers.Coordinate.score
GET   /$lang<\w\w\w?>/training/coordinate controllers.Coordinate.homeLang(lang: Language)

# Training - Puzzle
GET   /training                        controllers.Puzzle.home
GET   /training/daily                  controllers.Puzzle.daily
GET   /training/frame                  controllers.Puzzle.frame
GET   /training/help                   controllers.Puzzle.help
GET   /training/export/gif/thumbnail/:id.gif controllers.Export.puzzleThumbnail(id: PuzzleId, theme: Option[String], piece: Option[String])
GET   /training/themes                 controllers.Puzzle.themes
GET   /training/openings               controllers.Puzzle.openings(order ?= "popular")
GET   /training/of-player              controllers.Puzzle.ofPlayer(name: Option[UserStr] ?= None, page: Int ?= 1)
GET   /training/dashboard/$days<\d+>   controllers.Puzzle.dashboard(days: Days, path = "home", u: Option[UserStr])
GET   /training/dashboard/$days<\d+>/:path  controllers.Puzzle.dashboard(days: Days, path, u: Option[UserStr])
GET   /training/replay/$days<\d+>/:theme controllers.Puzzle.replay(days: Days, theme)
GET   /training/history                controllers.Puzzle.history(page: Int ?= 1, u: Option[UserStr])
GET   /training/batch                  controllers.Puzzle.mobileBcBatchSelect
POST  /training/batch                  controllers.Puzzle.mobileBcBatchSolve
GET   /training/new                    controllers.Puzzle.mobileBcNew
GET   /training/$numericalId<\d{6,}>/load      controllers.Puzzle.mobileBcLoad(numericalId: Long)
POST  /training/$numericalId<\d{6,}>/vote      controllers.Puzzle.mobileBcVote(numericalId: Long)
GET   /training/:angleOrId             controllers.Puzzle.show(angleOrId)
GET   /training/:angle/$color<white|black|random> controllers.Puzzle.angleAndColor(angle, color: String)
GET   /training/:angle/$id<\w{5}>      controllers.Puzzle.showWithAngle(angle, id: PuzzleId)
POST  /training/$numericalId<\d{6,}>/round2    controllers.Puzzle.mobileBcRound(numericalId: Long)
POST  /training/$id<\w{5}>/vote        controllers.Puzzle.vote(id: PuzzleId)
POST  /training/$id<\w{5}>/report      controllers.Puzzle.report(id: PuzzleId)
POST  /training/$id<\w{5}>/vote/:theme controllers.Puzzle.voteTheme(id: PuzzleId, theme)
POST  /training/complete/:theme/$id<\w{5}>  controllers.Puzzle.complete(theme, id: PuzzleId)
POST  /training/difficulty/:theme      controllers.Puzzle.setDifficulty(theme)
GET   /$lang<\w\w\w?>/training            controllers.Puzzle.homeLang(lang: Language)
GET   /$lang<\w\w\w?>/training/themes     controllers.Puzzle.themesLang(lang: Language)
GET   /$lang<\w\w\w?>/training/:angleOrId controllers.Puzzle.showLang(lang: Language, angleOrId)

# Puzzle Streak
GET   /streak                           controllers.Puzzle.streak
GET   /api/streak                       controllers.Puzzle.apiStreak
POST  /api/streak/:score                controllers.Puzzle.apiStreakResult(score: Int)
GET   /$lang<\w\w\w?>/streak            controllers.Puzzle.streakLang(lang: Language)

# Puzzle Storm
GET   /storm                            controllers.Storm.home
POST  /storm                            controllers.Storm.record
GET   /storm/dashboard                  controllers.Storm.dashboard(page: Int ?= 1)
GET   /storm/dashboard/:username        controllers.Storm.dashboardOf(username: UserStr, page: Int ?= 1)
GET   /api/storm                        controllers.Storm.apiGet
GET   /api/storm/dashboard/:username    controllers.Storm.apiDashboardOf(username: UserStr, days: Int ?= 30)
GET   /$lang<\w\w\w?>/storm             controllers.Storm.homeLang(lang: Language)

# Puzzle Racer
GET   /racer                            controllers.Racer.home
POST  /racer                            controllers.Racer.create
POST  /api/racer                        controllers.Racer.apiCreate
GET   /api/racer/:id                    controllers.Racer.apiGet(id)
GET   /racer/:id                        controllers.Racer.show(id)
GET   /racer/:id/rematch                controllers.Racer.rematch(id)
POST  /racer/lobby                      controllers.Racer.lobby
GET   /$lang<\w\w\w?>/racer             controllers.Racer.homeLang(lang: Language)

# User Analysis
GET   /analysis/help                   controllers.UserAnalysis.help
GET   /analysis/pgn/*pgn               controllers.UserAnalysis.pgn(pgn)
GET   /analysis/*something             controllers.UserAnalysis.parseArg(something)
GET   /analysis                        controllers.UserAnalysis.index
GET   /embed/analysis                  controllers.UserAnalysis.embed

# Study
GET   /study                           controllers.Study.allDefault(page: Int ?= 1)
GET   /study/staff-picks               controllers.Study.staffPicks
POST  /study/staff-picks               controllers.Study.staffPicksPost
GET   /study/all/:order                controllers.Study.all(order: StudyOrder, page: Int ?= 1)
GET   /study/mine/:order               controllers.Study.mine(order: StudyOrder, page: Int ?= 1)
GET   /study/member/:order             controllers.Study.mineMember(order: StudyOrder, page: Int ?= 1)
GET   /study/public/:order             controllers.Study.minePublic(order: StudyOrder, page: Int ?= 1)
GET   /study/private/:order            controllers.Study.minePrivate(order: StudyOrder, page: Int ?= 1)
GET   /study/likes/:order              controllers.Study.mineLikes(order: StudyOrder, page: Int ?= 1)
GET   /study/by/:username              controllers.Study.byOwnerDefault(username: UserStr, page: Int ?= 1)
GET   /study/by/:username/:order       controllers.Study.byOwner(username: UserStr, order: StudyOrder, page: Int ?= 1)
GET   /study/search                    controllers.Study.search(q ?= "", page: Int ?= 1, order: Option[StudyOrder] ?= None)
GET   /study/$id<\w{8}>                controllers.Study.show(id: StudyId)
POST  /study                           controllers.Study.create
POST  /study/as                        controllers.Study.createAs
GET   /study/$id<\w{8}>.pgn            controllers.Study.pgn(id: StudyId)
GET   /study/$id<\w{8}>/$chapterId<\w{8}>.pgn controllers.Study.chapterPgn(id: StudyId, chapterId: StudyChapterId)
GET   /study/$id<\w{8}>/$chapterId<\w{8}>.gif controllers.Study.chapterGif(id: StudyId, chapterId: StudyChapterId, theme: Option[String], piece: Option[String], showGlyphs: Boolean ?= false)
POST  /study/$id<\w{8}>/delete         controllers.Study.delete(id: StudyId)
GET   /study/$id<\w{8}>/clone          controllers.Study.cloneStudy(id: StudyId)
POST  /study/$id<\w{8}>/cloneApply     controllers.Study.cloneApply(id: StudyId)
GET   /study/$id<\w{8}>/$chapterId<\w{8}>        controllers.Study.chapter(id: StudyId, chapterId: StudyChapterId)
GET   /study/$id<\w{8}>/$chapterId<\w{8}>/config controllers.Study.chapterConfig(id: StudyId, chapterId: StudyChapterId)
GET   /study/embed/$id<\w{8}>/$chapterId<\w{8}>  controllers.Study.embed(id: StudyId, chapterId: StudyChapterId)
POST  /study/$id<\w{8}>/clear-chat     controllers.Study.clearChat(id: StudyId)
POST  /study/$id<\w{8}>/import-pgn     controllers.Study.importPgn(id: StudyId)
POST  /study/$id<\w{8}>/admin          controllers.Study.admin(id: StudyId)
GET   /study/topic                     controllers.Study.topics
POST  /study/topic                     controllers.Study.setTopics
GET   /study/topic/:topic/:order       controllers.Study.byTopic(topic, order: StudyOrder, page: Int ?= 1)
GET   /study/topic/autocomplete        controllers.Study.topicAutocomplete
GET   /study/glyphs/:lang.json         controllers.Study.glyphs(lang: String)
GET   /$lang<\w\w\w?>/study            controllers.Study.homeLang(lang: Language)
POST  /api/study                       controllers.Study.apiCreate
GET   /api/study/$id<\w{8}>.pgn        controllers.Study.apiPgn(id: StudyId)
GET   /api/study/$id<\w{8}>/$chapterId<\w{8}>.pgn controllers.Study.apiChapterPgn(id: StudyId, chapterId: StudyChapterId)
DELETE /api/study/$id<\w{8}>/$chapterId<\w{8}>    controllers.Study.apiChapterDelete(id: StudyId, chapterId: StudyChapterId)
GET   /api/study/by/:user              controllers.Study.apiListByOwner(user: UserStr)
GET   /api/study/by/:username/export.pgn controllers.Study.apiExportPgn(username: UserStr)
POST  /api/study/$id<\w{8}>/import-pgn controllers.Study.apiImportPgn(id: StudyId)
POST  /api/study/$id<\w{8}>/$chapterId<\w{8}>/tags controllers.Study.apiChapterTagsUpdate(id: StudyId, chapterId: StudyChapterId)
POST  /api/study/$id<\w{8}>/$chapterId<\w{8}>/moves controllers.Study.apiChapterPgnMovesUpdate(id: StudyId, chapterId: StudyChapterId)

# Relay
GET   /broadcast                              controllers.RelayTour.index(page: Int ?= 1, q ?= "")
GET   /broadcast/new                          controllers.RelayTour.form
POST  /broadcast/new                          controllers.RelayTour.create
GET   /broadcast/calendar                     controllers.RelayTour.calendar
GET   /broadcast/calendar/:year/:month        controllers.RelayTour.calendarMonth(year: Int, month: Int)
GET   /broadcast/help                         controllers.RelayTour.help
GET   /broadcast/app                          controllers.RelayTour.app
GET   /broadcast/by/:user                     controllers.RelayTour.by(user: UserStr, page: Int ?= 1)
GET   /api/broadcast/by/:user                 controllers.RelayTour.apiBy(user: UserStr, page: Int ?= 1)
GET   /broadcast/:ts/$id<\w{8}>               controllers.RelayTour.show(ts, id: RelayTourId)
GET   /embed/broadcast/:ts/$id<\w{8}>         controllers.RelayTour.embedShow(ts, id: RelayTourId)
GET   /api/broadcast/$id<\w{8}>               controllers.RelayTour.apiShow(id: RelayTourId)
GET   /api/broadcast/$tourId<\w{8}>.pgn       controllers.RelayTour.pgn(tourId: RelayTourId)
GET   /broadcast/$tourId<\w{8}>/edit          controllers.RelayTour.edit(tourId: RelayTourId)
POST  /broadcast/$tourId<\w{8}>/edit          controllers.RelayTour.update(tourId: RelayTourId)
POST  /broadcast/$tourId<\w{8}>/delete        controllers.RelayTour.delete(tourId: RelayTourId)
POST  /upload/image/broadcast/$id<\w{8}>      controllers.RelayTour.image(id: RelayTourId, tag: Option[String] ?= None)
POST  /broadcast/$tourId<\w{8}>/clone         controllers.RelayTour.cloneTour(tourId: RelayTourId)
POST  /broadcast/$tourId<\w{8}>/subscribe     controllers.RelayTour.subscribe(tourId: RelayTourId, set: Boolean)
GET   /broadcast/$tourId<\w{8}>/new           controllers.RelayRound.form(tourId: RelayTourId)
POST  /broadcast/$tourId<\w{8}>/new           controllers.RelayRound.create(tourId: RelayTourId)
GET   /broadcast/$tourId<\w{8}>/players       controllers.RelayTour.playersView(tourId: RelayTourId)
GET   /broadcast/$tourId<\w{8}>/players/:id   controllers.RelayTour.player(tourId: RelayTourId, id:  String)
GET   /broadcast/$tourId<\w{8}>/teams/standings controllers.RelayTour.teamLeaderboard(tourId: RelayTourId)
GET   /broadcast/:ts/:rs/$roundId<\w{8}>      controllers.RelayRound.show(ts, rs, roundId: RelayRoundId)
GET   /api/broadcast/:ts/:rs/$roundId<\w{8}>  controllers.RelayRound.apiShow(ts, rs, roundId: RelayRoundId)
GET   /embed/broadcast/:ts/:rs/$roundId<\w{8}> controllers.RelayRound.embedShow(ts, rs, roundId: RelayRoundId)
GET   /embed/broadcast/:ts/:rs/$roundId<\w{8}>/$chapterId<\w{8}> controllers.RelayRound.embedChapter(ts, rs, roundId: RelayRoundId, chapterId: StudyChapterId)
GET   /broadcast/:ts/:rs/$roundId<\w{8}>/$chapterId<\w{8}> controllers.RelayRound.chapter(ts, rs, roundId: RelayRoundId, chapterId: StudyChapterId)
GET   /broadcast/round/$roundId<\w{8}>/edit   controllers.RelayRound.edit(roundId: RelayRoundId)
POST  /broadcast/round/$roundId<\w{8}>/edit   controllers.RelayRound.update(roundId: RelayRoundId)
POST  /broadcast/round/$roundId<\w{8}>/reset  controllers.RelayRound.reset(roundId: RelayRoundId)
POST  /api/broadcast/round/$roundId<\w{8}>/reset  controllers.RelayRound.reset(roundId: RelayRoundId)
GET   /broadcast/round/$roundId<\w{8}>/stats  controllers.RelayRound.stats(roundId: RelayRoundId)
POST  /api/broadcast/round/$roundId<\w{8}>/push controllers.RelayRound.push(roundId: RelayRoundId)
GET   /broadcast/:ts/:rs/$roundId<\w{8}>.pgn  controllers.RelayRound.pgn(ts, rs, roundId: RelayRoundId)
GET   /broadcast/$roundId<\w{8}>/teams        controllers.RelayRound.teamsView(roundId: RelayRoundId)
GET   /broadcast/:pager                       controllers.RelayTour.pager(pager: String, page: Int ?= 1)
GET   /api/broadcast/round/$roundId<\w{8}>.pgn controllers.RelayRound.apiPgn(roundId: RelayRoundId)
GET   /api/stream/broadcast/round/$roundId<\w{8}>.pgn controllers.RelayRound.stream(roundId: RelayRoundId)
GET   /api/broadcast                          controllers.RelayTour.apiIndex
GET   /api/broadcast/top                      controllers.RelayTour.apiTop(page: Int ?= 1)
GET   /api/broadcast/search                   controllers.RelayTour.apiSearch(page: Int ?= 1, q: String ?= "")
GET   /api/broadcast/my-rounds                controllers.RelayRound.apiMyRounds
GET   /$lang<\w\w\w?>/broadcast               controllers.RelayTour.indexLang(lang: Language)

# Learn
GET   /learn                           controllers.Learn.index
POST  /learn/score                     controllers.Learn.score
POST  /learn/reset                     controllers.Learn.reset
GET   /$lang<\w\w\w?>/learn            controllers.Learn.indexLang(lang: Language)

# Patron
GET   /patron                          controllers.Plan.index(page: Int ?= 1)
GET   /patron/thanks                   controllers.Plan.thanks
GET   /patron/list                     controllers.Plan.list
POST  /patron/style                    controllers.Plan.style
POST  /patron/switch                   controllers.Plan.switch
POST  /patron/cancel                   controllers.Plan.cancel
POST  /patron/webhook                  controllers.Plan.webhook
POST  /patron/stripe/checkout          controllers.Plan.stripeCheckout
POST  /patron/stripe/update-payment    controllers.Plan.updatePayment
GET   /patron/stripe/update-payment    controllers.Plan.updatePaymentCallback
POST  /patron/ipn                      controllers.Plan.payPalIpn
POST  /patron/paypal/checkout          controllers.Plan.payPalCheckout
POST  /patron/paypal/capture/:id       controllers.Plan.payPalCapture(id)
POST  /api/patron/stripe/checkout      controllers.Plan.apiStripeCheckout
GET   /api/patron/currencies           controllers.Plan.apiCurrencies
GET   /features                        controllers.Plan.features

# Practice
GET   /practice                                   controllers.Practice.index
GET   /practice/load/:studyId/:chapterId          controllers.Practice.chapter(studyId: StudyId, chapterId: StudyChapterId)
POST  /practice/reset                             controllers.Practice.reset
GET   /practice/:sectionId                        controllers.Practice.showSection(sectionId)
GET   /practice/:sectionId/:studySlug             controllers.Practice.showStudySlug(sectionId, studySlug)
GET   /practice/:sectionId/:studySlug/:studyId    controllers.Practice.show(sectionId, studySlug, studyId: StudyId)
GET   /practice/:sectionId/:studySlug/:studyId/:chapterId controllers.Practice.showChapter(sectionId, studySlug, studyId: StudyId, chapterId: StudyChapterId)
POST  /practice/complete/:chapterId/:moves        controllers.Practice.complete(chapterId: StudyChapterId, moves: Int)

# Streamer
GET   /streamer                        controllers.Streamer.index(page: Int ?= 1)
GET   /api/streamer/featured           controllers.Streamer.featured
GET   /api/streamer/live               controllers.Streamer.live
POST  /api/x/streamer/youtube-pubsub   controllers.Streamer.onYoutubeVideo
GET   /api/x/streamer/youtube-pubsub   controllers.Streamer.youtubePubSubChallenge
POST  /api/streamer/twitch-eventsub    controllers.Streamer.onTwitchEventSub
GET   /streamer/live                   controllers.Main.movedPermanently(to = "/api/streamer/live")
GET   /streamer/edit                   controllers.Streamer.edit
POST  /streamer/new                    controllers.Streamer.create
POST  /streamer/edit                   controllers.Streamer.editApply
POST  /streamer/subscribe/:streamer    controllers.Streamer.subscribe(streamer: UserStr, set: Boolean ?= true)
POST  /upload/image/streamer           controllers.Streamer.pictureApply
GET   /streamer/:username              controllers.Streamer.show(username: UserStr, redirect: Boolean ?= false)
POST  /streamer/:username/check        controllers.Streamer.checkOnline(username: UserStr)
GET   /streamer/twitch/oauth/link      controllers.Streamer.oauthLinkTwitch
GET   /streamer/twitch/oauth/redirect  controllers.Streamer.oauthTwitchRedirect
GET   /streamer/youtube/oauth/link     controllers.Streamer.oauthLinkYoutube
GET   /streamer/youtube/oauth/redirect controllers.Streamer.oauthYoutubeRedirect
POST  /streamer/youtube/oauth/choose/:channelId controllers.Streamer.oauthYoutubeChannel(channelId: String)
POST  /streamer/$platform<youtube|twitch>/oauth/unlink controllers.Streamer.oauthUnlink(platform: String, user: Option[UserStr] ?= None)

# Private Play

GET   /bots                           controllers.JsBot.index
GET   /bots/assets                    controllers.JsBot.assetKeys
# GET   /bots/test                      controllers.JsBot.test
GET   /bots/dev                       controllers.JsBot.devIndex
GET   /bots/dev/history               controllers.JsBot.devBotHistory(id: Option[String])
POST  /bots/dev/bot                   controllers.JsBot.devPostBot
GET   /bots/dev/assets                controllers.JsBot.devAssets
POST  /bots/dev/asset/$tpe<sound|image|book>/$key<\w{12}(\.\w{2,4})?> controllers.JsBot.devPostAsset(tpe: String, key: String)
POST  /bots/dev/asset/mv/$key<\w{12}(\.\w{2,4})?>/:name  controllers.JsBot.devNameAsset(key: String, name: String)

# Round
GET   /$gameId<\w{8}>                            controllers.Round.watcher(gameId: GameId, color: Color = Color.white)
GET   /$gameId<\w{8}>/$color<white|black>        controllers.Round.watcher(gameId: GameId, color: Color)
GET   /$fullId<\w{12}>                           controllers.Round.player(fullId: GameFullId)
GET   /$gameId<\w{8}>/$color<white|black>/sides  controllers.Round.sides(gameId: GameId, color: Color)
GET   /$gameId<\w{8}>/continue/:mode             controllers.Round.continue(gameId: GameId, mode)
GET   /$gameId<\w{8}>/note                       controllers.Round.readNote(gameId: GameId)
POST  /$gameId<\w{8}>/note                       controllers.Round.writeNote(gameId: GameId)
GET   /$gameId<\w{8}>/mini                       controllers.Round.mini(gameId: GameId, color: Color = Color.white)
GET   /$gameId<\w{8}>/$color<white|black>/mini   controllers.Round.mini(gameId: GameId, color: Color)
GET   /$fullId<\w{12}>/mini                      controllers.Round.miniFullId(fullId: GameFullId)
GET   /$gameId<\w{8}>/edit                       controllers.Editor.game(gameId: GameId)
GET   /$gameId<\w{8}>/$color<white|black>/analysis controllers.UserAnalysis.game(gameId: GameId, color: Color)
GET   /$fullId<\w{12}>/forecasts                 controllers.UserAnalysis.forecastsGet(fullId: GameFullId)
POST  /$fullId<\w{12}>/forecasts                 controllers.UserAnalysis.forecastsPost(fullId: GameFullId)
POST  /$fullId<\w{12}>/forecasts/:uci            controllers.UserAnalysis.forecastsOnMyTurn(fullId: GameFullId, uci)
POST  /$fullId<\w{12}>/resign                    controllers.Round.resign(fullId: GameFullId)

GET   /embed/$gameId<\w{8}>                      controllers.Analyse.embed(gameId: GameId, color: Color = Color.white)
GET   /embed/$gameId<\w{8}>/$color<white|black>  controllers.Analyse.embed(gameId: GameId, color: Color)

GET   /embed/game/$gameId<\w{8}>                      controllers.Analyse.embedReplayGame(gameId: GameId, color: Color = Color.white)
GET   /embed/game/$gameId<\w{8}>/$color<white|black>  controllers.Analyse.embedReplayGame(gameId: GameId, color: Color)

POST  /$gameId<\w{8}>/delete                     controllers.Game.delete(gameId: GameId)

GET   /round-next/$gameId<\w{8}>                 controllers.Round.next(gameId: GameId)
GET   /whats-next/$fullId<\w{12}>                controllers.Round.whatsNext(fullId: GameFullId)
GET   /round/help                                controllers.Round.help

# Tournament
GET   /tournament                             controllers.Tournament.home
GET   /tournament/featured                    controllers.Tournament.featured
GET   /tournament/new                         controllers.Tournament.form
POST  /tournament/new                         controllers.Tournament.webCreate
GET   /tournament/team-battle/new/:teamId     controllers.Tournament.teamBattleForm(teamId: TeamId)
GET   /tournament/team-battle/edit/:id        controllers.Tournament.teamBattleEdit(id: TourId)
POST  /tournament/team-battle/edit/:id        controllers.Tournament.teamBattleUpdate(id: TourId)
GET   /tournament/calendar                    controllers.Tournament.calendar
GET   /tournament/history                     controllers.Tournament.history(freq = "unique", page: Int ?= 1)
GET   /tournament/history/:freq               controllers.Tournament.history(freq, page: Int ?= 1)
GET   /tournament/$id<\w{8}>                  controllers.Tournament.show(id: TourId)
GET   /tournament/$id<\w{8}>/standing/:page   controllers.Tournament.standing(id: TourId, page: Int)
GET   /tournament/$id<\w{8}>/page-of/:user    controllers.Tournament.pageOf(id: TourId, user: UserStr)
POST  /tournament/$id<\w{8}>/join             controllers.Tournament.join(id: TourId)
POST  /tournament/$id<\w{8}>/withdraw         controllers.Tournament.pause(id: TourId)
GET   /tournament/$id<\w{8}>/player/:user     controllers.Tournament.player(id: TourId, user: UserStr)
GET   /tournament/$id<\w{8}>/team/:team       controllers.Tournament.teamInfo(id: TourId, team: TeamId)
POST  /tournament/$id<\w{8}>/terminate        controllers.Tournament.terminate(id: TourId)
GET   /tournament/$id<\w{8}>/edit             controllers.Tournament.edit(id: TourId)
POST  /tournament/$id<\w{8}>/edit             controllers.Tournament.update(id: TourId)
GET   /tournament/$id<\w{8}>/teams            controllers.Tournament.battleTeams(id: TourId)
GET   /tournament/$id<\w{8}>/mod/:view        controllers.Tournament.moderation(id: TourId, view: String)
GET   /tournament/help                        controllers.Tournament.help
GET   /tournament/leaderboard                 controllers.Tournament.leaderboard
GET   /tournament/shields                     controllers.Tournament.shields
GET   /tournament/shields/:categ              controllers.Tournament.categShields(categ)
GET   /$lang<\w\w\w?>/tournament              controllers.Tournament.homeLang(lang: Language)

# Tournament CRUD
GET   /tournament/manager                     controllers.TournamentCrud.index(page: Int ?= 1)
GET   /tournament/manager/clone/$id<\w{8}>    controllers.TournamentCrud.cloneT(id: TourId)
GET   /tournament/manager/$id<\w{8}>          controllers.TournamentCrud.edit(id: TourId)
POST  /tournament/manager/$id<\w{8}>          controllers.TournamentCrud.update(id: TourId)
GET   /tournament/manager/new                 controllers.TournamentCrud.form
POST  /tournament/manager                     controllers.TournamentCrud.create

# Swiss
GET   /swiss                                  controllers.Swiss.home
GET   /swiss/new/:teamId                      controllers.Swiss.form(teamId: TeamId)
POST  /swiss/new/:teamId                      controllers.Swiss.create(teamId: TeamId)
GET   /swiss/$id<\w{8}>                       controllers.Swiss.show(id: SwissId)
GET   /swiss/$id<\w{8}>/round/:round          controllers.Swiss.round(id: SwissId, round: Int)
GET   /swiss/$id<\w{8}>.trf                   controllers.Swiss.exportTrf(id: SwissId)
GET   /swiss/$id<\w{8}>/edit                  controllers.Swiss.edit(id: SwissId)
POST  /swiss/$id<\w{8}>/edit                  controllers.Swiss.update(id: SwissId)
POST  /swiss/$id<\w{8}>/terminate             controllers.Swiss.terminate(id: SwissId)
GET   /swiss/$id<\w{8}>/standing/:page        controllers.Swiss.standing(id: SwissId, page: Int)
GET   /swiss/$id<\w{8}>/page-of/:user         controllers.Swiss.pageOf(id: SwissId, user: UserStr)
GET   /swiss/$id<\w{8}>/player/:user          controllers.Swiss.player(id: SwissId, user: UserStr)
POST  /api/swiss/$id<\w{8}>/schedule-next-round controllers.Swiss.scheduleNextRound(id: SwissId)
GET   /$lang<\w\w\w?>/swiss                   controllers.Swiss.homeLang(lang: Language)

# Simul
GET   /simul                                  controllers.Simul.home
GET   /simul/new                              controllers.Simul.form
POST  /simul/new                              controllers.Simul.create
GET   /simul/reload                           controllers.Simul.homeReload
GET   /simul/$id<\w{8}>                       controllers.Simul.show(id: SimulId)
GET   /simul/$id<\w{8}>/edit                  controllers.Simul.edit(id: SimulId)
POST  /simul/$id<\w{8}>/edit                  controllers.Simul.update(id: SimulId)
POST  /simul/$id<\w{8}>/host-ping             controllers.Simul.hostPing(id: SimulId)
POST  /simul/$id<\w{8}>/accept/:user          controllers.Simul.accept(id: SimulId, user: UserStr)
POST  /simul/$id<\w{8}>/reject/:user          controllers.Simul.reject(id: SimulId, user: UserStr)
POST  /simul/$id<\w{8}>/start                 controllers.Simul.start(id: SimulId)
POST  /simul/$id<\w{8}>/abort                 controllers.Simul.abort(id: SimulId)
POST  /simul/$id<\w{8}>/join/:variant         controllers.Simul.join(id: SimulId, variant: chess.variant.Variant.LilaKey)
POST  /simul/$id<\w{8}>/withdraw              controllers.Simul.withdraw(id: SimulId)
GET   /$lang<\w\w\w?>/simul                   controllers.Simul.homeLang(lang: Language)

# Team API
GET   /api/team/all                           controllers.TeamApi.all(page: Int ?= 1)
GET   /api/team/search                        controllers.TeamApi.search(text ?= "", page: Int ?= 1)
GET   /api/team/of/:username                  controllers.TeamApi.teamsOf(username: UserStr)
GET   /api/team/:id                           controllers.TeamApi.show(id: TeamId)
GET   /api/team/:id/users                     controllers.TeamApi.users(id: TeamId)
GET   /api/team/:id/arena                     controllers.Tournament.byTeam(id: TeamId)
GET   /api/team/:id/swiss                     controllers.Swiss.byTeam(id: TeamId)
GET   /api/team/:id/requests                  controllers.TeamApi.requests(id: TeamId)
POST  /api/team/:id/update/:name			  controllers.TeamApi.update(id: TeamId, name: String)
POST  /api/team/:id/request/:userId/:decision controllers.TeamApi.requestProcess(id: TeamId, userId: UserStr, decision)
POST  /api/team/:id/kick/:user                controllers.TeamApi.kickUser(id: TeamId, user: UserStr)

# Analyse
POST  /$gameId<\w{8}>/request-analysis        controllers.Analyse.requestAnalysis(gameId: GameId)

GET   /game/export/$gameId<\w{8}>                             controllers.Game.exportOne(gameId: GameId)
GET   /game/export/$gameId<\w{8}>.pgn                         controllers.Game.exportOne(gameId: GameId)
GET   /game/export/png/$gameId<\w{8}>.png                     controllers.Export.legacyGameThumbnail(gameId: GameId, theme: Option[String], piece: Option[String])
GET   /game/export/gif/thumbnail/$gameId<\w{8}>.gif           controllers.Export.gameThumbnail(gameId: GameId, theme: Option[String], piece: Option[String])
GET   /game/export/gif/$gameId<\w{8}>.gif                     controllers.Export.gif(gameId: GameId, color: Color = Color.white, theme: Option[String], piece: Option[String])
GET   /game/export/gif/:color/$gameId<\w{8}>.gif              controllers.Export.gif(gameId: GameId, color: Color, theme: Option[String], piece: Option[String])
GET   /export/fen.gif                                         controllers.Export.fenThumbnail(fen, color: Option[Color], lastMove: Option[Uci], variant: Option[chess.variant.Variant.LilaKey], theme: Option[String], piece: Option[String])

# Fishnet
POST  /fishnet/acquire                        controllers.Fishnet.acquire(slow: Boolean ?= false)
POST  /fishnet/analysis/$workId<\w{8}>        controllers.Fishnet.analysis(workId, slow: Boolean ?= false, stop: Boolean ?= false)
POST  /fishnet/abort/$workId<\w{8}>           controllers.Fishnet.abort(workId)
GET   /fishnet/key/$key<\w{8}>                controllers.Fishnet.keyExists(key)
GET   /fishnet/status                         controllers.Fishnet.status

# Pref
POST  /pref/:name                             controllers.Pref.set(name)
GET   /account/preferences/network            controllers.Pref.network
POST  /account/preferences/network            controllers.Pref.networkPost
GET   /account/preferences/:categ             controllers.Pref.form(categ)
POST  /account/preferences                    controllers.Pref.formApply
POST  /account/preferences/notification       controllers.Pref.notifyFormApply

# Setup
POST  /setup/ai                        controllers.Setup.ai
POST  /setup/friend                    controllers.Setup.friend(user: Option[UserStr] ?= None)
POST  /setup/hook/:sri/like/:gameId    controllers.Setup.like(sri: Sri, gameId: GameId)
POST  /setup/hook/:sri                 controllers.Setup.hook(sri: Sri)
GET   /setup/filter                    controllers.Setup.filterForm
GET   /setup/validate-fen              controllers.Setup.validateFen

# Challenge
GET   /challenge                       controllers.Challenge.all
GET   /challenge/$id<\w{8}>            controllers.Challenge.show(id: ChallengeId, color: Option[Color] ?= None)
POST  /challenge/$id<\w{8}>/accept     controllers.Challenge.accept(id: ChallengeId, color: Option[Color] ?= None)
POST  /challenge/$id<\w{8}>/decline    controllers.Challenge.decline(id: ChallengeId)
POST  /challenge/$id<\w{8}>/cancel     controllers.Challenge.cancel(id: ChallengeId)
POST  /challenge/$id<\w{8}>/to-friend  controllers.Challenge.toFriend(id: ChallengeId)
POST  /challenge/rematch-of/$id<\w{8}> controllers.Challenge.offerRematchForGame(id: GameId)

# Notify
GET  /notify                           controllers.Notify.recent(page: Int ?= 1)
POST /notify/clear                     controllers.Notify.clear

# Video
GET   /video                                 controllers.Video.index
GET   /video/tags                            controllers.Video.tags
GET   /video/author/:author                  controllers.Video.author(author)
GET   /video/:id                             controllers.Video.show(id)
GET   /$lang<\w\w\w?>/video                  controllers.Video.indexLang(lang: Language)
GET   /$lang<\w\w\w?>/video/tags             controllers.Video.tagsLang(lang: Language)
GET   /$lang<\w\w\w?>/video/author/:author   controllers.Video.authorLang(lang: Language, author: String)
GET   /$lang<\w\w\w?>/video/:id              controllers.Video.showLang(lang: Language, id: String)

# I18n
POST  /translation/select              controllers.I18n.select

# Authentication
GET   /login                           controllers.Auth.login
POST  /login                           controllers.Auth.authenticate
POST  /login/class                     controllers.Auth.clasLogin
GET   /$lang<\w\w\w?>/login            controllers.Auth.loginLang(lang: Language)
GET   /logout                          controllers.Auth.logoutGet
POST  /logout                          controllers.Auth.logout
GET   /signup                          controllers.Auth.signup
POST  /signup                          controllers.Auth.signupPost
GET   /$lang<\w\w\w?>/signup           controllers.Auth.signupLang(lang: Language)
GET   /signup/check-your-email         controllers.Auth.checkYourEmail
POST  /signup/fix-email                controllers.Auth.fixEmail
GET   /signup/confirm/:token           controllers.Auth.signupConfirmEmail(token)
POST  /signup/confirm/:token           controllers.Auth.signupConfirmEmailPost(token)
GET   /password/reset                  controllers.Auth.passwordReset
POST  /password/reset/send             controllers.Auth.passwordResetApply
GET   /password/reset/sent/:email      controllers.Auth.passwordResetSent(email)
GET   /password/reset/confirm/:token   controllers.Auth.passwordResetConfirm(token)
POST  /password/reset/confirm/:token   controllers.Auth.passwordResetConfirmApply(token)
POST  /auth/set-fp/:fp/:ms             controllers.Auth.setFingerPrint(fp, ms: Int)
POST  /auth/token                      controllers.Auth.makeLoginToken
GET   /auth/token/:token               controllers.Auth.loginWithToken(token)
POST  /auth/token/:token               controllers.Auth.loginWithTokenPost(token)
GET   /auth/magic-link                 controllers.Auth.magicLink
POST  /auth/magic-link/send            controllers.Auth.magicLinkApply
GET   /auth/magic-link/sent            controllers.Auth.magicLinkSent
GET   /auth/check                      controllers.Auth.check

# Mod
POST  /mod/:username/alt/:v            controllers.Mod.alt(username: UserStr, v: Boolean)
POST  /mod/alt-many                    controllers.Mod.altMany
POST  /mod/:username/engine/:v         controllers.Mod.engine(username: UserStr, v: Boolean)
POST  /mod/:username/booster/:v        controllers.Mod.booster(username: UserStr, v: Boolean)
POST  /mod/:username/troll/:v          controllers.Mod.troll(username: UserStr, v: Boolean)
POST  /mod/:username/isolate/:v        controllers.Mod.isolate(username: UserStr, v: Boolean)
POST  /mod/:username/delete-pms-and-chats controllers.Mod.deletePmsAndChats(username: UserStr)
POST  /mod/:username/warn              controllers.Mod.warn(username: UserStr, subject)
POST  /mod/:username/kid               controllers.Mod.kid(username: UserStr, v: Boolean)
POST  /mod/:username/disable-2fa       controllers.Mod.disableTwoFactor(username: UserStr)
POST  /mod/:username/close             controllers.Mod.closeAccount(username: UserStr)
POST  /mod/:username/reopen            controllers.Mod.reopenAccount(username: UserStr)
POST  /mod/:username/title             controllers.Mod.setTitle(username: UserStr)
POST  /mod/:username/inquiry           controllers.Mod.spontaneousInquiry(username: UserStr)
GET   /mod/:username/communication     controllers.Mod.communicationPublic(username: UserStr)
GET   /mod/:username/communication/private controllers.Mod.communicationPrivate(username: UserStr)
POST  /mod/:username/communication/full-comms-export controllers.Mod.fullCommsExport(username: UserStr)
POST  /mod/:username/rankban/:v        controllers.Mod.rankban(username: UserStr, v: Boolean)
POST  /mod/:username/arenaban/:v       controllers.Mod.arenaBan(username: UserStr, v: Boolean)
POST  /mod/:username/prizeban/:v       controllers.Mod.prizeban(username: UserStr, v: Boolean)
POST  /mod/:username/reportban/:v      controllers.Mod.reportban(username: UserStr, v: Boolean)
POST  /mod/:username/blank-password    controllers.Mod.blankPassword(username: UserStr)
POST  /mod/:username/free-patron       controllers.Mod.freePatron(username: UserStr)
POST  /mod/:username/impersonate       controllers.Mod.impersonate(username: String)
GET   /mod/:username/games             controllers.GameMod.index(username: UserStr)
POST  /mod/:username/games             controllers.GameMod.post(username: UserStr)
GET   /mod/table                       controllers.Mod.table
GET   /mod/log                         controllers.Mod.log(mod: Option[UserStr] ?= None, id: Option[String] ?= None)
POST  /mod/:username/refreshUserAssess controllers.Mod.refreshUserAssess(username: UserStr)
POST  /mod/:username/email             controllers.Mod.setEmail(username: UserStr)
POST  /mod/inquiry-to-zulip            controllers.Mod.inquiryToZulip
POST  /mod/:username/create-name-close-vote controllers.Mod.createNameCloseVote(username: UserStr)
POST  /mod/:username/ask-usertable-check controllers.Mod.askUsertableCheck(username: UserStr)
GET   /mod/leaderboard                 controllers.Mod.gamify
GET   /mod/leaderboard/:period         controllers.Mod.gamifyPeriod(period)
GET   /mod/search                      controllers.Mod.search
GET   /mod/notes                       controllers.Mod.notes(page: Int ?= 1, q ?= "")
GET   /mod/chat-user/:username         controllers.Mod.chatUser(username: UserStr)
GET   /mod/:username/permissions       controllers.Mod.permissions(username: UserStr)
POST  /mod/:username/permissions       controllers.Mod.savePermissions(username: UserStr)
POST  /mod/:username/gdpr-erase        controllers.Mod.gdprErase(username: UserStr)
GET   /mod/public-chat                 controllers.Mod.publicChat
POST  /mod/public-chat/timeout         controllers.Mod.publicChatTimeout
GET   /mod/email-confirm               controllers.Mod.emailConfirmGet
POST  /mod/email-confirm               controllers.Mod.emailConfirmApi
GET   /mod/print/:fh                   controllers.Mod.print(fh)
POST  /mod/print/ban/:v/:fh            controllers.Mod.printBan(v: Boolean, fh)
GET   /mod/ip/:ip                      controllers.Mod.singleIp(ip)
POST  /mod/ip/ban/:v/:ip               controllers.Mod.singleIpBan(v: Boolean, ip)
GET   /mod/presets/:group              controllers.Mod.presets(group)
POST  /mod/presets/:group              controllers.Mod.presetsUpdate(group)
GET   /api/stream/mod                  controllers.Mod.eventStream
GET   /api/stream/mod-marked-since     controllers.Mod.markedUsersStream
GET   /mod/image/queue                 controllers.Mod.imageQueue(page: Int ?= 1)
POST  /mod/image/accept/:id            controllers.Mod.imageAccept(id: ImageId, v: Boolean)

# Irwin
GET   /irwin                           controllers.Irwin.dashboard
POST  /irwin/report                    controllers.Irwin.saveReport
GET   /api/stream/irwin                controllers.Irwin.eventStream

# Kaladin
GET   /kaladin                         controllers.Irwin.kaladin

# Forum
GET   /forum                           controllers.ForumCateg.index
GET   /forum/search                    controllers.ForumPost.search(text ?= "", page: Int ?= 1)
GET   /forum/:categId                  controllers.ForumCateg.show(categId: ForumCategId, page: Int ?= 1)
GET   /forum/:categId/form             controllers.ForumTopic.form(categId: ForumCategId)
POST  /forum/:categId/new              controllers.ForumTopic.create(categId: ForumCategId)
GET   /forum/:categId/mod-feed         controllers.ForumCateg.modFeed(categId: ForumCategId, page: Int ?= 1)
GET   /forum/participants/:topicId     controllers.ForumTopic.participants(topicId: ForumTopicId)
GET   /forum/:categId/:slug            controllers.ForumTopic.show(categId: ForumCategId, slug: ForumTopicSlug, page: Int ?= 1)
POST  /forum/:categId/:slug/close      controllers.ForumTopic.close(categId: ForumCategId, slug: ForumTopicSlug)
POST  /forum/:categId/:slug/sticky     controllers.ForumTopic.sticky(categId: ForumCategId, slug: ForumTopicSlug)
POST  /forum/:categId/:slug/new        controllers.ForumPost.create(categId: ForumCategId, slug: ForumTopicSlug, page: Int ?= 1)
POST  /forum/delete/:id                controllers.ForumPost.delete(id: ForumPostId)
POST  /forum/relocate/:id              controllers.ForumPost.relocate(id: ForumPostId)
POST  /forum/:categId/react/:id/:reaction/:v controllers.ForumPost.react(categId: ForumCategId, id: ForumPostId, reaction, v: Boolean)
POST  /forum/post/:id                  controllers.ForumPost.edit(id: ForumPostId)
GET   /forum/redirect/post/:id         controllers.ForumPost.redirect(id: ForumPostId)
POST  /diagnostic                      controllers.ForumTopic.diagnostic
POST  /diagnostic/clear/:slug          controllers.ForumTopic.clearDiagnostic(slug: ForumTopicSlug)

# Msg
GET   /inbox                           controllers.Msg.home(before: Option[Long] ?= None)
GET   /inbox/search                    controllers.Msg.search(q)
GET   /inbox/unread-count              controllers.Msg.unreadCount
GET   /inbox/:username                 controllers.Msg.convo(username: UserStr, before: Option[Long] ?= None)
DELETE /inbox/:username                controllers.Msg.convoDelete(username: UserStr)
# Msg API/compat
POST  /inbox/:username                 controllers.Msg.apiPost(username: UserStr)
POST /inbox/:username/delete           controllers.Msg.convoDelete(username: UserStr)

# Coach
GET   /coach                           controllers.Coach.all(page: Int ?= 1)
GET   /coach/edit                      controllers.Coach.edit
POST  /coach/edit                      controllers.Coach.editApply
POST  /upload/image/coach              controllers.Coach.pictureApply
GET   /coach/:username                 controllers.Coach.show(username: UserStr)
GET   /coach/:lang/:country/:order     controllers.Coach.search(lang: String, order, country: FlagCode, page: Int ?= 1)
GET   /$lang<\w\w\w?>/coach            controllers.Coach.homeLang(lang: Language)

# Paste
GET   /paste                           controllers.Importer.importGame
POST  /import                          controllers.Importer.sendGame
GET   /import/master/$id<\w{8}>/:color controllers.Importer.masterGame(id: GameId, color: Color)

# Edit
GET   /editor.json                     controllers.Editor.data
GET   /editor/*urlFen                  controllers.Editor.load(urlFen)
GET   /editor                          controllers.Editor.index

# Lichess Mobile
GET   /api/mobile/home                 controllers.Api.mobileHome
GET   /api/mobile/watch                controllers.Api.mobileWatch
GET   /api/mobile/profile/:username    controllers.Api.mobileProfile(username:  UserStr)
GET   /api/mobile/my-games             controllers.Api.mobileGames
GET   /api/mobile/following            controllers.Relation.apiMobileFollowing

->    /team                            team.Routes
->    /appeal                          appeal.Routes
->    /report                          report.Routes
->    /class                           clas.Routes

# Stats
GET    /stat/rating/distribution/:perf controllers.User.ratingDistribution(perf: PerfKey, username: Option[UserStr] ?= None)

# API
POST  /api/users                       controllers.Api.usersByIds
GET   /api/puzzle/daily                controllers.Puzzle.apiDaily
GET   /api/puzzle/activity             controllers.Puzzle.activity
GET   /api/puzzle/dashboard/$days<\d+> controllers.Puzzle.apiDashboard(days: Days)
GET   /api/puzzle/$id<\w{5}>           controllers.Puzzle.apiShow(id: PuzzleId)
GET   /api/puzzle/next                 controllers.Puzzle.apiNext
POST  /api/puzzle/vote-themes          controllers.Puzzle.apiBatchVoteThemes
GET   /api/puzzle/batch/:angle         controllers.Puzzle.apiBatchSelect(angle)
POST  /api/puzzle/batch/:angle         controllers.Puzzle.apiBatchSolve(angle)
GET   /api/puzzle/replay/$days<\d+>/:theme controllers.Puzzle.apiReplay(days: Days, theme)
GET   /api/user/:user/tournament/created controllers.UserTournament.apiTournamentsByOwner(user: UserStr, status: List[Int])
GET   /api/user/:user/tournament/played  controllers.UserTournament.apiTournamentsByPlayer(user: UserStr)
GET   /api/user/:user                  controllers.Api.user(user: UserStr)
GET   /api/user/:user/activity         controllers.Api.activity(user: UserStr)
GET   /api/user/:user/note             controllers.User.apiReadNote(user: UserStr)
POST  /api/user/:user/note             controllers.User.apiWriteNote(user: UserStr)
GET   /api/user/:user/rating-history   controllers.User.ratingHistory(user: UserStr)
GET   /api/user/:user/current-game     controllers.User.tvExport(user: UserStr)
GET   /api/user/:user/perf/:perfKey    controllers.Api.perfStat(user: UserStr, perfKey: PerfKey)
GET   /api/user/:user/mod-log          controllers.Mod.apiUserLog(user: UserStr)
GET   /api/game/:id                    controllers.Api.game(id: GameId)
GET   /api/game/:id/chat               controllers.Api.gameChat(id: GameId)
GET   /api/tournament                  controllers.Api.currentTournaments
GET   /api/tournament/$id<\w{8}>            controllers.Tournament.apiShow(id: TourId)
GET   /api/tournament/$id<\w{8}>/games      controllers.Api.tournamentGames(id: TourId)
GET   /api/tournament/$id<\w{8}>/results    controllers.Api.tournamentResults(id: TourId)
GET   /api/tournament/$id<\w{8}>/teams      controllers.Api.tournamentTeams(id: TourId)
POST  /api/tournament                       controllers.Tournament.apiCreate
POST  /api/tournament/$id<\w{8}>            controllers.Tournament.apiUpdate(id: TourId)
POST  /api/tournament/$id<\w{8}>/join       controllers.Tournament.apiJoin(id: TourId)
POST  /api/tournament/$id<\w{8}>/withdraw   controllers.Tournament.apiWithdraw(id: TourId)
POST  /api/tournament/$id<\w{8}>/terminate  controllers.Tournament.apiTerminate(id: TourId)
POST  /api/tournament/team-battle/:id  controllers.Tournament.apiTeamBattleUpdate(id: TourId)
POST  /api/swiss/new/:teamId           controllers.Swiss.apiCreate(teamId: TeamId)
POST  /api/swiss/$id<\w{8}>/edit       controllers.Swiss.apiUpdate(id: SwissId)
POST  /api/swiss/$id<\w{8}>/join       controllers.Swiss.join(id: SwissId)
POST  /api/swiss/$id<\w{8}>/withdraw   controllers.Swiss.withdraw(id: SwissId)
POST  /api/swiss/$id<\w{8}>/terminate  controllers.Swiss.apiTerminate(id: SwissId)
GET   /api/swiss/$id<\w{8}>            controllers.Swiss.apiShow(id: SwissId)
GET   /api/swiss/$id<\w{8}>/games      controllers.Api.swissGames(id: SwissId)
GET   /api/swiss/$id<\w{8}>/results    controllers.Api.swissResults(id: SwissId)
GET   /api/simul                       controllers.Simul.apiList
GET   /api/status                      controllers.Api.status
GET   /api/users/status                controllers.Api.usersStatus
GET   /api/crosstable/:u1/:u2          controllers.Api.crosstable(u1: UserStr, u2: UserStr)
POST  /api/stream/games-by-users       controllers.Api.gamesByUsersStream
GET   /api/stream/event                controllers.Api.eventStream
GET   /api/stream/games/by-oauth-origin controllers.Api.gamesByOauthOriginStream
POST  /api/stream/games/:streamId      controllers.Api.gamesByIdsStream(streamId)
POST  /api/stream/games/:streamId/add  controllers.Api.gamesByIdsStreamAddIds(streamId)
GET   /api/stream/game/:id             controllers.Api.moveStream(id: GameId)
GET   /api/account                     controllers.Account.apiMe
GET   /api/account/playing             controllers.Account.apiNowPlaying
GET   /api/account/email               controllers.Account.apiEmail
GET   /api/account/kid                 controllers.Account.apiKid
POST  /api/account/kid                 controllers.Account.apiKidPost
GET   /api/account/preferences         controllers.Pref.apiGet
POST  /api/account/preferences/:name   controllers.Pref.apiSet(name)
GET   /api/challenge                   controllers.Challenge.apiList
POST  /api/challenge/ai                controllers.Setup.apiAi
POST  /api/challenge/open              controllers.Challenge.openCreate
POST  /api/challenge/:user             controllers.Challenge.apiCreate(user: UserStr)
GET   /api/challenge/$id<\w{8}>/show   controllers.Challenge.apiShow(id: ChallengeId)
POST  /api/challenge/$id<\w{8}>/accept controllers.Challenge.apiAccept(id: ChallengeId, color: Option[Color] ?= None)
POST  /api/challenge/$id<\w{8}>/decline controllers.Challenge.apiDecline(id: ChallengeId)
POST  /api/challenge/$id<\w{8}>/cancel controllers.Challenge.apiCancel(id: ChallengeId)
POST  /api/challenge/$id<\w{8}>/start-clocks  controllers.Challenge.apiStartClocks(id: GameId)
POST  /api/round/$id<\w{8}>/add-time/:seconds controllers.Round.apiAddTime(id: GameId, seconds: Int)
GET   /api/cloud-eval                  controllers.Api.cloudEval
POST  /api/import                      controllers.Importer.apiSendGame
GET   /api/bulk-pairing                controllers.BulkPairing.list
POST  /api/bulk-pairing                controllers.BulkPairing.create
GET   /api/bulk-pairing/:id            controllers.BulkPairing.show(id)
DELETE /api/bulk-pairing/:id           controllers.BulkPairing.delete(id)
GET   /api/bulk-pairing/:id/games      controllers.BulkPairing.games(id)
POST  /api/bulk-pairing/:id/start-clocks  controllers.BulkPairing.startClocks(id)

GET   /api/games/user/:username        controllers.Game.apiExportByUser(username: UserStr)
GET   /api/games/export/imports        controllers.Game.apiExportByUserImportedGames()
GET   /api/games/export/bookmarks      controllers.Game.apiExportByUserBookmarks()

# External engine
GET   /api/external-engine             controllers.Analyse.externalEngineList
POST  /api/external-engine             controllers.Analyse.externalEngineCreate
GET   /api/external-engine/:id         controllers.Analyse.externalEngineShow(id)
PUT   /api/external-engine/:id         controllers.Analyse.externalEngineUpdate(id)
DELETE /api/external-engine/:id        controllers.Analyse.externalEngineDelete(id)

# Bot API
GET   /api/bot/game/stream/:id         controllers.PlayApi.botGameStream(id: GameId)
POST  /api/bot/game/:id/move/:uci      controllers.PlayApi.botMove(id: GameId, uci, offeringDraw: Option[Boolean] ?= None)
GET   /api/bot/online                  controllers.PlayApi.botOnlineApi
POST  /api/bot/*cmd                    controllers.PlayApi.botCommand(cmd)
GET   /api/bot/*cmd                    controllers.PlayApi.botCommandGet(cmd)
GET   /player/bots                     controllers.PlayApi.botOnline

# Board API
GET   /api/board/game/stream/:id        controllers.PlayApi.boardGameStream(id: GameId)
POST  /api/board/game/:id/move/:uci     controllers.PlayApi.boardMove(id: GameId, uci, offeringDraw: Option[Boolean] ?= None)
POST  /api/board/seek                   controllers.Setup.boardApiHook
DELETE /api/board/seek                  controllers.Setup.boardApiHookCancel
POST  /api/board/*cmd                   controllers.PlayApi.boardCommandPost(cmd)
GET   /api/board/*cmd                   controllers.PlayApi.boardCommandGet(cmd)

# Account
GET   /account/passwd                  controllers.Account.passwd
POST  /account/passwd                  controllers.Account.passwdApply
GET   /account/email                   controllers.Account.email
POST  /account/email                   controllers.Account.emailApply
GET   /contact/email-confirm/help      controllers.Account.emailConfirmHelp
GET   /account/email/confirm/:token    controllers.Account.emailConfirm(token)
GET   /account/close                   controllers.Account.close
POST  /account/close                   controllers.Account.closeConfirm
GET   /account/delete                  controllers.Account.delete
POST  /account/delete                  controllers.Account.deleteConfirm
GET   /account/delete/done             controllers.Account.deleteDone
GET   /account/profile                 controllers.Account.profile
POST  /account/profile                 controllers.Account.profileApply
GET   /account/username                controllers.Account.username
POST  /account/username                controllers.Account.usernameApply
GET   /account/kid                     controllers.Account.kid
POST  /account/kid                     controllers.Account.kidPost
GET   /account/twofactor               controllers.Account.twoFactor
POST  /account/twofactor/setup         controllers.Account.setupTwoFactor
POST  /account/twofactor/disable       controllers.Account.disableTwoFactor
GET   /account/reopen                  controllers.Account.reopen
POST  /account/reopen/send             controllers.Account.reopenApply
GET   /account/reopen/sent             controllers.Account.reopenSent
GET   /account/reopen/login/:token     controllers.Account.reopenLogin(token)
GET   /account/personal-data           controllers.Account.data

# App BC
GET   /account/security                controllers.Account.security
POST  /account/signout/:sessionId      controllers.Account.signout(sessionId)
GET   /account/now-playing             controllers.Account.nowPlaying

GET   /tutor                           controllers.Tutor.home()
GET   /tutor/:user                     controllers.Tutor.user(user: UserStr)
POST  /tutor/:user/compute             controllers.Tutor.compute(user: UserStr)
GET   /tutor/:user/:range              controllers.Tutor.report(user: UserStr, range)
POST  /tutor/:user/delete              controllers.Tutor.delete(user: UserStr, range)
GET   /tutor/:user/:range/:perf        controllers.Tutor.perf(user: UserStr, range, perf: PerfKey)
GET   /tutor/:user/:range/:perf/:angle controllers.Tutor.angle(user: UserStr, range, perf: PerfKey, angle)
GET   /tutor/:user/:range/:perf/opening/:color/:opening controllers.Tutor.opening(user: UserStr, range, perf: PerfKey, color: Color, opening: String)

# Recap
GET   /recap                           controllers.Recap.home
GET   /recap/:username                 controllers.Recap.user(username: UserStr)

# OAuth
GET    /oauth                          controllers.OAuth.authorize
POST   /oauth                          controllers.OAuth.legacyTokenApply
GET    /oauth/authorize                controllers.OAuth.legacyAuthorize
POST   /oauth/authorize                controllers.OAuth.authorizeApply
POST   /oauth/revoke-client            controllers.OAuth.revokeClient
POST   /api/token                      controllers.OAuth.tokenApply
DELETE /api/token                      controllers.OAuth.tokenRevoke
GET    /account/oauth/token            controllers.OAuthToken.index
GET    /account/oauth/token/create     controllers.OAuthToken.create
POST   /account/oauth/token/create     controllers.OAuthToken.createApply
POST   /account/oauth/token/:id/delete controllers.OAuthToken.delete(id)
POST   /api/token/admin-challenge      controllers.OAuth.challengeTokens
POST   /api/token/test                 controllers.OAuth.testTokens

# Events
GET   /event/$id<\w{8}>                controllers.Event.show(id)
GET   /event/manager                   controllers.Event.manager(page: Int ?= 1)
GET   /event/manager/$id<\w{8}>        controllers.Event.edit(id)
POST  /event/manager/$id<\w{8}>        controllers.Event.update(id)
GET   /event/manager/clone/$id<\w{8}>  controllers.Event.cloneE(id)
GET   /event/manager/new               controllers.Event.form
POST  /event/manager                   controllers.Event.create

GET   /cms                            controllers.Cms.index
GET   /cms/new                        controllers.Cms.createForm(key: Option[CmsPageKey])
POST  /cms/new                        controllers.Cms.create
GET   /cms/:id/edit                   controllers.Cms.edit(id: CmsPageId)
POST  /cms/:id/edit                   controllers.Cms.update(id: CmsPageId)
POST  /cms/:id/delete                 controllers.Cms.delete(id: CmsPageId)

# Misc
GET   /developers                      controllers.Main.webmasters
GET   /app                             controllers.Main.app
GET   /app-store                       controllers.Main.redirectToAppStore
GET   /swag                            controllers.Main.redirectToSwag
GET   /$lang<\w\w\w?>/app              controllers.Main.appLang(lang: Language)
GET   /lag                             controllers.Main.lag
GET   /get-fishnet                     controllers.Main.getFishnet
GET   /costs                           controllers.Main.costs
GET   /InstantChess.com                controllers.Main.instantChess
POST  /upload/image/user/:rel          controllers.Main.uploadImage(rel)
GET   /image-url/:id                   controllers.Main.imageUrl(id: ImageId, width: Int)
POST  /github/secret-scanning          controllers.Github.secretScanning

GET   /verify-title                    controllers.TitleVerify.index
GET   /verify-title/form               controllers.TitleVerify.form
POST  /verify-title/form               controllers.TitleVerify.create
GET   /verify-title/queue              controllers.TitleVerify.queue
GET   /verify-title/:id                controllers.TitleVerify.show(id: TitleRequestId)
POST  /verify-title/:id                controllers.TitleVerify.update(id: TitleRequestId)
POST  /verify-title/:id/cancel         controllers.TitleVerify.cancel(id: TitleRequestId)
POST  /verify-title/:id/process        controllers.TitleVerify.process(id: TitleRequestId)
POST  /upload/image/verify-title/:id   controllers.TitleVerify.image(id: TitleRequestId, tag: String)

# Technical
GET   /run/captcha/$id<\w{8}>          controllers.Main.captchaCheck(id: GameId)
GET   /run/temporarily-disabled        controllers.Main.temporarilyDisabled(path = "")
GET   /run/temporarily-disabled/*path  controllers.Main.temporarilyDisabled(path)
POST  /run/toggle-blind-mode           controllers.Main.toggleBlindMode
POST  /run/cli                         controllers.Dev.command

# Dev
GET   /dev/cli                         controllers.Dev.cli
POST  /dev/cli                         controllers.Dev.cliPost
GET   /dev/settings                    controllers.Dev.settings
POST  /dev/settings/:id                controllers.Dev.settingsPost(id)
GET   /dev/ip-tiers                    controllers.Dev.ipTiers
POST  /dev/ip-tiers                    controllers.Dev.ipTiersPost
GET   /dev/email-error                 controllers.Dev.emailErrorGet
POST  /dev/email-error                 controllers.Dev.emailErrorPost

GET   /prometheus-metrics/:key         controllers.Main.prometheusMetrics(key: String)

# Push
POST  /mobile/register/:platform/:deviceId controllers.Push.mobileRegister(platform, deviceId)
POST  /mobile/unregister               controllers.Push.mobileUnregister
POST  /push/subscribe                  controllers.Push.webSubscribe

# Pages
GET   /terms-of-service                controllers.Cms.tos
GET   /$key<privacy|thanks|about|ads|changelog> controllers.Cms.menuPage(key: CmsPageKey)
GET   /contact                         controllers.Main.contact
GET   /faq                             controllers.Main.faq
GET   /source                          controllers.Cms.source
GET   /qa/:id/:slug                    controllers.Main.legacyQaQuestion(id: Int, slug)
GET   /page/:key                       controllers.Cms.lonePage(key: CmsPageKey)

# Variants
GET   /variant                         controllers.Cms.variantHome
GET   /variant/:key                    controllers.Cms.variant(key: chess.variant.Variant.LilaKey)

# Help
GET   /help/contribute                 controllers.Cms.help
GET   /help/*path                      controllers.Main.helpPath(path)

# DGT
GET   /dgt                             controllers.DgtCtrl.index
GET   /dgt/play                        controllers.DgtCtrl.play
GET   /dgt/config                      controllers.DgtCtrl.config
POST  /dgt/config/token                controllers.DgtCtrl.generateToken

POST  /jslog/$id<\w{12}>               controllers.Main.jslog(id: GameFullId)

GET   /assets/_$v<\w{6}>/*file         controllers.Main.devAsset(v, path="public", file)
GET   /assets/*file                    controllers.ExternalAssets.at(path="public", file)

GET   /manifest.json                   controllers.Main.manifest
GET   /robots.txt                      controllers.Main.robots

GET   /mobile                          controllers.Main.mobile
GET   /$lang<\w\w\w?>/mobile           controllers.Main.mobileLang(lang: Language)

GET   /*path                           controllers.User.redirect(path)
