You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

home.go 28 kB

11 years ago
11 years ago
11 years ago
3 years ago
11 years ago
11 years ago
4 years ago
3 years ago
5 years ago
3 years ago
3 years ago
3 years ago
5 years ago
11 years ago
3 years ago
3 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
5 years ago
4 years ago
4 years ago
4 years ago
4 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
5 years ago
11 years ago
4 years ago
11 years ago
11 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
11 years ago
3 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
5 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package routers
  6. import (
  7. "bytes"
  8. "code.gitea.io/gitea/routers/response"
  9. "net/http"
  10. "strconv"
  11. "strings"
  12. "code.gitea.io/gitea/services/repository"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. code_indexer "code.gitea.io/gitea/modules/indexer/code"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/routers/user"
  22. )
  23. const (
  24. // tplHome home page template
  25. tplHome base.TplName = "home"
  26. // tplExploreRepos explore repositories page template
  27. tplExploreRepos base.TplName = "explore/repos"
  28. // tplExploreDataset explore datasets page template
  29. tplExploreDataset base.TplName = "explore/datasets"
  30. // tplExploreUsers explore users page template
  31. tplExploreUsers base.TplName = "explore/users"
  32. // tplExploreOrganizations explore organizations page template
  33. tplExploreOrganizations base.TplName = "explore/organizations"
  34. // tplExploreCode explore code page template
  35. tplExploreCode base.TplName = "explore/code"
  36. tplExploreImages base.TplName = "explore/images"
  37. tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis"
  38. tplHomeTerm base.TplName = "terms"
  39. tplHomePrivacy base.TplName = "privacy"
  40. tplResoruceDesc base.TplName = "resource_desc"
  41. )
  42. // Home render home page
  43. func Home(ctx *context.Context) {
  44. ctx.Data["PageIsHome"] = true
  45. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  46. setRecommendURL(ctx)
  47. ctx.HTML(200, tplHome)
  48. }
  49. func setRecommendURLOnly(ctx *context.Context) {
  50. addr := setting.RecommentRepoAddr[10:]
  51. start := strings.Index(addr, "/")
  52. end := strings.Index(addr, "raw")
  53. if start != -1 && end != -1 {
  54. ctx.Data["RecommendURL"] = addr[start:end]
  55. } else {
  56. ctx.Data["RecommendURL"] = setting.RecommentRepoAddr
  57. }
  58. }
  59. func setRecommendURL(ctx *context.Context) {
  60. setRecommendURLOnly(ctx)
  61. ctx.Data["page_title"] = ctx.Tr("home.page_title")
  62. ctx.Data["page_small_title"] = ctx.Tr("home.page_small_title")
  63. ctx.Data["page_description"] = ctx.Tr("home.page_description")
  64. ctx.Data["page_use"] = ctx.Tr("home.page_use")
  65. ctx.Data["page_only_dynamic"] = ctx.Tr("home.page_only_dynamic")
  66. ctx.Data["page_recommend_org"] = ctx.Tr("home.page_recommend_org")
  67. ctx.Data["page_recommend_org_desc"] = ctx.Tr("home.page_recommend_org_desc")
  68. ctx.Data["page_recommend_org_commit"] = ctx.Tr("home.page_recommend_org_commit")
  69. ctx.Data["page_recommend_org_more"] = ctx.Tr("home.page_recommend_org_more")
  70. ctx.Data["page_recommend_repo"] = ctx.Tr("home.page_recommend_repo")
  71. ctx.Data["page_recommend_repo_desc"] = ctx.Tr("home.page_recommend_repo_desc")
  72. ctx.Data["page_recommend_repo_commit"] = ctx.Tr("home.page_recommend_repo_commit")
  73. ctx.Data["page_recommend_repo_go"] = ctx.Tr("home.page_recommend_repo_go")
  74. ctx.Data["page_recommend_repo_more"] = ctx.Tr("home.page_recommend_repo_more")
  75. ctx.Data["page_dev_env"] = ctx.Tr("home.page_dev_env")
  76. ctx.Data["page_dev_env_desc"] = ctx.Tr("home.page_dev_env_desc")
  77. ctx.Data["page_dev_env_desc_title"] = ctx.Tr("home.page_dev_env_desc_title")
  78. ctx.Data["page_dev_env_desc_desc"] = ctx.Tr("home.page_dev_env_desc_desc")
  79. ctx.Data["page_dev_env_desc1_title"] = ctx.Tr("home.page_dev_env_desc1_title")
  80. ctx.Data["page_dev_env_desc1_desc"] = ctx.Tr("home.page_dev_env_desc1_desc")
  81. ctx.Data["page_dev_env_desc2_title"] = ctx.Tr("home.page_dev_env_desc2_title")
  82. ctx.Data["page_dev_env_desc2_desc"] = ctx.Tr("home.page_dev_env_desc2_desc")
  83. ctx.Data["page_dev_env_desc3_title"] = ctx.Tr("home.page_dev_env_desc3_title")
  84. ctx.Data["page_dev_env_desc3_desc"] = ctx.Tr("home.page_dev_env_desc3_desc")
  85. ctx.Data["page_dev_yunlao"] = ctx.Tr("home.page_dev_yunlao")
  86. ctx.Data["page_dev_yunlao_desc1"] = ctx.Tr("home.page_dev_yunlao_desc1")
  87. ctx.Data["page_dev_yunlao_desc2"] = ctx.Tr("home.page_dev_yunlao_desc2")
  88. ctx.Data["page_dev_yunlao_desc3"] = ctx.Tr("home.page_dev_yunlao_desc3")
  89. ctx.Data["page_dev_yunlao_desc4"] = ctx.Tr("home.page_dev_yunlao_desc4")
  90. ctx.Data["page_dev_yunlao_desc5"] = ctx.Tr("home.page_dev_yunlao_desc5")
  91. ctx.Data["page_dev_yunlao_apply"] = ctx.Tr("home.page_dev_yunlao_apply")
  92. ctx.Data["page_recommend_activity"] = ctx.Tr("home.page_recommend_activity")
  93. ctx.Data["page_recommend_activity_desc"] = ctx.Tr("home.page_recommend_activity_desc")
  94. }
  95. func Dashboard(ctx *context.Context) {
  96. if ctx.IsSigned {
  97. pictureInfo, err := getImageInfo("dashboard-picture")
  98. if err == nil && len(pictureInfo) > 0 {
  99. log.Info("set image info=" + pictureInfo[0]["url"])
  100. ctx.Data["image_url"] = pictureInfo[0]["url"]
  101. ctx.Data["image_link"] = pictureInfo[0]["image_link"]
  102. if len(pictureInfo) > 1 {
  103. ctx.Data["invite_image_url"] = pictureInfo[1]["url"]
  104. ctx.Data["invite_image_link"] = pictureInfo[1]["image_link"]
  105. }
  106. }
  107. if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
  108. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  109. ctx.HTML(200, user.TplActivate)
  110. } else if !ctx.User.IsActive || ctx.User.ProhibitLogin {
  111. log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
  112. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  113. ctx.HTML(200, "user/auth/prohibit_login")
  114. } else if ctx.User.MustChangePassword {
  115. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  116. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
  117. ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
  118. ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
  119. } else if setting.PhoneService.Enabled && ctx.User.IsActive && ctx.User.PhoneNumber == "" {
  120. ctx.Data["Title"] = ctx.Tr("phone.bind_phone")
  121. ctx.HTML(200, "user/auth/bind_phone")
  122. return
  123. } else {
  124. user.Dashboard(ctx)
  125. }
  126. return
  127. // Check non-logged users landing page.
  128. } else if setting.LandingPageURL != setting.LandingPageHome {
  129. ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
  130. return
  131. }
  132. // Check auto-login.
  133. uname := ctx.GetCookie(setting.CookieUserName)
  134. if len(uname) != 0 {
  135. ctx.Redirect(setting.AppSubURL + "/user/login")
  136. return
  137. }
  138. setRecommendURL(ctx)
  139. ctx.Data["PageIsHome"] = true
  140. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  141. ctx.HTML(200, tplHome)
  142. }
  143. // RepoSearchOptions when calling search repositories
  144. type RepoSearchOptions struct {
  145. OwnerID int64
  146. Private bool
  147. Restricted bool
  148. PageSize int
  149. TplName base.TplName
  150. Course util.OptionalBool
  151. }
  152. var (
  153. nullByte = []byte{0x00}
  154. )
  155. func isKeywordValid(keyword string) bool {
  156. return !bytes.Contains([]byte(keyword), nullByte)
  157. }
  158. // RenderRepoSearch render repositories search page
  159. func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
  160. page := ctx.QueryInt("page")
  161. if page <= 0 {
  162. page = 1
  163. }
  164. var (
  165. repos []*models.Repository
  166. count int64
  167. err error
  168. orderBy models.SearchOrderBy
  169. )
  170. ctx.Data["SortType"] = ctx.Query("sort")
  171. switch ctx.Query("sort") {
  172. case "newest":
  173. orderBy = models.SearchOrderByNewest
  174. case "oldest":
  175. orderBy = models.SearchOrderByOldest
  176. case "recentupdate":
  177. orderBy = models.SearchOrderByRecentUpdated
  178. case "leastupdate":
  179. orderBy = models.SearchOrderByLeastUpdated
  180. case "reversealphabetically":
  181. orderBy = models.SearchOrderByAlphabeticallyReverse
  182. case "alphabetically":
  183. orderBy = models.SearchOrderByAlphabetically
  184. case "reversesize":
  185. orderBy = models.SearchOrderBySizeReverse
  186. case "size":
  187. orderBy = models.SearchOrderBySize
  188. case "moststars":
  189. orderBy = models.SearchOrderByStarsReverse
  190. case "feweststars":
  191. orderBy = models.SearchOrderByStars
  192. case "mostforks":
  193. orderBy = models.SearchOrderByForksReverse
  194. case "fewestforks":
  195. orderBy = models.SearchOrderByForks
  196. case "hot":
  197. orderBy = models.SearchOrderByHot
  198. case "active":
  199. orderBy = models.SearchOrderByActive
  200. default:
  201. ctx.Data["SortType"] = "hot"
  202. orderBy = models.SearchOrderByHot
  203. }
  204. orderBy = orderBy + ",id"
  205. //todo:support other topics
  206. keyword := strings.Trim(ctx.Query("q"), " ")
  207. topic := strings.Trim(ctx.Query("topic"), " ")
  208. repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
  209. ListOptions: models.ListOptions{
  210. Page: page,
  211. PageSize: opts.PageSize,
  212. },
  213. Actor: ctx.User,
  214. OrderBy: orderBy,
  215. Private: opts.Private,
  216. Keyword: keyword,
  217. OwnerID: opts.OwnerID,
  218. AllPublic: true,
  219. AllLimited: true,
  220. TopicName: topic,
  221. IncludeDescription: setting.UI.SearchRepoDescription,
  222. Course: opts.Course,
  223. })
  224. if err != nil {
  225. ctx.ServerError("SearchRepository", err)
  226. return
  227. }
  228. for _, repo := range repos {
  229. repo.Hot = int64(repo.NumWatches) + int64(repo.NumStars) + int64(repo.NumForks) + int64(repo.CloneCnt)
  230. repo.Active = int64(repo.NumIssues) + int64(repo.NumPulls) + int64(repo.NumCommit)
  231. }
  232. ctx.Data["Keyword"] = keyword
  233. ctx.Data["Topic"] = topic
  234. ctx.Data["Total"] = count
  235. ctx.Data["Repos"] = repos
  236. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  237. pager := context.NewPagination(int(count), opts.PageSize, page, 5)
  238. pager.SetDefaultParams(ctx)
  239. pager.AddParam(ctx, "topic", "TopicOnly")
  240. ctx.Data["Page"] = pager
  241. recommendOrgs, err := models.GetRecommendOrgInfos()
  242. if err != nil {
  243. log.Error("GetRecommendOrgInfos failed:%v", err.Error(), ctx.Data["MsgID"])
  244. ctx.ServerError("GetRecommendOrgInfos", err)
  245. return
  246. }
  247. ctx.Data["RecommendOrgs"] = recommendOrgs
  248. ctx.HTML(http.StatusOK, opts.TplName)
  249. }
  250. // ExploreRepos render explore repositories page
  251. func ExploreRepos(ctx *context.Context) {
  252. ctx.Data["Title"] = ctx.Tr("explore")
  253. ctx.Data["PageIsExplore"] = true
  254. ctx.Data["PageIsExploreRepositories"] = true
  255. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  256. pictureInfo, err := getImageInfo("explore-user-picture")
  257. if err == nil && len(pictureInfo) > 0 {
  258. ctx.Data["image_url"] = pictureInfo[0]["url"]
  259. ctx.Data["image_link"] = pictureInfo[0]["image_link"]
  260. }
  261. var ownerID int64
  262. if ctx.User != nil && !ctx.User.IsAdmin {
  263. ownerID = ctx.User.ID
  264. }
  265. RenderRepoSearch(ctx, &RepoSearchOptions{
  266. PageSize: setting.UI.ExplorePagingNum,
  267. OwnerID: ownerID,
  268. Private: ctx.User != nil,
  269. TplName: tplExploreRepos,
  270. })
  271. }
  272. func RepoSquare(ctx *context.Context) {
  273. var result []*models.Repository4Card
  274. var err error
  275. switch ctx.Query("type") {
  276. case "preferred":
  277. result, err = repository.GetPreferredRepos()
  278. case "incubation":
  279. result, err = repository.GetIncubationRepos()
  280. case "hot-paper":
  281. result, err = repository.GetHotPaperRepos()
  282. default:
  283. result, err = repository.GetPreferredRepos()
  284. }
  285. if err != nil {
  286. ctx.JSON(http.StatusOK, response.ResponseError(err))
  287. return
  288. }
  289. resultMap := make(map[string]interface{}, 0)
  290. resultMap["Repos"] = result
  291. ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
  292. }
  293. func ActiveUser(ctx *context.Context) {
  294. var err error
  295. var currentUserId int64
  296. if ctx.User != nil {
  297. currentUserId = ctx.User.ID
  298. }
  299. result, err := repository.GetActiveUser4Square(currentUserId)
  300. if err != nil {
  301. log.Error("ActiveUser err. %v", err)
  302. ctx.JSON(http.StatusOK, response.Success())
  303. return
  304. }
  305. resultMap := make(map[string]interface{}, 0)
  306. resultMap["Users"] = result
  307. ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
  308. }
  309. func ActiveOrg(ctx *context.Context) {
  310. result, err := repository.GetActiveOrgs()
  311. if err != nil {
  312. log.Error("ActiveOrg err. %v", err)
  313. ctx.JSON(http.StatusOK, response.Success())
  314. return
  315. }
  316. resultMap := make(map[string]interface{}, 0)
  317. resultMap["Orgs"] = result
  318. ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
  319. }
  320. func RepoFind(ctx *context.Context) {
  321. keyword := strings.Trim(ctx.Query("q"), " ")
  322. topic := strings.Trim(ctx.Query("topic"), " ")
  323. sort := strings.Trim(ctx.Query("sort"), " ")
  324. page := ctx.QueryInt("page")
  325. pageSize := models.SelectedPageSize(ctx.QueryInt("pageSize"))
  326. if !pageSize.IsLegal() {
  327. ctx.JSON(http.StatusOK, response.ServerError("pageSize illegal"))
  328. return
  329. }
  330. if page <= 0 {
  331. page = 1
  332. }
  333. var ownerID int64
  334. if ctx.User != nil && !ctx.User.IsAdmin {
  335. ownerID = ctx.User.ID
  336. }
  337. result, err := repository.FindRepos(repository.FindReposOptions{
  338. ListOptions: models.ListOptions{Page: page, PageSize: pageSize.Int()},
  339. Actor: ctx.User,
  340. Sort: sort,
  341. Keyword: keyword,
  342. Topic: topic,
  343. Private: ctx.User != nil,
  344. OwnerID: ownerID,
  345. })
  346. if err != nil {
  347. log.Error("RepoFind error. %v", err)
  348. ctx.JSON(http.StatusOK, response.ResponseError(err))
  349. return
  350. }
  351. ctx.JSON(http.StatusOK, response.SuccessWithData(result))
  352. }
  353. func ExploreDatasets(ctx *context.Context) {
  354. ctx.Data["Title"] = ctx.Tr("explore")
  355. ctx.Data["PageIsExplore"] = true
  356. ctx.Data["PageIsExploreDatasets"] = true
  357. // ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  358. var (
  359. datasets []*models.Dataset
  360. count int64
  361. err error
  362. orderBy models.SearchOrderBy
  363. )
  364. page := ctx.QueryInt("page")
  365. if page <= 0 {
  366. page = 1
  367. }
  368. ctx.Data["SortType"] = ctx.Query("sort")
  369. switch ctx.Query("sort") {
  370. case "newest":
  371. orderBy = models.SearchOrderByNewest
  372. case "oldest":
  373. orderBy = models.SearchOrderByOldest
  374. case "recentupdate":
  375. orderBy = models.SearchOrderByRecentUpdated
  376. case "leastupdate":
  377. orderBy = models.SearchOrderByLeastUpdated
  378. case "reversealphabetically":
  379. orderBy = models.SearchOrderByAlphabeticallyReverse
  380. case "alphabetically":
  381. orderBy = models.SearchOrderByAlphabetically
  382. case "reversesize":
  383. orderBy = models.SearchOrderBySizeReverse
  384. case "downloadtimes":
  385. orderBy = models.SearchOrderByDownloadTimes
  386. case "moststars":
  387. orderBy = models.SearchOrderByStarsReverse
  388. case "feweststars":
  389. orderBy = models.SearchOrderByStars
  390. case "mostusecount":
  391. orderBy = models.SearchOrderByUseCountReverse
  392. case "fewestusecount":
  393. orderBy = models.SearchOrderByUseCount
  394. case "default":
  395. orderBy = models.SearchOrderByDefault
  396. default:
  397. ctx.Data["SortType"] = "default"
  398. orderBy = models.SearchOrderByDefault
  399. }
  400. keyword := strings.Trim(ctx.Query("q"), " ")
  401. category := ctx.Query("category")
  402. task := ctx.Query("task")
  403. license := ctx.Query("license")
  404. var ownerID int64
  405. if ctx.User != nil && !ctx.User.IsAdmin {
  406. ownerID = ctx.User.ID
  407. }
  408. var datasetsIds []int64
  409. if ownerID > 0 {
  410. collaboratorDatasetsIds := models.GetCollaboratorDatasetIdsByUserID(ownerID)
  411. teamDatasetsIds := models.GetTeamDatasetIdsByUserID(ownerID)
  412. datasetsIds = append(collaboratorDatasetsIds, teamDatasetsIds...)
  413. }
  414. opts := &models.SearchDatasetOptions{
  415. Keyword: keyword,
  416. IncludePublic: true,
  417. SearchOrderBy: orderBy,
  418. Category: category,
  419. Task: task,
  420. License: license,
  421. OwnerID: ownerID,
  422. DatasetIDs: datasetsIds,
  423. RecommendOnly: ctx.QueryBool("recommend"),
  424. CloudBrainType: -1,
  425. ListOptions: models.ListOptions{
  426. Page: page,
  427. PageSize: 30,
  428. },
  429. }
  430. datasets, count, err = models.SearchDataset(opts)
  431. if err != nil {
  432. ctx.ServerError("SearchDatasets", err)
  433. return
  434. }
  435. pager := context.NewPagination(int(count), opts.PageSize, page, 5)
  436. ctx.Data["Keyword"] = opts.Keyword
  437. ctx.Data["Category"] = category
  438. ctx.Data["Task"] = task
  439. ctx.Data["License"] = license
  440. ctx.Data["Recommend"] = ctx.QueryBool("recommend")
  441. pager.SetDefaultParams(ctx)
  442. ctx.Data["Page"] = pager
  443. ctx.Data["Datasets"] = repository.ConvertToDatasetWithStar(ctx, datasets)
  444. ctx.Data["Total"] = count
  445. ctx.Data["PageIsDatasets"] = true
  446. ctx.HTML(200, tplExploreDataset)
  447. }
  448. // RenderUserSearch render user search page
  449. func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplName base.TplName) {
  450. opts.Page = ctx.QueryInt("page")
  451. if opts.Page <= 1 {
  452. opts.Page = 1
  453. }
  454. var (
  455. users []*models.User
  456. count int64
  457. err error
  458. orderBy models.SearchOrderBy
  459. )
  460. ctx.Data["SortType"] = ctx.Query("sort")
  461. switch ctx.Query("sort") {
  462. case "newest":
  463. orderBy = models.SearchOrderByIDReverse
  464. case "oldest":
  465. orderBy = models.SearchOrderByID
  466. case "recentupdate":
  467. orderBy = models.SearchOrderByRecentUpdated
  468. case "leastupdate":
  469. orderBy = models.SearchOrderByLeastUpdated
  470. case "reversealphabetically":
  471. orderBy = models.SearchOrderByAlphabeticallyReverse
  472. case "alphabetically":
  473. orderBy = models.SearchOrderByAlphabetically
  474. default:
  475. ctx.Data["SortType"] = "alphabetically"
  476. orderBy = models.SearchOrderByAlphabetically
  477. }
  478. opts.Keyword = strings.Trim(ctx.Query("q"), " ")
  479. opts.OrderBy = orderBy
  480. if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
  481. users, count, err = models.SearchUsers(opts)
  482. if err != nil {
  483. ctx.ServerError("SearchUsers", err)
  484. return
  485. }
  486. }
  487. ctx.Data["Keyword"] = opts.Keyword
  488. ctx.Data["Total"] = count
  489. ctx.Data["Users"] = users
  490. ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
  491. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  492. pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
  493. pager.SetDefaultParams(ctx)
  494. ctx.Data["Page"] = pager
  495. ctx.HTML(200, tplName)
  496. }
  497. // ExploreUsers render explore users page
  498. func ExploreUsers(ctx *context.Context) {
  499. ctx.Data["Title"] = ctx.Tr("explore")
  500. ctx.Data["PageIsExplore"] = true
  501. ctx.Data["PageIsExploreUsers"] = true
  502. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  503. pictureInfo, err := getImageInfo("explore-user-picture")
  504. if err == nil && len(pictureInfo) > 0 {
  505. ctx.Data["image_url"] = pictureInfo[0]["url"]
  506. ctx.Data["image_link"] = pictureInfo[0]["image_link"]
  507. }
  508. RenderUserSearch(ctx, &models.SearchUserOptions{
  509. Actor: ctx.User,
  510. Type: models.UserTypeIndividual,
  511. ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum},
  512. IsActive: util.OptionalBoolTrue,
  513. Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
  514. }, tplExploreUsers)
  515. }
  516. // ExploreOrganizations render explore organizations page
  517. func ExploreOrganizations(ctx *context.Context) {
  518. ctx.Data["Title"] = ctx.Tr("explore")
  519. ctx.Data["PageIsExplore"] = true
  520. ctx.Data["PageIsExploreOrganizations"] = true
  521. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  522. N := 10
  523. starInfo, err := models.FindTopNStarsOrgs(N)
  524. if err != nil {
  525. log.Error("GetStarOrgInfos failed:%v", err.Error(), ctx.Data["MsgID"])
  526. ctx.ServerError("GetStarOrgInfos", err)
  527. return
  528. }
  529. memberInfo, err := models.FindTopNMembersOrgs(N)
  530. if err != nil {
  531. log.Error("GetMemberOrgInfos failed:%v", err.Error(), ctx.Data["MsgID"])
  532. ctx.ServerError("GetMemberOrgInfos", err)
  533. return
  534. }
  535. openIInfo, err := models.FindTopNOpenIOrgs(N)
  536. if err != nil {
  537. log.Error("GetOpenIOrgInfos failed:%v", err.Error(), ctx.Data["MsgID"])
  538. ctx.ServerError("GetOpenIOrgInfos", err)
  539. return
  540. }
  541. recommendOrgs, err := getRecommendOrg()
  542. if err != nil {
  543. log.Error("GetRecommendOrgInfos failed:%v", err.Error(), ctx.Data["MsgID"])
  544. ctx.ServerError("GetRecommendOrgInfos", err)
  545. return
  546. }
  547. setRecommendURLOnly(ctx)
  548. ctx.Data["RecommendOrgs"] = recommendOrgs
  549. ctx.Data["StarOrgs"] = starInfo
  550. ctx.Data["MemberOrgs"] = memberInfo
  551. ctx.Data["ActiveOrgs"] = openIInfo
  552. ctx.HTML(http.StatusOK, tplExploreOrganizations)
  553. }
  554. // ExploreCode render explore code page
  555. func ExploreCode(ctx *context.Context) {
  556. if !setting.Indexer.RepoIndexerEnabled {
  557. ctx.Redirect(setting.AppSubURL+"/explore", 302)
  558. return
  559. }
  560. ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  561. ctx.Data["Title"] = ctx.Tr("explore")
  562. ctx.Data["PageIsExplore"] = true
  563. ctx.Data["PageIsExploreCode"] = true
  564. language := strings.TrimSpace(ctx.Query("l"))
  565. keyword := strings.TrimSpace(ctx.Query("q"))
  566. page := ctx.QueryInt("page")
  567. if page <= 0 {
  568. page = 1
  569. }
  570. var (
  571. repoIDs []int64
  572. err error
  573. isAdmin bool
  574. userID int64
  575. )
  576. if ctx.User != nil {
  577. userID = ctx.User.ID
  578. isAdmin = ctx.User.IsAdmin
  579. }
  580. // guest user or non-admin user
  581. if ctx.User == nil || !isAdmin {
  582. repoIDs, err = models.FindUserAccessibleRepoIDs(ctx.User)
  583. if err != nil {
  584. ctx.ServerError("SearchResults", err)
  585. return
  586. }
  587. }
  588. var (
  589. total int
  590. searchResults []*code_indexer.Result
  591. searchResultLanguages []*code_indexer.SearchResultLanguages
  592. )
  593. // if non-admin login user, we need check UnitTypeCode at first
  594. if ctx.User != nil && len(repoIDs) > 0 {
  595. repoMaps, err := models.GetRepositoriesMapByIDs(repoIDs)
  596. if err != nil {
  597. ctx.ServerError("SearchResults", err)
  598. return
  599. }
  600. var rightRepoMap = make(map[int64]*models.Repository, len(repoMaps))
  601. repoIDs = make([]int64, 0, len(repoMaps))
  602. for id, repo := range repoMaps {
  603. if repo.CheckUnitUser(userID, isAdmin, models.UnitTypeCode) {
  604. rightRepoMap[id] = repo
  605. repoIDs = append(repoIDs, id)
  606. }
  607. }
  608. ctx.Data["RepoMaps"] = rightRepoMap
  609. total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum)
  610. if err != nil {
  611. ctx.ServerError("SearchResults", err)
  612. return
  613. }
  614. // if non-login user or isAdmin, no need to check UnitTypeCode
  615. } else if (ctx.User == nil && len(repoIDs) > 0) || isAdmin {
  616. total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum)
  617. if err != nil {
  618. ctx.ServerError("SearchResults", err)
  619. return
  620. }
  621. var loadRepoIDs = make([]int64, 0, len(searchResults))
  622. for _, result := range searchResults {
  623. var find bool
  624. for _, id := range loadRepoIDs {
  625. if id == result.RepoID {
  626. find = true
  627. break
  628. }
  629. }
  630. if !find {
  631. loadRepoIDs = append(loadRepoIDs, result.RepoID)
  632. }
  633. }
  634. repoMaps, err := models.GetRepositoriesMapByIDs(loadRepoIDs)
  635. if err != nil {
  636. ctx.ServerError("SearchResults", err)
  637. return
  638. }
  639. ctx.Data["RepoMaps"] = repoMaps
  640. }
  641. ctx.Data["Keyword"] = keyword
  642. ctx.Data["Language"] = language
  643. ctx.Data["SearchResults"] = searchResults
  644. ctx.Data["SearchResultLanguages"] = searchResultLanguages
  645. ctx.Data["RequireHighlightJS"] = true
  646. ctx.Data["PageIsViewCode"] = true
  647. pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5)
  648. pager.SetDefaultParams(ctx)
  649. pager.AddParam(ctx, "l", "Language")
  650. ctx.Data["Page"] = pager
  651. ctx.HTML(200, tplExploreCode)
  652. }
  653. func ExploreImages(ctx *context.Context) {
  654. ctx.HTML(200, tplExploreImages)
  655. }
  656. func ExploreDataAnalysisUserTrend(ctx *context.Context) {
  657. ctx.Data["url_params"] = "UserTrend"
  658. ctx.HTML(200, tplExploreExploreDataAnalysis)
  659. }
  660. func ExploreDataAnalysisUserAnalysis(ctx *context.Context) {
  661. ctx.Data["url_params"] = "UserAnalysis"
  662. ctx.HTML(200, tplExploreExploreDataAnalysis)
  663. }
  664. func ExploreDataAnalysisProTrend(ctx *context.Context) {
  665. ctx.Data["url_params"] = "ProTrend"
  666. ctx.HTML(200, tplExploreExploreDataAnalysis)
  667. }
  668. func ExploreDataAnalysisProAnalysis(ctx *context.Context) {
  669. ctx.Data["url_params"] = "ProAnalysis"
  670. ctx.HTML(200, tplExploreExploreDataAnalysis)
  671. }
  672. func ExploreDataAnalysisOverview(ctx *context.Context) {
  673. ctx.Data["url_params"] = "Overview"
  674. ctx.HTML(200, tplExploreExploreDataAnalysis)
  675. }
  676. func ExploreDataAnalysisBrainAnalysis(ctx *context.Context) {
  677. ctx.Data["url_params"] = "BrainAnalysis"
  678. ctx.HTML(200, tplExploreExploreDataAnalysis)
  679. }
  680. func ExploreDataAnalysis(ctx *context.Context) {
  681. ctx.Data["url_params"] = ""
  682. ctx.HTML(200, tplExploreExploreDataAnalysis)
  683. }
  684. // NotFound render 404 page
  685. func NotFound(ctx *context.Context) {
  686. ctx.Data["Title"] = "Page Not Found"
  687. ctx.NotFound("home.NotFound", nil)
  688. }
  689. func getRecommendOrg() ([]map[string]interface{}, error) {
  690. url := setting.RecommentRepoAddr + "organizations"
  691. result, err := repository.RecommendFromPromote(url)
  692. if err != nil {
  693. return nil, err
  694. }
  695. names := make([]string, 0)
  696. for _, userName := range result {
  697. names = append(names, userName)
  698. }
  699. users, _ := models.GetUsersByNames(names)
  700. userMap := make(map[string]*models.User, 0)
  701. for _, user := range users {
  702. userMap[user.Name] = user
  703. }
  704. resultOrg := make([]map[string]interface{}, 0)
  705. for _, userName := range result {
  706. user := userMap[userName]
  707. if user != nil {
  708. userMap := make(map[string]interface{})
  709. userMap["Name"] = user.Name
  710. userMap["Description"] = user.Description
  711. userMap["FullName"] = user.FullName
  712. userMap["HomeLink"] = user.HomeLink()
  713. userMap["ID"] = user.ID
  714. userMap["Avatar"] = user.RelAvatarLink()
  715. userMap["NumRepos"] = user.NumRepos
  716. userMap["NumTeams"] = user.NumTeams
  717. userMap["NumMembers"] = user.NumMembers
  718. resultOrg = append(resultOrg, userMap)
  719. } else {
  720. log.Info("the user not exist," + userName)
  721. }
  722. }
  723. return resultOrg, nil
  724. }
  725. func getImageInfo(filename string) ([]map[string]string, error) {
  726. url := setting.RecommentRepoAddr + filename
  727. result, err := repository.RecommendFromPromote(url)
  728. if err != nil {
  729. return nil, err
  730. }
  731. imageInfo := make([]map[string]string, 0)
  732. for i := 0; i < (len(result) - 1); i++ {
  733. line := result[i]
  734. imageMap := make(map[string]string)
  735. if line[0:4] == "url=" {
  736. url := line[4:]
  737. imageMap["url"] = url
  738. if result[i+1][0:11] == "image_link=" {
  739. image_link := result[i+1][11:]
  740. imageMap["image_link"] = image_link
  741. }
  742. }
  743. imageInfo = append(imageInfo, imageMap)
  744. i = i + 1
  745. }
  746. return imageInfo, nil
  747. }
  748. func GetMapInfo(ctx *context.Context) {
  749. filename := ctx.Query("filename")
  750. url := setting.RecommentRepoAddr + filename
  751. result, err := repository.RecommendContentFromPromote(url)
  752. if err != nil {
  753. log.Info("get file error:" + err.Error())
  754. }
  755. ctx.JSON(http.StatusOK, result)
  756. }
  757. func GetRankUser(index string) ([]map[string]interface{}, error) {
  758. url := setting.RecommentRepoAddr + "user_rank_" + index
  759. result, err := repository.RecommendFromPromote(url)
  760. if err != nil {
  761. return nil, err
  762. }
  763. resultOrg := make([]map[string]interface{}, 0)
  764. for _, userRank := range result {
  765. tmpIndex := strings.Index(userRank, " ")
  766. userName := userRank
  767. score := 0
  768. if tmpIndex != -1 {
  769. userName = userRank[0:tmpIndex]
  770. tmpScore, err := strconv.Atoi(userRank[tmpIndex+1:])
  771. if err != nil {
  772. log.Info("convert to int error.")
  773. }
  774. score = tmpScore
  775. }
  776. user, err := models.GetUserByName(userName)
  777. if err == nil {
  778. userMap := make(map[string]interface{})
  779. userMap["Name"] = user.Name
  780. userMap["Description"] = user.Description
  781. userMap["FullName"] = user.FullName
  782. userMap["HomeLink"] = user.HomeLink()
  783. userMap["ID"] = user.ID
  784. userMap["Avatar"] = user.RelAvatarLink()
  785. userMap["Score"] = score
  786. resultOrg = append(resultOrg, userMap)
  787. } else {
  788. log.Info("query user error," + err.Error())
  789. }
  790. }
  791. return resultOrg, nil
  792. }
  793. func GetUserRankFromPromote(ctx *context.Context) {
  794. index := ctx.Params("index")
  795. resultUserRank, err := GetRankUser(index)
  796. if err != nil {
  797. ctx.ServerError("500", err)
  798. return
  799. }
  800. ctx.JSON(200, resultUserRank)
  801. }
  802. func RecommendHomeInfo(ctx *context.Context) {
  803. resultOrg, err := getRecommendOrg()
  804. if err != nil {
  805. log.Info("error." + err.Error())
  806. }
  807. resultRepo, err := repository.GetRecommendRepoFromPromote("projects")
  808. if err != nil {
  809. log.Info("error." + err.Error())
  810. }
  811. resultImage, err := getImageInfo("picture_info")
  812. if err != nil {
  813. log.Info("error." + err.Error())
  814. }
  815. mapInterface := make(map[string]interface{})
  816. mapInterface["org"] = resultOrg
  817. mapInterface["repo"] = resultRepo
  818. mapInterface["image"] = resultImage
  819. //mapInterface["cloudbrain"] = resultCloudBrain
  820. ctx.JSON(http.StatusOK, mapInterface)
  821. }
  822. func HomeTerm(ctx *context.Context) {
  823. ctx.HTML(200, tplHomeTerm)
  824. }
  825. func HomePrivacy(ctx *context.Context) {
  826. ctx.HTML(200, tplHomePrivacy)
  827. }
  828. func HomeResoruceDesc(ctx *context.Context) {
  829. ctx.HTML(200, tplResoruceDesc)
  830. }