diff --git a/app/controllers/users/is_pinned_projects_controller.rb b/app/controllers/users/is_pinned_projects_controller.rb
new file mode 100644
index 000000000..22ce29263
--- /dev/null
+++ b/app/controllers/users/is_pinned_projects_controller.rb
@@ -0,0 +1,27 @@
+class Users::IsPinnedProjectsController < Users::BaseController
+ before_action :private_user_resources!, only: [:pin]
+ def index
+ @is_pinned_projects = observed_user.is_pinned_projects.includes(:project_category, :project_language, :repository).order(position: :desc)
+ @is_pinned_projects = kaminari_paginate(@is_pinned_projects)
+ end
+
+ def pin
+ observed_user.is_pinned_project_ids = is_pinned_project_ids
+ render_ok
+ rescue ActiveRecord::RecordNotFound => e
+ render_not_found
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ private
+ def is_pinned_project_ids
+ if params[:is_pinned_project_ids].present?
+ return params[:is_pinned_project_ids].select{|id| observed_user.full_member_projects.pluck(:id).include?(id.to_i) }
+ end
+ if params[:is_pinned_project_id].present?
+ return observed_user.is_pinned_project_ids.include?(params[:is_pinned_project_id].to_i) ? observed_user.is_pinned_project_ids : observed_user.is_pinned_project_ids.push(params[:is_pinned_project_id].to_i)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/docs/slate/source/includes/_users.md b/app/docs/slate/source/includes/_users.md
index 982543fdc..b2c3ebccb 100644
--- a/app/docs/slate/source/includes/_users.md
+++ b/app/docs/slate/source/includes/_users.md
@@ -1,7 +1,7 @@
# Users
@@ -47,6 +47,135 @@ await octokit.request('GET /api/users/me.json')
Success Data.
+## 获取用户星标项目
+获取用户星标项目
+
+> 示例:
+
+```shell
+curl -X GET http://localhost:3000/api/users/yystopf/is_pinned_projects.json
+```
+
+```javascript
+await octokit.request('GET /api/users/:login/is_pinned_projects.json')
+```
+
+### HTTP 请求
+`GET api/users/:login/is_pinned_projects.json`
+
+### 返回字段说明:
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|total_count |int |星标项目数量 |
+|identifier |string |项目标识 |
+|name |string |项目名称 |
+|description |string |项目描述 |
+|visits |int |项目访问数量|
+|praises_count |int |项目点赞数量|
+|watchers_count |int |项目关注数量|
+|issues_count |int |项目issue数量|
+|pull_requests_count |int |项目合并请求数量|
+|forked_count |int |项目复刻数量|
+|is_public |bool |项目是否公开|
+|mirror_url |string |镜像地址|
+|type |int |项目类型 0 普通项目 1 普通镜像项目 2 同步镜像项目|
+|time_ago |string |上次更新时间|
+|open_devops |int |是否开启devops|
+|forked_from_project_id |int |fork项目id|
+|platform |string |项目平台|
+|author.name |string |项目拥有者名称|
+|author.type |string |项目拥有者类型|
+|author.login |string |项目拥有者用户名|
+|author.image_url |string |项目拥有者头像|
+|category.name |string |项目分类名称|
+|language.name |string |项目语言名称|
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "total_count": 1,
+ "projects": [
+ {
+ "id": 89,
+ "repo_id": 89,
+ "identifier": "monkey",
+ "name": "boke",
+ "description": "dkkd",
+ "visits": 4,
+ "praises_count": 0,
+ "watchers_count": 0,
+ "issues_count": 0,
+ "pull_requests_count": 0,
+ "forked_count": 0,
+ "is_public": true,
+ "mirror_url": "https://github.com/viletyy/monkey.git",
+ "type": 1,
+ "last_update_time": 1619685144,
+ "time_ago": "27天前",
+ "forked_from_project_id": null,
+ "open_devops": false,
+ "platform": "forge",
+ "author": {
+ "name": "测试组织",
+ "type": "Organization",
+ "login": "ceshi_org",
+ "image_url": "images/avatars/Organization/9?t=1612706073"
+ },
+ "category": {
+ "id": 3,
+ "name": "深度学习"
+ },
+ "language": {
+ "id": 2,
+ "name": "C"
+ }
+ }
+ ]
+}
+```
+
+
+## 用户添加星标项目
+用户添加星标项目
+
+> 示例:
+
+```shell
+curl -X POST http://localhost:3000/api/users/yystopf/is_pinned_projects/pin.json
+```
+
+```javascript
+await octokit.request('GET /api/users/:login/is_pinned_projects/pin.json')
+```
+
+### HTTP 请求
+`POST /api/users/:login/is_pinned_projects/pin.json`
+
+### 请求字段说明:
+#### 同时设定多个星标项目
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|is_pinned_project_ids |array |设定为星标项目的id |
+
+#### 只设定一个星标项目
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|is_pinned_project_id |integer |设定为星标项目的id |
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
+
+
## 获取用户贡献度
获取用户贡献度
diff --git a/app/models/pinned_project.rb b/app/models/pinned_project.rb
new file mode 100644
index 000000000..8e47c9ea4
--- /dev/null
+++ b/app/models/pinned_project.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: pinned_projects
+#
+# id :integer not null, primary key
+# user_id :integer
+# project_id :integer
+# position :integer default("0")
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_pinned_projects_on_project_id (project_id)
+# index_pinned_projects_on_user_id (user_id)
+#
+
+class PinnedProject < ApplicationRecord
+
+ belongs_to :user
+ belongs_to :project
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index b48680830..9299abcdb 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1,76 +1,76 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer not null, primary key
-# name :string(255) default(""), not null
-# description :text(4294967295)
-# homepage :string(255) default("")
-# is_public :boolean default("1"), not null
-# parent_id :integer
-# created_on :datetime
-# updated_on :datetime
-# identifier :string(255)
-# status :integer default("1"), not null
-# lft :integer
-# rgt :integer
-# inherit_members :boolean default("0"), not null
-# project_type :integer default("0")
-# hidden_repo :boolean default("0"), not null
-# attachmenttype :integer default("1")
-# user_id :integer
-# dts_test :integer default("0")
-# enterprise_name :string(255)
-# organization_id :integer
-# project_new_type :integer
-# gpid :integer
-# forked_from_project_id :integer
-# forked_count :integer default("0")
-# publish_resource :integer default("0")
-# visits :integer default("0")
-# hot :integer default("0")
-# invite_code :string(255)
-# qrcode :string(255)
-# qrcode_expiretime :integer default("0")
-# script :text(65535)
-# training_status :integer default("0")
-# rep_identifier :string(255)
-# project_category_id :integer
-# project_language_id :integer
-# praises_count :integer default("0")
-# watchers_count :integer default("0")
-# issues_count :integer default("0")
-# pull_requests_count :integer default("0")
-# language :string(255)
-# versions_count :integer default("0")
-# issue_tags_count :integer default("0")
-# closed_issues_count :integer default("0")
-# open_devops :boolean default("0")
-# gitea_webhook_id :integer
-# open_devops_count :integer default("0")
-# recommend :boolean default("0")
-# platform :integer default("0")
-# license_id :integer
-# ignore_id :integer
-# default_branch :string(255) default("master")
-# website :string(255)
-# lesson_url :string(255)
-#
-# Indexes
-#
-# index_projects_on_forked_from_project_id (forked_from_project_id)
-# index_projects_on_identifier (identifier)
-# index_projects_on_is_public (is_public)
-# index_projects_on_lft (lft)
-# index_projects_on_name (name)
-# index_projects_on_platform (platform)
-# index_projects_on_project_type (project_type)
-# index_projects_on_recommend (recommend)
-# index_projects_on_rgt (rgt)
-# index_projects_on_status (status)
-# index_projects_on_updated_on (updated_on)
-#
-
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255) default(""), not null
+# description :text(4294967295)
+# homepage :string(255) default("")
+# is_public :boolean default("1"), not null
+# parent_id :integer
+# created_on :datetime
+# updated_on :datetime
+# identifier :string(255)
+# status :integer default("1"), not null
+# lft :integer
+# rgt :integer
+# inherit_members :boolean default("0"), not null
+# project_type :integer default("0")
+# hidden_repo :boolean default("0"), not null
+# attachmenttype :integer default("1")
+# user_id :integer
+# dts_test :integer default("0")
+# enterprise_name :string(255)
+# organization_id :integer
+# project_new_type :integer
+# gpid :integer
+# forked_from_project_id :integer
+# forked_count :integer default("0")
+# publish_resource :integer default("0")
+# visits :integer default("0")
+# hot :integer default("0")
+# invite_code :string(255)
+# qrcode :string(255)
+# qrcode_expiretime :integer default("0")
+# script :text(65535)
+# training_status :integer default("0")
+# rep_identifier :string(255)
+# project_category_id :integer
+# project_language_id :integer
+# praises_count :integer default("0")
+# watchers_count :integer default("0")
+# issues_count :integer default("0")
+# pull_requests_count :integer default("0")
+# language :string(255)
+# versions_count :integer default("0")
+# issue_tags_count :integer default("0")
+# closed_issues_count :integer default("0")
+# open_devops :boolean default("0")
+# gitea_webhook_id :integer
+# open_devops_count :integer default("0")
+# recommend :boolean default("0")
+# platform :integer default("0")
+# license_id :integer
+# ignore_id :integer
+# default_branch :string(255) default("master")
+# website :string(255)
+# lesson_url :string(255)
+#
+# Indexes
+#
+# index_projects_on_forked_from_project_id (forked_from_project_id)
+# index_projects_on_identifier (identifier)
+# index_projects_on_is_public (is_public)
+# index_projects_on_lft (lft)
+# index_projects_on_name (name)
+# index_projects_on_platform (platform)
+# index_projects_on_project_type (project_type)
+# index_projects_on_recommend (recommend)
+# index_projects_on_rgt (rgt)
+# index_projects_on_status (status)
+# index_projects_on_updated_on (updated_on)
+#
+
class Project < ApplicationRecord
include Matchable
@@ -114,6 +114,8 @@ class Project < ApplicationRecord
has_many :team_projects, dependent: :destroy
has_many :project_units, dependent: :destroy
has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy
+ has_many :pinned_projects, dependent: :destroy
+ has_many :has_pinned_users, through: :pinned_projects, source: :user
after_save :check_project_members
scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)}
diff --git a/app/models/user.rb b/app/models/user.rb
index b778c80f9..bac795dd5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -164,6 +164,9 @@ class User < Owner
has_many :organization_users, dependent: :destroy
has_many :organizations, through: :organization_users
+ has_many :pinned_projects, dependent: :destroy
+ has_many :is_pinned_projects, through: :pinned_projects, source: :project
+ accepts_nested_attributes_for :is_pinned_projects
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
@@ -195,6 +198,13 @@ class User < Owner
validate :validate_sensitive_string
validate :validate_password_length
+ # 用户参与的所有项目
+ def full_member_projects
+ normal_projects = Project.members_projects(self.id).to_sql
+ org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: self.id}).to_sql
+ return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
+ end
+
def name
login
end
diff --git a/app/views/projects/_project_detail.json.jbuilder b/app/views/projects/_project_detail.json.jbuilder
index d0b62aaa3..2e256fcb4 100644
--- a/app/views/projects/_project_detail.json.jbuilder
+++ b/app/views/projects/_project_detail.json.jbuilder
@@ -5,6 +5,9 @@ json.name project.name
json.description Nokogiri::HTML(project.description).text
json.visits project.visits
json.praises_count project.praises_count.to_i
+json.watchers_count project.watchers_count.to_i
+json.issues_count project.issues_count.to_i
+json.pull_requests_count project.pull_requests_count.to_i
json.forked_count project.forked_count.to_i
json.is_public project.is_public
json.mirror_url project.repository&.mirror_url
diff --git a/app/views/repositories/edit.json.jbuilder b/app/views/repositories/edit.json.jbuilder
index 7a11411f4..e0b601f6d 100644
--- a/app/views/repositories/edit.json.jbuilder
+++ b/app/views/repositories/edit.json.jbuilder
@@ -12,4 +12,5 @@ json.permission render_permission(current_user, @project)
json.is_transfering @project.is_transfering
json.transfer do
json.partial! "/users/user_simple", locals: {user: @project&.applied_transfer_project&.owner}
-end
\ No newline at end of file
+end
+json.is_pinned @project.has_pinned_users.include?(current_user)
\ No newline at end of file
diff --git a/app/views/users/is_pinned_projects/index.json.jbuilder b/app/views/users/is_pinned_projects/index.json.jbuilder
new file mode 100644
index 000000000..facc158b9
--- /dev/null
+++ b/app/views/users/is_pinned_projects/index.json.jbuilder
@@ -0,0 +1,4 @@
+json.total_count @is_pinned_projects.total_count
+json.projects @is_pinned_projects.each do |project|
+ json.partial! "projects/project_detail", project: project
+end
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index ae121bb08..d61acaf8e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -266,6 +266,11 @@ Rails.application.routes.draw do
end
end
resources :headmaps, only: [:index]
+ resources :is_pinned_projects, only: [:index] do
+ collection do
+ post :pin
+ end
+ end
resources :organizations, only: [:index]
# resources :projects, only: [:index]
# resources :subjects, only: [:index]
diff --git a/db/migrate/20210527024043_create_pinned_projects.rb b/db/migrate/20210527024043_create_pinned_projects.rb
new file mode 100644
index 000000000..50345cebc
--- /dev/null
+++ b/db/migrate/20210527024043_create_pinned_projects.rb
@@ -0,0 +1,11 @@
+class CreatePinnedProjects < ActiveRecord::Migration[5.2]
+ def change
+ create_table :pinned_projects do |t|
+ t.references :user
+ t.references :project
+ t.integer :position, default: 0
+
+ t.timestamps
+ end
+ end
+end
diff --git a/public/docs/api.html b/public/docs/api.html
index 9f52b9066..9bd9bb6ea 100644
--- a/public/docs/api.html
+++ b/public/docs/api.html
@@ -331,6 +331,12 @@
获取当前登陆用户信息
+
+ 获取用户星标项目
+
+
+ 用户添加星标项目
+
获取用户贡献度
@@ -590,7 +596,7 @@ Success — a happy kitten is an authenticated kitten!
Users
获取当前登陆用户信息
@@ -652,7 +658,235 @@ Success — a happy kitten is an authenticated kitten!
-获取用户贡献度
+获取用户星标项目
+获取用户星标项目
+
+
+示例:
+
+curl -X GET http://localhost:3000/api/users/yystopf/is_pinned_projects.json
+
await octokit.request('GET /api/users/:login/is_pinned_projects.json')
+
HTTP 请求
+GET api/users/:login/is_pinned_projects.json
+返回字段说明:
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+total_count |
+int |
+星标项目数量 |
+
+
+identifier |
+string |
+项目标识 |
+
+
+name |
+string |
+项目名称 |
+
+
+description |
+string |
+项目描述 |
+
+
+visits |
+int |
+项目访问数量 |
+
+
+praises_count |
+int |
+项目点赞数量 |
+
+
+watchers_count |
+int |
+项目关注数量 |
+
+
+issues_count |
+int |
+项目issue数量 |
+
+
+pull_requests_count |
+int |
+项目合并请求数量 |
+
+
+forked_count |
+int |
+项目复刻数量 |
+
+
+is_public |
+bool |
+项目是否公开 |
+
+
+mirror_url |
+string |
+镜像地址 |
+
+
+type |
+int |
+项目类型 0 普通项目 1 普通镜像项目 2 同步镜像项目 |
+
+
+time_ago |
+string |
+上次更新时间 |
+
+
+open_devops |
+int |
+是否开启devops |
+
+
+forked_from_project_id |
+int |
+fork项目id |
+
+
+platform |
+string |
+项目平台 |
+
+
+author.name |
+string |
+项目拥有者名称 |
+
+
+author.type |
+string |
+项目拥有者类型 |
+
+
+author.login |
+string |
+项目拥有者用户名 |
+
+
+author.image_url |
+string |
+项目拥有者头像 |
+
+
+category.name |
+string |
+项目分类名称 |
+
+
+language.name |
+string |
+项目语言名称 |
+
+
+
+
+返回的JSON示例:
+
+{
+ "total_count": 1,
+ "projects": [
+ {
+ "id": 89,
+ "repo_id": 89,
+ "identifier": "monkey",
+ "name": "boke",
+ "description": "dkkd",
+ "visits": 4,
+ "praises_count": 0,
+ "watchers_count": 0,
+ "issues_count": 0,
+ "pull_requests_count": 0,
+ "forked_count": 0,
+ "is_public": true,
+ "mirror_url": "https://github.com/viletyy/monkey.git",
+ "type": 1,
+ "last_update_time": 1619685144,
+ "time_ago": "27天前",
+ "forked_from_project_id": null,
+ "open_devops": false,
+ "platform": "forge",
+ "author": {
+ "name": "测试组织",
+ "type": "Organization",
+ "login": "ceshi_org",
+ "image_url": "images/avatars/Organization/9?t=1612706073"
+ },
+ "category": {
+ "id": 3,
+ "name": "深度学习"
+ },
+ "language": {
+ "id": 2,
+ "name": "C"
+ }
+ }
+ ]
+}
+
+
+用户添加星标项目
+用户添加星标项目
+
+
+示例:
+
+curl -X POST http://localhost:3000/api/users/yystopf/is_pinned_projects/pin.json
+
await octokit.request('GET /api/users/:login/is_pinned_projects/pin.json')
+
HTTP 请求
+POST /api/users/:login/is_pinned_projects/pin.json
+请求字段说明:
同时设定多个星标项目
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+is_pinned_project_ids |
+array |
+设定为星标项目的id |
+
+
+只设定一个星标项目
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+is_pinned_project_id |
+integer |
+设定为星标项目的id |
+
+
+
+
+返回的JSON示例:
+
+{
+ "status": 0,
+ "message": "success"
+}
+
获取用户贡献度
获取用户贡献度
@@ -660,9 +894,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X GET http://localhost:3000/api/users/yystopf/headmaps.json
await octokit.request('GET /api/users/:login/headmaps.json')
-
HTTP 请求
+HTTP 请求
GET api/users/:login/headmaps.json
-返回字段说明:
+返回字段说明:
参数 |
@@ -791,9 +1025,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json
await octokit.request('GET /api/users/:login/applied_messages.json')
-
HTTP 请求
+HTTP 请求
GET /api/users/:login/applied_messages.json
-请求字段说明:
+请求字段说明:
参数 |
@@ -807,7 +1041,7 @@ Success — a happy kitten is an authenticated kitten!
用户标识 |
-返回字段说明:
+返回字段说明:
参数 |
@@ -1028,9 +1262,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X GET http://localhost:3000/api/users/yystopf/applied_transfer_projects.json
await octokit.request('GET /api/users/:login/applied_transfer_projects.json')
-
HTTP 请求
+HTTP 请求
GET /api/users/:login/applied_transfer_projects.json
-请求字段说明:
+请求字段说明:
参数 |
@@ -1044,7 +1278,7 @@ Success — a happy kitten is an authenticated kitten!
用户标识 |
-返回字段说明:
+返回字段说明:
参数 |
@@ -1220,9 +1454,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/accept.json
await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/accept.json')
-
HTTP 请求
+HTTP 请求
GET /api/users/:login/applied_transfer_projects/:id/accept.json
-请求字段说明:
+请求字段说明:
参数 |
@@ -1241,7 +1475,7 @@ Success — a happy kitten is an authenticated kitten!
迁移id |
-返回字段说明:
+返回字段说明:
参数 |
@@ -1411,9 +1645,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/refuse.json
await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refuse.json')
-
HTTP 请求
+HTTP 请求
GET /api/users/:login/applied_transfer_projects/:id/refuse.json
-请求字段说明:
+请求字段说明:
参数 |
@@ -1432,7 +1666,7 @@ Success — a happy kitten is an authenticated kitten!
迁移id |
-返回字段说明:
+返回字段说明:
参数 |
diff --git a/spec/models/pinned_project_spec.rb b/spec/models/pinned_project_spec.rb
new file mode 100644
index 000000000..cda43562e
--- /dev/null
+++ b/spec/models/pinned_project_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe PinnedProject, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end