summaryrefslogtreecommitdiff
path: root/lib/api/notes.rb
blob: d6e7203adafd894361d8ed65620d68114a366bdf (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
149
150
151
152
module API
  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: API::PROJECT_ENDPOINT_REQUIREMENTS 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 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 = find_project_noteable(noteables_str, 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: Entities::Note
          else
            not_found!("Notes")
          end
        end

        desc 'Get a single +noteable+ note' do
          success 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 = find_project_noteable(noteables_str, 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: Entities::Note
          else
            not_found!("Note")
          end
        end

        desc 'Create a new +noteable+ note' do
          success 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
          noteable = find_project_noteable(noteables_str, params[:noteable_id])

          opts = {
            note: params[:body],
            noteable_type: noteables_str.classify,
            noteable_id: 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: 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 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: Entities::Note
          else
            render_api_error!("Failed to save note #{note.errors.messages}", 400)
          end
        end

        desc 'Delete a +noteable+ note' do
          success 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

          destroy_conditionally!(note) do |note|
            ::Notes::DestroyService.new(user_project, current_user).execute(note)
          end
        end
      end
    end

    helpers do
      def find_project_noteable(noteables_str, noteable_id)
        public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
      end

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