Add user projects API
This commit is contained in:
parent
462b1ed99d
commit
8d44d5142a
6 changed files with 261 additions and 54 deletions
|
@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder
|
|||
end
|
||||
|
||||
def execute
|
||||
collection = init_collection
|
||||
user = params.delete(:user)
|
||||
collection =
|
||||
if user
|
||||
PersonalProjectsFinder.new(user).execute(current_user)
|
||||
else
|
||||
init_collection
|
||||
end
|
||||
|
||||
collection = by_ids(collection)
|
||||
collection = by_personal(collection)
|
||||
collection = by_starred(collection)
|
||||
|
|
4
changelogs/unreleased/33657-user-projects-api.yml
Normal file
4
changelogs/unreleased/33657-user-projects-api.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add user projects API
|
||||
merge_request: 12596
|
||||
author: Ivan Chernov
|
|
@ -173,6 +173,164 @@ Parameters:
|
|||
]
|
||||
```
|
||||
|
||||
### List a user's projects
|
||||
|
||||
Get a list of visible projects for the given user. When accessed without authentication, only public projects are returned.
|
||||
|
||||
```
|
||||
GET /users/:user_id/projects
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user_id` | string | yes | The ID or username of the user |
|
||||
| `archived` | boolean | no | Limit by archived status |
|
||||
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
|
||||
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `search` | string | no | Return list of projects matching the search criteria |
|
||||
| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
|
||||
| `owned` | boolean | no | Limit by projects owned by the current user |
|
||||
| `membership` | boolean | no | Limit by projects that the current user is a member of |
|
||||
| `starred` | boolean | no | Limit by projects starred by the current user |
|
||||
| `statistics` | boolean | no | Include project statistics |
|
||||
| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
|
||||
| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 4,
|
||||
"description": null,
|
||||
"default_branch": "master",
|
||||
"visibility": "private",
|
||||
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
|
||||
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
|
||||
"web_url": "http://example.com/diaspora/diaspora-client",
|
||||
"tag_list": [
|
||||
"example",
|
||||
"disapora client"
|
||||
],
|
||||
"owner": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"created_at": "2013-09-30T13:46:02Z"
|
||||
},
|
||||
"name": "Diaspora Client",
|
||||
"name_with_namespace": "Diaspora / Diaspora Client",
|
||||
"path": "diaspora-client",
|
||||
"path_with_namespace": "diaspora/diaspora-client",
|
||||
"issues_enabled": true,
|
||||
"open_issues_count": 1,
|
||||
"merge_requests_enabled": true,
|
||||
"jobs_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"container_registry_enabled": false,
|
||||
"created_at": "2013-09-30T13:46:02Z",
|
||||
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"id": 3,
|
||||
"name": "Diaspora",
|
||||
"path": "diaspora",
|
||||
"kind": "group",
|
||||
"full_path": "diaspora"
|
||||
},
|
||||
"import_status": "none",
|
||||
"archived": false,
|
||||
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
|
||||
"shared_runners_enabled": true,
|
||||
"forks_count": 0,
|
||||
"star_count": 0,
|
||||
"runners_token": "b8547b1dc37721d05889db52fa2f02",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": false,
|
||||
"request_access_enabled": false,
|
||||
"statistics": {
|
||||
"commit_count": 37,
|
||||
"storage_size": 1038090,
|
||||
"repository_size": 1038090,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"description": null,
|
||||
"default_branch": "master",
|
||||
"visibility": "private",
|
||||
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
|
||||
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
|
||||
"web_url": "http://example.com/brightbox/puppet",
|
||||
"tag_list": [
|
||||
"example",
|
||||
"puppet"
|
||||
],
|
||||
"owner": {
|
||||
"id": 4,
|
||||
"name": "Brightbox",
|
||||
"created_at": "2013-09-30T13:46:02Z"
|
||||
},
|
||||
"name": "Puppet",
|
||||
"name_with_namespace": "Brightbox / Puppet",
|
||||
"path": "puppet",
|
||||
"path_with_namespace": "brightbox/puppet",
|
||||
"issues_enabled": true,
|
||||
"open_issues_count": 1,
|
||||
"merge_requests_enabled": true,
|
||||
"jobs_enabled": true,
|
||||
"wiki_enabled": true,
|
||||
"snippets_enabled": false,
|
||||
"container_registry_enabled": false,
|
||||
"created_at": "2013-09-30T13:46:02Z",
|
||||
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||
"creator_id": 3,
|
||||
"namespace": {
|
||||
"id": 4,
|
||||
"name": "Brightbox",
|
||||
"path": "brightbox",
|
||||
"kind": "group",
|
||||
"full_path": "brightbox"
|
||||
},
|
||||
"import_status": "none",
|
||||
"import_error": null,
|
||||
"permissions": {
|
||||
"project_access": {
|
||||
"access_level": 10,
|
||||
"notification_level": 3
|
||||
},
|
||||
"group_access": {
|
||||
"access_level": 50,
|
||||
"notification_level": 3
|
||||
}
|
||||
},
|
||||
"archived": false,
|
||||
"avatar_url": null,
|
||||
"shared_runners_enabled": true,
|
||||
"forks_count": 0,
|
||||
"star_count": 0,
|
||||
"runners_token": "b8547b1dc37721d05889db52fa2f02",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": false,
|
||||
"request_access_enabled": false,
|
||||
"statistics": {
|
||||
"commit_count": 12,
|
||||
"storage_size": 2066080,
|
||||
"repository_size": 2066080,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get single project
|
||||
|
||||
Get a specific project. This endpoint can be accessed without authentication if
|
||||
|
|
|
@ -268,6 +268,7 @@ module API
|
|||
finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
|
||||
finder_params[:archived] = params[:archived]
|
||||
finder_params[:search] = params[:search] if params[:search]
|
||||
finder_params[:user] = params.delete(:user) if params[:user]
|
||||
finder_params
|
||||
end
|
||||
|
||||
|
@ -313,7 +314,7 @@ module API
|
|||
|
||||
def present_artifacts!(artifacts_file)
|
||||
return not_found! unless artifacts_file.exists?
|
||||
|
||||
|
||||
if artifacts_file.file_storage?
|
||||
present_file!(artifacts_file.path, artifacts_file.filename)
|
||||
else
|
||||
|
|
|
@ -35,61 +35,78 @@ module API
|
|||
params :statistics_params do
|
||||
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
|
||||
end
|
||||
|
||||
params :collection_params do
|
||||
use :sort_params
|
||||
use :filter_params
|
||||
use :pagination
|
||||
|
||||
optional :simple, type: Boolean, default: false,
|
||||
desc: 'Return only the ID, URL, name, and path of each project'
|
||||
end
|
||||
|
||||
params :sort_params do
|
||||
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
|
||||
default: 'created_at', desc: 'Return projects ordered by field'
|
||||
optional :sort, type: String, values: %w[asc desc], default: 'desc',
|
||||
desc: 'Return projects sorted in ascending and descending order'
|
||||
end
|
||||
|
||||
params :filter_params do
|
||||
optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
|
||||
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'Limit by visibility'
|
||||
optional :search, type: String, desc: 'Return list of projects matching the search criteria'
|
||||
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
|
||||
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
|
||||
optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
|
||||
optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
|
||||
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
|
||||
end
|
||||
|
||||
params :create_params do
|
||||
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
|
||||
optional :import_url, type: String, desc: 'URL from which the project is imported'
|
||||
end
|
||||
|
||||
def present_projects(options = {})
|
||||
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
|
||||
projects = reorder_projects(projects)
|
||||
projects = projects.with_statistics if params[:statistics]
|
||||
projects = projects.with_issues_enabled if params[:with_issues_enabled]
|
||||
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
|
||||
|
||||
options = options.reverse_merge(
|
||||
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
|
||||
statistics: params[:statistics],
|
||||
current_user: current_user
|
||||
)
|
||||
options[:with] = Entities::BasicProjectDetails if params[:simple]
|
||||
|
||||
present paginate(projects), options
|
||||
end
|
||||
end
|
||||
|
||||
resource :users, requirements: { user_id: %r{[^/]+} } do
|
||||
desc 'Get a user projects' do
|
||||
success Entities::BasicProjectDetails
|
||||
end
|
||||
params do
|
||||
requires :user_id, type: String, desc: 'The ID or username of the user'
|
||||
use :collection_params
|
||||
use :statistics_params
|
||||
end
|
||||
get ":user_id/projects" do
|
||||
user = find_user(params[:user_id])
|
||||
not_found!('User') unless user
|
||||
|
||||
params[:user] = user
|
||||
|
||||
present_projects
|
||||
end
|
||||
end
|
||||
|
||||
resource :projects do
|
||||
helpers do
|
||||
params :collection_params do
|
||||
use :sort_params
|
||||
use :filter_params
|
||||
use :pagination
|
||||
|
||||
optional :simple, type: Boolean, default: false,
|
||||
desc: 'Return only the ID, URL, name, and path of each project'
|
||||
end
|
||||
|
||||
params :sort_params do
|
||||
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
|
||||
default: 'created_at', desc: 'Return projects ordered by field'
|
||||
optional :sort, type: String, values: %w[asc desc], default: 'desc',
|
||||
desc: 'Return projects sorted in ascending and descending order'
|
||||
end
|
||||
|
||||
params :filter_params do
|
||||
optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
|
||||
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'Limit by visibility'
|
||||
optional :search, type: String, desc: 'Return list of projects matching the search criteria'
|
||||
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
|
||||
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
|
||||
optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
|
||||
optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
|
||||
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
|
||||
end
|
||||
|
||||
params :create_params do
|
||||
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
|
||||
optional :import_url, type: String, desc: 'URL from which the project is imported'
|
||||
end
|
||||
|
||||
def present_projects(options = {})
|
||||
projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
|
||||
projects = reorder_projects(projects)
|
||||
projects = projects.with_statistics if params[:statistics]
|
||||
projects = projects.with_issues_enabled if params[:with_issues_enabled]
|
||||
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
|
||||
|
||||
options = options.reverse_merge(
|
||||
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
|
||||
statistics: params[:statistics],
|
||||
current_user: current_user
|
||||
)
|
||||
options[:with] = Entities::BasicProjectDetails if params[:simple]
|
||||
|
||||
present paginate(projects), options
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get a list of visible projects for authenticated user' do
|
||||
success Entities::BasicProjectDetails
|
||||
end
|
||||
|
|
|
@ -475,6 +475,26 @@ describe API::Projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /users/:user_id/projects/' do
|
||||
let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
|
||||
|
||||
it 'returns error when user not found' do
|
||||
get api('/users/9999/projects/')
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
expect(json_response['message']).to eq('404 User Not Found')
|
||||
end
|
||||
|
||||
it 'returns projects filtered by user' do
|
||||
get api("/users/#{user4.id}/projects/", user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /projects/user/:id' do
|
||||
before do
|
||||
expect(project).to be_persisted
|
||||
|
|
Loading…
Reference in a new issue