summaryrefslogtreecommitdiff
path: root/lib/api/v3/notes.rb
blob: 009ec5c6bbdb6026c7f1bc40909499a8a03e2a4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
module API
  module V3
    class Notes < Grape::API
      include PaginationParams

      before { authenticate! }

      NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze

      params do
        requires :id, type: String, desc: 'The ID of a project'
      end
      resource :projects, requirements: { id: %r{[^/]+} } do
        NOTEABLE_TYPES.each do |noteable_type|
          noteables_str = noteable_type.to_s.underscore.pluralize

          desc 'Get a list of project +noteable+ notes' do
            success ::API::V3::Entities::Note
          end
          params do
            requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
            use :pagination
          end
          get ":id/#{noteables_str}/:noteable_id/notes" do
            noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])

            if can?(current_user, noteable_read_ability_name(noteable), noteable)
              # We exclude notes that are cross-references and that cannot be viewed
              # by the current user. By doing this exclusion at this level and not
              # at the DB query level (which we cannot in that case), the current
              # page can have less elements than :per_page even if
              # there's more than one page.
              notes =
                # paginate() only works with a relation. This could lead to a
                # mismatch between the pagination headers info and the actual notes
                # array returned, but this is really a edge-case.
                paginate(noteable.notes).
                reject { |n| n.cross_reference_not_visible_for?(current_user) }
              present notes, with: ::API::V3::Entities::Note
            else
              not_found!("Notes")
            end
          end

          desc 'Get a single +noteable+ note' do
            success ::API::V3::Entities::Note
          end
          params do
            requires :note_id, type: Integer, desc: 'The ID of a note'
            requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
          end
          get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
            noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
            note = noteable.notes.find(params[:note_id])
            can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)

            if can_read_note
              present note, with: ::API::V3::Entities::Note
            else
              not_found!("Note")
            end
          end

          desc 'Create a new +noteable+ note' do
            success ::API::V3::Entities::Note
          end
          params do
            requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
            requires :body, type: String, desc: 'The content of a note'
            optional :created_at, type: String, desc: 'The creation date of the note'
          end
          post ":id/#{noteables_str}/:noteable_id/notes" do
            opts = {
              note: params[:body],
              noteable_type: noteables_str.classify,
              noteable_id: params[:noteable_id]
            }

            noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])

            if can?(current_user, noteable_read_ability_name(noteable), noteable)
              if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
                opts[:created_at] = params[:created_at]
              end

              note = ::Notes::CreateService.new(user_project, current_user, opts).execute
              if note.valid?
                present note, with: ::API::V3::Entities.const_get(note.class.name)
              else
                not_found!("Note #{note.errors.messages}")
              end
            else
              not_found!("Note")
            end
          end

          desc 'Update an existing +noteable+ note' do
            success ::API::V3::Entities::Note
          end
          params do
            requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
            requires :note_id, type: Integer, desc: 'The ID of a note'
            requires :body, type: String, desc: 'The content of a note'
          end
          put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
            note = user_project.notes.find(params[:note_id])

            authorize! :admin_note, note

            opts = {
              note: params[:body]
            }

            note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note)

            if note.valid?
              present note, with: ::API::V3::Entities::Note
            else
              render_api_error!("Failed to save note #{note.errors.messages}", 400)
            end
          end

          desc 'Delete a +noteable+ note' do
            success ::API::V3::Entities::Note
          end
          params do
            requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
            requires :note_id, type: Integer, desc: 'The ID of a note'
          end
          delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
            note = user_project.notes.find(params[:note_id])
            authorize! :admin_note, note

            ::Notes::DestroyService.new(user_project, current_user).execute(note)

            present note, with: ::API::V3::Entities::Note
          end
        end
      end

      helpers do
        def noteable_read_ability_name(noteable)
          "read_#{noteable.class.to_s.underscore}".to_sym
        end
      end
    end
  end
end