#1692 APIs: Users Followers
- User profile un/follow - List user's followers/following
This commit is contained in:
parent
c62a6b7a12
commit
a49af93faf
27 changed files with 641 additions and 271 deletions
|
@ -13,7 +13,7 @@ watch_dirs = [
|
||||||
watch_exts = [".go"]
|
watch_exts = [".go"]
|
||||||
build_delay = 1500
|
build_delay = 1500
|
||||||
cmds = [
|
cmds = [
|
||||||
["go", "install", "-race"], # sqlite redis memcache cert pam tidb
|
["go", "install", "-v", "-race"], # sqlite redis memcache cert pam tidb
|
||||||
["go", "build", "-race"],
|
["go", "build", "-race"],
|
||||||
["./gogs", "web"]
|
["./gogs", "web"]
|
||||||
]
|
]
|
2
Makefile
2
Makefile
|
@ -16,7 +16,7 @@ NOW = $(shell date -u '+%Y%m%d%I%M%S')
|
||||||
.IGNORE: public/css/gogs.css
|
.IGNORE: public/css/gogs.css
|
||||||
|
|
||||||
build: $(GENERATED)
|
build: $(GENERATED)
|
||||||
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
go install -v -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||||
cp '$(GOPATH)/bin/gogs' .
|
cp '$(GOPATH)/bin/gogs' .
|
||||||
|
|
||||||
govet:
|
govet:
|
||||||
|
|
|
@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
|
||||||
|
|
||||||
![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
|
![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
|
||||||
|
|
||||||
##### Current version: 0.8.12
|
##### Current version: 0.8.13
|
||||||
|
|
||||||
| Web | UI | Preview |
|
| Web | UI | Preview |
|
||||||
|:-------------:|:-------:|:-------:|
|
|:-------------:|:-------:|:-------:|
|
||||||
|
@ -82,6 +82,7 @@ There are 5 ways to install Gogs:
|
||||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
|
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
|
||||||
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
|
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
|
||||||
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
|
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
|
||||||
|
- [Cloudflare Full SSL with GOGS (Go Git Service) using NGINX](http://www.listekconsulting.com/articles/cloudflare-full-ssl-with-gogs-go-git-service-using-nginx/)
|
||||||
|
|
||||||
### Screencasts
|
### Screencasts
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ There are 5 ways to install Gogs:
|
||||||
- [Drone](https://github.com/drone/drone) (CI)
|
- [Drone](https://github.com/drone/drone) (CI)
|
||||||
- [Fabric8](http://fabric8.io/) (DevOps)
|
- [Fabric8](http://fabric8.io/) (DevOps)
|
||||||
- [Taiga](https://taiga.io/) (Project Management)
|
- [Taiga](https://taiga.io/) (Project Management)
|
||||||
|
- [Puppet](https://forge.puppetlabs.com/Siteminds/gogs) (IT)
|
||||||
|
|
||||||
### Product Support
|
### Product Support
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||||
- [Drone](https://github.com/drone/drone)(CI)
|
- [Drone](https://github.com/drone/drone)(CI)
|
||||||
- [Fabric8](http://fabric8.io/)(DevOps)
|
- [Fabric8](http://fabric8.io/)(DevOps)
|
||||||
- [Taiga](https://taiga.io/)(项目管理)
|
- [Taiga](https://taiga.io/)(项目管理)
|
||||||
|
- [Puppet](https://forge.puppetlabs.com/Siteminds/gogs)(IT)
|
||||||
|
|
||||||
### 产品支持
|
### 产品支持
|
||||||
|
|
||||||
|
|
12
cmd/web.go
12
cmd/web.go
|
@ -289,7 +289,13 @@ func runWeb(ctx *cli.Context) {
|
||||||
// ***** END: Admin *****
|
// ***** END: Admin *****
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
m.Get("/:username", user.Profile)
|
m.Group("/:username", func() {
|
||||||
|
m.Get("", user.Profile)
|
||||||
|
m.Get("/followers", user.Followers)
|
||||||
|
m.Get("/following", user.Following)
|
||||||
|
m.Get("/stars", user.Stars)
|
||||||
|
})
|
||||||
|
|
||||||
m.Get("/attachments/:uuid", func(ctx *middleware.Context) {
|
m.Get("/attachments/:uuid", func(ctx *middleware.Context) {
|
||||||
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -319,6 +325,10 @@ func runWeb(ctx *cli.Context) {
|
||||||
m.Post("/issues/attachments", repo.UploadIssueAttachment)
|
m.Post("/issues/attachments", repo.UploadIssueAttachment)
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
|
||||||
|
m.Group("/:username", func() {
|
||||||
|
m.Get("/action/:action", user.Action)
|
||||||
|
}, reqSignIn)
|
||||||
|
|
||||||
if macaron.Env == macaron.DEV {
|
if macaron.Env == macaron.DEV {
|
||||||
m.Get("/template/*", dev.TemplatePreview)
|
m.Get("/template/*", dev.TemplatePreview)
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,8 +230,10 @@ join_on = Joined on
|
||||||
repositories = Repositories
|
repositories = Repositories
|
||||||
activity = Public Activity
|
activity = Public Activity
|
||||||
followers = Followers
|
followers = Followers
|
||||||
starred = Starred
|
starred = Starred repositories
|
||||||
following = Following
|
following = Following
|
||||||
|
follow = Follow
|
||||||
|
unfollow = Unfollow
|
||||||
|
|
||||||
form.name_reserved = Username '%s' is reserved.
|
form.name_reserved = Username '%s' is reserved.
|
||||||
form.name_pattern_not_allowed = Username pattern '%s' is not allowed.
|
form.name_pattern_not_allowed = Username pattern '%s' is not allowed.
|
||||||
|
|
2
gogs.go
2
gogs.go
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VER = "0.8.12.1219"
|
const APP_VER = "0.8.13.1221"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
|
@ -665,6 +665,47 @@ func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int
|
||||||
return ius, err
|
return ius, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateMentions(userNames []string, issueId int64) error {
|
||||||
|
for i := range userNames {
|
||||||
|
userNames[i] = strings.ToLower(userNames[i])
|
||||||
|
}
|
||||||
|
users := make([]*User, 0, len(userNames))
|
||||||
|
|
||||||
|
if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := make([]int64, 0, len(userNames))
|
||||||
|
for _, user := range users {
|
||||||
|
ids = append(ids, user.Id)
|
||||||
|
if !user.IsOrganization() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.NumMembers == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tempIds := make([]int64, 0, user.NumMembers)
|
||||||
|
orgUsers, err := GetOrgUsersByOrgId(user.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, orgUser := range orgUsers {
|
||||||
|
tempIds = append(tempIds, orgUser.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = append(ids, tempIds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateIssueUsersByMentions(ids, issueId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IssueStats represents issue statistic information.
|
// IssueStats represents issue statistic information.
|
||||||
type IssueStats struct {
|
type IssueStats struct {
|
||||||
OpenCount, ClosedCount int64
|
OpenCount, ClosedCount int64
|
||||||
|
|
165
models/user.go
165
models/user.go
|
@ -56,7 +56,7 @@ type User struct {
|
||||||
LowerName string `xorm:"UNIQUE NOT NULL"`
|
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||||
Name string `xorm:"UNIQUE NOT NULL"`
|
Name string `xorm:"UNIQUE NOT NULL"`
|
||||||
FullName string
|
FullName string
|
||||||
// Email is the primary email address (to be used for communication).
|
// Email is the primary email address (to be used for communication)
|
||||||
Email string `xorm:"NOT NULL"`
|
Email string `xorm:"NOT NULL"`
|
||||||
Passwd string `xorm:"NOT NULL"`
|
Passwd string `xorm:"NOT NULL"`
|
||||||
LoginType LoginType
|
LoginType LoginType
|
||||||
|
@ -78,24 +78,24 @@ type User struct {
|
||||||
// Maximum repository creation limit, -1 means use gloabl default
|
// Maximum repository creation limit, -1 means use gloabl default
|
||||||
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"`
|
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"`
|
||||||
|
|
||||||
// Permissions.
|
// Permissions
|
||||||
IsActive bool
|
IsActive bool
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
AllowGitHook bool
|
AllowGitHook bool
|
||||||
AllowImportLocal bool // Allow migrate repository by local path
|
AllowImportLocal bool // Allow migrate repository by local path
|
||||||
|
|
||||||
// Avatar.
|
// Avatar
|
||||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
|
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
|
||||||
AvatarEmail string `xorm:"NOT NULL"`
|
AvatarEmail string `xorm:"NOT NULL"`
|
||||||
UseCustomAvatar bool
|
UseCustomAvatar bool
|
||||||
|
|
||||||
// Counters.
|
// Counters
|
||||||
NumFollowers int
|
NumFollowers int
|
||||||
NumFollowings int
|
NumFollowing int `xorm:"NOT NULL"`
|
||||||
NumStars int
|
NumStars int
|
||||||
NumRepos int
|
NumRepos int
|
||||||
|
|
||||||
// For organization.
|
// For organization
|
||||||
Description string
|
Description string
|
||||||
NumTeams int
|
NumTeams int
|
||||||
NumMembers int
|
NumMembers int
|
||||||
|
@ -263,6 +263,34 @@ func (u *User) AvatarLink() string {
|
||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User.GetFollwoers returns range of user's followers.
|
||||||
|
func (u *User) GetFollowers(page int) ([]*User, error) {
|
||||||
|
users := make([]*User, 0, ItemsPerPage)
|
||||||
|
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.Id)
|
||||||
|
if setting.UsePostgreSQL {
|
||||||
|
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
|
||||||
|
} else {
|
||||||
|
sess = sess.Join("LEFT", "follow", "user.id=follow.user_id")
|
||||||
|
}
|
||||||
|
return users, sess.Find(&users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsFollowing(followID int64) bool {
|
||||||
|
return IsFollowing(u.Id, followID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowing returns range of user's following.
|
||||||
|
func (u *User) GetFollowing(page int) ([]*User, error) {
|
||||||
|
users := make([]*User, 0, ItemsPerPage)
|
||||||
|
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.Id)
|
||||||
|
if setting.UsePostgreSQL {
|
||||||
|
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
|
||||||
|
} else {
|
||||||
|
sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id")
|
||||||
|
}
|
||||||
|
return users, sess.Find(&users)
|
||||||
|
}
|
||||||
|
|
||||||
// NewGitSig generates and returns the signature of given user.
|
// NewGitSig generates and returns the signature of given user.
|
||||||
func (u *User) NewGitSig() *git.Signature {
|
func (u *User) NewGitSig() *git.Signature {
|
||||||
return &git.Signature{
|
return &git.Signature{
|
||||||
|
@ -1077,100 +1105,73 @@ func SearchUserByName(opt SearchOption) (us []*User, err error) {
|
||||||
return us, err
|
return us, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow is connection request for receiving user notification.
|
// ___________ .__ .__
|
||||||
|
// \_ _____/___ | | | | ______ _ __
|
||||||
|
// | __)/ _ \| | | | / _ \ \/ \/ /
|
||||||
|
// | \( <_> ) |_| |_( <_> ) /
|
||||||
|
// \___ / \____/|____/____/\____/ \/\_/
|
||||||
|
// \/
|
||||||
|
|
||||||
|
// Follow represents relations of user and his/her followers.
|
||||||
type Follow struct {
|
type Follow struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
UserID int64 `xorm:"UNIQUE(follow)"`
|
UserID int64 `xorm:"UNIQUE(follow)"`
|
||||||
FollowID int64 `xorm:"UNIQUE(follow)"`
|
FollowID int64 `xorm:"UNIQUE(follow)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsFollowing(userID, followID int64) bool {
|
||||||
|
has, _ := x.Get(&Follow{UserID: userID, FollowID: followID})
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
// FollowUser marks someone be another's follower.
|
// FollowUser marks someone be another's follower.
|
||||||
func FollowUser(userId int64, followId int64) (err error) {
|
func FollowUser(userID, followID int64) (err error) {
|
||||||
|
if userID == followID || IsFollowing(userID, followID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
defer sess.Close()
|
defer sessionRelease(sess)
|
||||||
sess.Begin()
|
if err = sess.Begin(); err != nil {
|
||||||
|
|
||||||
if _, err = sess.Insert(&Follow{UserID: userId, FollowID: followId}); err != nil {
|
|
||||||
sess.Rollback()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawSql := "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?"
|
if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil {
|
||||||
if _, err = sess.Exec(rawSql, followId); err != nil {
|
|
||||||
sess.Rollback()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawSql = "UPDATE `user` SET num_followings = num_followings + 1 WHERE id = ?"
|
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
|
||||||
if _, err = sess.Exec(rawSql, userId); err != nil {
|
return err
|
||||||
sess.Rollback()
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnFollowUser unmarks someone be another's follower.
|
// UnfollowUser unmarks someone be another's follower.
|
||||||
func UnFollowUser(userId int64, unFollowId int64) (err error) {
|
func UnfollowUser(userID, followID int64) (err error) {
|
||||||
session := x.NewSession()
|
if userID == followID || !IsFollowing(userID, followID) {
|
||||||
defer session.Close()
|
|
||||||
session.Begin()
|
|
||||||
|
|
||||||
if _, err = session.Delete(&Follow{UserID: userId, FollowID: unFollowId}); err != nil {
|
|
||||||
session.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawSql := "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?"
|
|
||||||
if _, err = session.Exec(rawSql, unFollowId); err != nil {
|
|
||||||
session.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawSql = "UPDATE `user` SET num_followings = num_followings - 1 WHERE id = ?"
|
|
||||||
if _, err = session.Exec(rawSql, userId); err != nil {
|
|
||||||
session.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return session.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateMentions(userNames []string, issueId int64) error {
|
|
||||||
for i := range userNames {
|
|
||||||
userNames[i] = strings.ToLower(userNames[i])
|
|
||||||
}
|
|
||||||
users := make([]*User, 0, len(userNames))
|
|
||||||
|
|
||||||
if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ids := make([]int64, 0, len(userNames))
|
|
||||||
for _, user := range users {
|
|
||||||
ids = append(ids, user.Id)
|
|
||||||
if !user.IsOrganization() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.NumMembers == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tempIds := make([]int64, 0, user.NumMembers)
|
|
||||||
orgUsers, err := GetOrgUsersByOrgId(user.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, orgUser := range orgUsers {
|
|
||||||
tempIds = append(tempIds, orgUser.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
ids = append(ids, tempIds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := UpdateIssueUsersByMentions(ids, issueId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -242,7 +242,8 @@ func Contexter() macaron.Handler {
|
||||||
|
|
||||||
ctx.Data["CsrfToken"] = x.GetToken()
|
ctx.Data["CsrfToken"] = x.GetToken()
|
||||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`)
|
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`)
|
||||||
log.Debug("CSRF Token: %v | %v", ctx.Data["CsrfToken"], ctx.GetCookie("_csrf"))
|
log.Debug("Session ID: %s", sess.ID())
|
||||||
|
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
|
||||||
|
|
||||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||||
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
||||||
|
|
|
@ -2465,31 +2465,6 @@ footer .container .links > *:first-child {
|
||||||
.repository.new.release .prerelease.field {
|
.repository.new.release .prerelease.field {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.repository.watchers .list {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.repository.watchers .list .item {
|
|
||||||
list-style: none;
|
|
||||||
width: 32%;
|
|
||||||
margin: 10px 10px 10px 0;
|
|
||||||
padding-bottom: 14px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.repository.watchers .list .item .avatar {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
float: left;
|
|
||||||
display: block;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.repository.watchers .list .item .name {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.repository.watchers .list .item .meta {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
.repository.forks .list {
|
.repository.forks .list {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -2551,6 +2526,31 @@ footer .container .links > *:first-child {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
|
.user-cards .list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.user-cards .list .item {
|
||||||
|
list-style: none;
|
||||||
|
width: 32%;
|
||||||
|
margin: 10px 10px 10px 0;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.user-cards .list .item .avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.user-cards .list .item .name {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.user-cards .list .item .meta {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
#search-repo-box .results,
|
#search-repo-box .results,
|
||||||
#search-user-box .results {
|
#search-user-box .results {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -2862,7 +2862,7 @@ footer .container .links > *:first-child {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
.user {
|
.user:not(.icon) {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: 80px;
|
padding-bottom: 80px;
|
||||||
}
|
}
|
||||||
|
@ -2893,9 +2893,24 @@ footer .container .links > *:first-child {
|
||||||
.user.profile .ui.card .extra.content ul li:not(:last-child) {
|
.user.profile .ui.card .extra.content ul li:not(:last-child) {
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid #eaeaea;
|
||||||
}
|
}
|
||||||
|
.user.profile .ui.card .extra.content ul li .octicon {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.user.profile .ui.card .extra.content ul li.follow .ui.button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.user.profile .ui.repository.list {
|
.user.profile .ui.repository.list {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
}
|
}
|
||||||
|
.user.followers .header.name {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.user.followers .follow .ui.button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
}
|
||||||
.dashboard {
|
.dashboard {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: 80px;
|
padding-bottom: 80px;
|
||||||
|
|
|
@ -859,35 +859,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.watchers {
|
|
||||||
.list {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.item {
|
|
||||||
list-style: none;
|
|
||||||
width: 32%;
|
|
||||||
margin: 10px 10px 10px 0;
|
|
||||||
padding-bottom: 14px;
|
|
||||||
float: left;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
float: left;
|
|
||||||
display: block;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.meta {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.forks {
|
&.forks {
|
||||||
.list {
|
.list {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -982,6 +953,36 @@
|
||||||
}
|
}
|
||||||
// End of .repository
|
// End of .repository
|
||||||
|
|
||||||
|
&.user-cards {
|
||||||
|
.list {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
list-style: none;
|
||||||
|
width: 32%;
|
||||||
|
margin: 10px 10px 10px 0;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#search-repo-box,
|
#search-repo-box,
|
||||||
#search-user-box {
|
#search-user-box {
|
||||||
.results {
|
.results {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
.user {
|
.user {
|
||||||
|
&:not(.icon) {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: @footer-margin * 2;
|
padding-bottom: @footer-margin * 2;
|
||||||
|
}
|
||||||
|
|
||||||
&.settings {
|
&.settings {
|
||||||
.list {
|
.list {
|
||||||
|
@ -38,6 +40,17 @@
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid #eaeaea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.octicon {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.follow {
|
||||||
|
.ui.button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,4 +60,18 @@
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.followers {
|
||||||
|
.header.name {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow {
|
||||||
|
.ui.button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,19 +135,32 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Group("/users", func() {
|
m.Group("/users", func() {
|
||||||
m.Group("/:username", func() {
|
m.Group("/:username", func() {
|
||||||
m.Get("/keys", user.ListPublicKeys)
|
m.Get("/keys", user.ListPublicKeys)
|
||||||
|
|
||||||
|
m.Get("/followers", user.ListFollowers)
|
||||||
|
m.Group("/following", func() {
|
||||||
|
m.Get("", user.ListFollowing)
|
||||||
|
m.Get("/:target", user.CheckFollowing)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}, ReqToken())
|
}, ReqToken())
|
||||||
|
|
||||||
m.Group("/user", func() {
|
m.Group("/user", func() {
|
||||||
|
m.Combo("/emails").Get(user.ListEmails).
|
||||||
|
Post(bind(api.CreateEmailOption{}), user.AddEmail).
|
||||||
|
Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
|
||||||
|
|
||||||
|
m.Get("/followers", user.ListMyFollowers)
|
||||||
|
m.Group("/following", func() {
|
||||||
|
m.Get("", user.ListMyFollowing)
|
||||||
|
m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
|
||||||
|
})
|
||||||
|
|
||||||
m.Group("/keys", func() {
|
m.Group("/keys", func() {
|
||||||
m.Combo("").Get(user.ListMyPublicKeys).
|
m.Combo("").Get(user.ListMyPublicKeys).
|
||||||
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
|
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
|
||||||
m.Combo("/:id").Get(user.GetPublicKey).
|
m.Combo("/:id").Get(user.GetPublicKey).
|
||||||
Delete(user.DeletePublicKey)
|
Delete(user.DeletePublicKey)
|
||||||
})
|
})
|
||||||
m.Combo("/emails").Get(user.ListEmails).
|
|
||||||
Post(bind(api.CreateEmailOption{}), user.AddEmail).
|
|
||||||
Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
|
|
||||||
}, ReqToken())
|
}, ReqToken())
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
|
|
121
routers/api/v1/user/followers.go
Normal file
121
routers/api/v1/user/followers.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
api "github.com/gogits/go-gogs-client"
|
||||||
|
|
||||||
|
"github.com/gogits/gogs/models"
|
||||||
|
"github.com/gogits/gogs/modules/middleware"
|
||||||
|
"github.com/gogits/gogs/routers/api/v1/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func responseApiUsers(ctx *middleware.Context, users []*models.User) {
|
||||||
|
apiUsers := make([]*api.User, len(users))
|
||||||
|
for i := range users {
|
||||||
|
apiUsers[i] = convert.ToApiUser(users[i])
|
||||||
|
}
|
||||||
|
ctx.JSON(200, &apiUsers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listUserFollowers(ctx *middleware.Context, u *models.User) {
|
||||||
|
users, err := u.GetFollowers(ctx.QueryInt("page"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIError(500, "GetUserFollowers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responseApiUsers(ctx, users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListMyFollowers(ctx *middleware.Context) {
|
||||||
|
listUserFollowers(ctx, ctx.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user
|
||||||
|
func ListFollowers(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listUserFollowers(ctx, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listUserFollowing(ctx *middleware.Context, u *models.User) {
|
||||||
|
users, err := u.GetFollowing(ctx.QueryInt("page"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIError(500, "GetFollowing", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
responseApiUsers(ctx, users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListMyFollowing(ctx *middleware.Context) {
|
||||||
|
listUserFollowing(ctx, ctx.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user
|
||||||
|
func ListFollowing(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listUserFollowing(ctx, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkUserFollowing(ctx *middleware.Context, u *models.User, followID int64) {
|
||||||
|
if u.IsFollowing(followID) {
|
||||||
|
ctx.Status(204)
|
||||||
|
} else {
|
||||||
|
ctx.Error(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user
|
||||||
|
func CheckMyFollowing(ctx *middleware.Context) {
|
||||||
|
target := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkUserFollowing(ctx, ctx.User, target.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another
|
||||||
|
func CheckFollowing(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := GetUserByParamsName(ctx, ":target")
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkUserFollowing(ctx, u, target.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#follow-a-user
|
||||||
|
func Follow(ctx *middleware.Context) {
|
||||||
|
target := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := models.FollowUser(ctx.User.Id, target.Id); err != nil {
|
||||||
|
ctx.APIError(500, "FollowUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(204)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogits/go-gogs-client/wiki/Users-Followers#unfollow-a-user
|
||||||
|
func Unfollow(ctx *middleware.Context) {
|
||||||
|
target := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := models.UnfollowUser(ctx.User.Id, target.Id); err != nil {
|
||||||
|
ctx.APIError(500, "UnfollowUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(204)
|
||||||
|
}
|
|
@ -244,11 +244,7 @@ func Action(ctx *middleware.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(4, "Action(%s): %v", ctx.Params(":action"), err)
|
ctx.Handle(500, fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
|
||||||
ctx.JSON(200, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"err": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,7 @@ func Home(ctx *middleware.Context) {
|
||||||
ctx.HTML(200, HOME)
|
ctx.HTML(200, HOME)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderItems(ctx *middleware.Context, total int, getter func(page int) ([]*models.User, error)) {
|
func RenderUserCards(ctx *middleware.Context, total int, getter func(page int) ([]*models.User, error), tpl base.TplName) {
|
||||||
page := ctx.QueryInt("page")
|
page := ctx.QueryInt("page")
|
||||||
if page <= 0 {
|
if page <= 0 {
|
||||||
page = 1
|
page = 1
|
||||||
|
@ -230,21 +230,23 @@ func renderItems(ctx *middleware.Context, total int, getter func(page int) ([]*m
|
||||||
ctx.Handle(500, "getter", err)
|
ctx.Handle(500, "getter", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["Watchers"] = items
|
ctx.Data["Cards"] = items
|
||||||
|
|
||||||
ctx.HTML(200, WATCHERS)
|
ctx.HTML(200, tpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Watchers(ctx *middleware.Context) {
|
func Watchers(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.watchers")
|
ctx.Data["Title"] = ctx.Tr("repo.watchers")
|
||||||
|
ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers")
|
||||||
ctx.Data["PageIsWatchers"] = true
|
ctx.Data["PageIsWatchers"] = true
|
||||||
renderItems(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers)
|
RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers, WATCHERS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stars(ctx *middleware.Context) {
|
func Stars(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.stargazers")
|
ctx.Data["Title"] = ctx.Tr("repo.stargazers")
|
||||||
|
ctx.Data["CardsTitle"] = ctx.Tr("repo.stargazers")
|
||||||
ctx.Data["PageIsStargazers"] = true
|
ctx.Data["PageIsStargazers"] = true
|
||||||
renderItems(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers)
|
RenderUserCards(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers, WATCHERS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Forks(ctx *middleware.Context) {
|
func Forks(ctx *middleware.Context) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ package user
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
"github.com/Unknwon/paginater"
|
"github.com/Unknwon/paginater"
|
||||||
|
@ -21,7 +20,6 @@ import (
|
||||||
const (
|
const (
|
||||||
DASHBOARD base.TplName = "user/dashboard/dashboard"
|
DASHBOARD base.TplName = "user/dashboard/dashboard"
|
||||||
ISSUES base.TplName = "user/dashboard/issues"
|
ISSUES base.TplName = "user/dashboard/issues"
|
||||||
STARS base.TplName = "user/stars"
|
|
||||||
PROFILE base.TplName = "user/profile"
|
PROFILE base.TplName = "user/profile"
|
||||||
ORG_HOME base.TplName = "org/home"
|
ORG_HOME base.TplName = "org/home"
|
||||||
)
|
)
|
||||||
|
@ -338,67 +336,6 @@ func showOrgProfile(ctx *middleware.Context) {
|
||||||
ctx.HTML(200, ORG_HOME)
|
ctx.HTML(200, ORG_HOME)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Profile(ctx *middleware.Context) {
|
|
||||||
ctx.Data["Title"] = "Profile"
|
|
||||||
ctx.Data["PageIsUserProfile"] = true
|
|
||||||
|
|
||||||
uname := ctx.Params(":username")
|
|
||||||
// Special handle for FireFox requests favicon.ico.
|
|
||||||
if uname == "favicon.ico" {
|
|
||||||
ctx.Redirect(setting.AppSubUrl + "/img/favicon.png")
|
|
||||||
return
|
|
||||||
} else if strings.HasSuffix(uname, ".png") {
|
|
||||||
ctx.Error(404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isShowKeys := false
|
|
||||||
if strings.HasSuffix(uname, ".keys") {
|
|
||||||
isShowKeys = true
|
|
||||||
uname = strings.TrimSuffix(uname, ".keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := models.GetUserByName(uname)
|
|
||||||
if err != nil {
|
|
||||||
if models.IsErrUserNotExist(err) {
|
|
||||||
ctx.Handle(404, "GetUserByName", err)
|
|
||||||
} else {
|
|
||||||
ctx.Handle(500, "GetUserByName", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show SSH keys.
|
|
||||||
if isShowKeys {
|
|
||||||
ShowSSHKeys(ctx, u.Id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.IsOrganization() {
|
|
||||||
showOrgProfile(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["Owner"] = u
|
|
||||||
|
|
||||||
tab := ctx.Query("tab")
|
|
||||||
ctx.Data["TabName"] = tab
|
|
||||||
switch tab {
|
|
||||||
case "activity":
|
|
||||||
retrieveFeeds(ctx, u.Id, 0, true)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
ctx.Data["Repos"], err = models.GetRepositories(u.Id, ctx.IsSigned && ctx.User.Id == u.Id)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Handle(500, "GetRepositories", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.HTML(200, PROFILE)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Email2User(ctx *middleware.Context) {
|
func Email2User(ctx *middleware.Context) {
|
||||||
u, err := models.GetUserByEmail(ctx.Query("email"))
|
u, err := models.GetUserByEmail(ctx.Query("email"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
145
routers/user/profile.go
Normal file
145
routers/user/profile.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogits/gogs/models"
|
||||||
|
"github.com/gogits/gogs/modules/base"
|
||||||
|
"github.com/gogits/gogs/modules/middleware"
|
||||||
|
"github.com/gogits/gogs/modules/setting"
|
||||||
|
"github.com/gogits/gogs/routers/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOLLOWERS base.TplName = "user/meta/followers"
|
||||||
|
STARS base.TplName = "user/meta/stars"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUserByParams returns user whose name is presented in URL paramenter.
|
||||||
|
func GetUserByParams(ctx *middleware.Context) *models.User {
|
||||||
|
user, err := models.GetUserByName(ctx.Params(":username"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(404)
|
||||||
|
} else {
|
||||||
|
ctx.Handle(500, "GetUserByName", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func Profile(ctx *middleware.Context) {
|
||||||
|
uname := ctx.Params(":username")
|
||||||
|
// Special handle for FireFox requests favicon.ico.
|
||||||
|
if uname == "favicon.ico" {
|
||||||
|
ctx.Redirect(setting.AppSubUrl + "/img/favicon.png")
|
||||||
|
return
|
||||||
|
} else if strings.HasSuffix(uname, ".png") {
|
||||||
|
ctx.Error(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isShowKeys := false
|
||||||
|
if strings.HasSuffix(uname, ".keys") {
|
||||||
|
isShowKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show SSH keys.
|
||||||
|
if isShowKeys {
|
||||||
|
ShowSSHKeys(ctx, u.Id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.IsOrganization() {
|
||||||
|
showOrgProfile(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["Title"] = u.DisplayName()
|
||||||
|
ctx.Data["PageIsUserProfile"] = true
|
||||||
|
ctx.Data["Owner"] = u
|
||||||
|
|
||||||
|
tab := ctx.Query("tab")
|
||||||
|
ctx.Data["TabName"] = tab
|
||||||
|
switch tab {
|
||||||
|
case "activity":
|
||||||
|
retrieveFeeds(ctx, u.Id, 0, true)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var err error
|
||||||
|
ctx.Data["Repos"], err = models.GetRepositories(u.Id, ctx.IsSigned && ctx.User.Id == u.Id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "GetRepositories", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.HTML(200, PROFILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Followers(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Title"] = u.DisplayName()
|
||||||
|
ctx.Data["CardsTitle"] = ctx.Tr("user.followers")
|
||||||
|
ctx.Data["PageIsFollowers"] = true
|
||||||
|
ctx.Data["Owner"] = u
|
||||||
|
repo.RenderUserCards(ctx, u.NumFollowers, u.GetFollowers, FOLLOWERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Following(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Title"] = u.DisplayName()
|
||||||
|
ctx.Data["CardsTitle"] = ctx.Tr("user.following")
|
||||||
|
ctx.Data["PageIsFollowing"] = true
|
||||||
|
ctx.Data["Owner"] = u
|
||||||
|
repo.RenderUserCards(ctx, u.NumFollowing, u.GetFollowing, FOLLOWERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stars(ctx *middleware.Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Action(ctx *middleware.Context) {
|
||||||
|
u := GetUserByParams(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch ctx.Params(":action") {
|
||||||
|
case "follow":
|
||||||
|
err = models.FollowUser(ctx.User.Id, u.Id)
|
||||||
|
case "unfollow":
|
||||||
|
err = models.UnfollowUser(ctx.User.Id, u.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectTo := ctx.Query("redirect_to")
|
||||||
|
if len(redirectTo) == 0 {
|
||||||
|
redirectTo = u.HomeLink()
|
||||||
|
}
|
||||||
|
ctx.Redirect(redirectTo)
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
0.8.12.1219
|
0.8.13.1221
|
47
templates/repo/user_cards.tmpl
Normal file
47
templates/repo/user_cards.tmpl
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<div class="ui container user-cards">
|
||||||
|
<h2 class="ui dividing header">
|
||||||
|
{{.CardsTitle}}
|
||||||
|
</h2>
|
||||||
|
<ul class="list">
|
||||||
|
{{range .Cards}}
|
||||||
|
<li class="item ui segment">
|
||||||
|
<a href="{{.HomeLink}}">
|
||||||
|
<img class="avatar" src="{{.AvatarLink}}"/>
|
||||||
|
</a>
|
||||||
|
<h3 class="name"><a href="{{.HomeLink}}">{{.DisplayName}}</a></h3>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
{{if .Website}}
|
||||||
|
<span class="icon octicon octicon-link"></span> <a href="{{.Website}}" target="_blank">{{.Website}}</a>
|
||||||
|
{{else if .Location}}
|
||||||
|
<span class="icon octicon octicon-location"></span> {{.Location}}
|
||||||
|
{{else}}
|
||||||
|
<span class="icon octicon octicon-clock"></span> {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{with .Page}}
|
||||||
|
{{if gt .TotalPages 1}}
|
||||||
|
<div class="center page buttons">
|
||||||
|
<div class="ui borderless pagination menu">
|
||||||
|
<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}"{{end}}>
|
||||||
|
<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}}
|
||||||
|
</a>
|
||||||
|
{{range .Pages}}
|
||||||
|
{{if eq .Num -1}}
|
||||||
|
<a class="disabled item">...</a>
|
||||||
|
{{else}}
|
||||||
|
<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}"{{end}}>{{.Num}}</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}"{{end}}>
|
||||||
|
{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
|
@ -1,56 +1,6 @@
|
||||||
{{template "base/head" .}}
|
{{template "base/head" .}}
|
||||||
<div class="repository watchers">
|
<div class="repository watchers">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
{{template "repo/user_cards" .}}
|
||||||
<h2 class="ui dividing header">
|
|
||||||
{{if .PageIsWatchers}}
|
|
||||||
{{.i18n.Tr "repo.watchers"}}
|
|
||||||
{{else}}
|
|
||||||
{{.i18n.Tr "repo.stargazers"}}
|
|
||||||
{{end}}
|
|
||||||
</h2>
|
|
||||||
<ul class="list">
|
|
||||||
{{range .Watchers}}
|
|
||||||
<li class="item ui segment">
|
|
||||||
<a href="{{.HomeLink}}">
|
|
||||||
<img class="avatar" src="{{.AvatarLink}}"/>
|
|
||||||
</a>
|
|
||||||
<h3 class="name"><a href="{{.HomeLink}}">{{.DisplayName}}</a></h3>
|
|
||||||
|
|
||||||
<div class="meta">
|
|
||||||
{{if .Website}}
|
|
||||||
<span class="icon octicon octicon-link"></span> <a href="{{.Website}}" target="_blank">{{.Website}}</a>
|
|
||||||
{{else if .Location}}
|
|
||||||
<span class="icon octicon octicon-location"></span> {{.Location}}
|
|
||||||
{{else}}
|
|
||||||
<span class="icon octicon octicon-clock"></span> {{$.i18n.Tr "user.join_on"}} {{DateFmtShort .Created}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{{with .Page}}
|
|
||||||
{{if gt .TotalPages 1}}
|
|
||||||
<div class="center page buttons">
|
|
||||||
<div class="ui borderless pagination menu">
|
|
||||||
<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}"{{end}}>
|
|
||||||
<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}}
|
|
||||||
</a>
|
|
||||||
{{range .Pages}}
|
|
||||||
{{if eq .Num -1}}
|
|
||||||
<a class="disabled item">...</a>
|
|
||||||
{{else}}
|
|
||||||
<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}"{{end}}>{{.Num}}</a>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}"{{end}}>
|
|
||||||
{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
6
templates/user/meta/followers.tmpl
Normal file
6
templates/user/meta/followers.tmpl
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="user followers">
|
||||||
|
{{template "user/meta/header" .}}
|
||||||
|
{{template "repo/user_cards" .}}
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
25
templates/user/meta/header.tmpl
Normal file
25
templates/user/meta/header.tmpl
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{{with .Owner}}
|
||||||
|
<div class="ui container">
|
||||||
|
<img class="ui avatar image" src="{{.AvatarLink}}">
|
||||||
|
<span class="header name">
|
||||||
|
<a href="{{.HomeLink}}">{{.Name}}</a>
|
||||||
|
{{with .FullName}}({{.}}){{end}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="ui right">
|
||||||
|
|
||||||
|
{{if or $.PageIsFollowers $.PageIsFollowing}}
|
||||||
|
{{if and $.IsSigned (ne $.SignedUserName .Name)}}
|
||||||
|
<div class="follow">
|
||||||
|
{{if $.SignedUser.IsFollowing .Id}}
|
||||||
|
<a class="ui small basic red button" href="{{.HomeLink}}/action/unfollow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{$.i18n.Tr "user.unfollow"}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a class="ui small basic green button" href="{{.HomeLink}}/action/follow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{$.i18n.Tr "user.follow"}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="ui divider"></div>
|
0
templates/user/meta/stars.tmpl
Normal file
0
templates/user/meta/stars.tmpl
Normal file
|
@ -39,6 +39,33 @@
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
<li><i class="icon octicon octicon-clock"></i> {{.i18n.Tr "user.join_on"}} {{DateFmtShort .Owner.Created}}</li>
|
<li><i class="icon octicon octicon-clock"></i> {{.i18n.Tr "user.join_on"}} {{DateFmtShort .Owner.Created}}</li>
|
||||||
|
<li>
|
||||||
|
<i class="user icon"></i>
|
||||||
|
<a href="{{.Owner.HomeLink}}/followers">
|
||||||
|
{{.Owner.NumFollowers}} {{.i18n.Tr "user.followers"}}
|
||||||
|
</a>
|
||||||
|
-
|
||||||
|
<a href="{{.Owner.HomeLink}}/following">
|
||||||
|
{{.Owner.NumFollowing}} {{.i18n.Tr "user.following"}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{/*
|
||||||
|
<li>
|
||||||
|
<i class="octicon octicon-star"></i>
|
||||||
|
<a href="{{.Owner.HomeLink}}/stars">
|
||||||
|
{{.Owner.NumStars}} {{.i18n.Tr "user.starred"}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
*/}}
|
||||||
|
{{if and .IsSigned (ne .SignedUserName .Owner.Name)}}
|
||||||
|
<li class="follow">
|
||||||
|
{{if .SignedUser.IsFollowing .Owner.Id}}
|
||||||
|
<a class="ui basic red button" href="{{.Link}}/action/unfollow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{.i18n.Tr "user.unfollow"}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a class="ui basic green button" href="{{.Link}}/action/follow?redirect_to={{$.Link}}"><i class="octicon octicon-person"></i> {{.i18n.Tr "user.follow"}}</a>
|
||||||
|
{{end}}
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue