File: create_service.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (132 lines) | stat: -rw-r--r-- 3,912 bytes parent folder | download
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
# frozen_string_literal: true

module Snippets
  class CreateService < Snippets::BaseService
    include Gitlab::InternalEventsTracking

    def initialize(project:, current_user: nil, params: {}, perform_spam_check: true)
      super(project: project, current_user: current_user, params: params)
      @perform_spam_check = perform_spam_check
    end

    def execute
      @snippet = build_from_params

      return invalid_params_error(@snippet) unless valid_params?

      unless visibility_allowed?(snippet.visibility_level)
        return forbidden_visibility_error(snippet)
      end

      @snippet.author = current_user

      if perform_spam_check
        @snippet.check_for_spam(user: current_user, action: :create, extra_features: { files: file_paths_to_commit })
      end

      if save_and_commit
        UserAgentDetailService.new(spammable: @snippet, perform_spam_check: perform_spam_check).create
        track_internal_event('create_snippet', project: project, user: current_user)

        move_temporary_files

        ServiceResponse.success(payload: { snippet: @snippet })
      else
        snippet_error_response(@snippet, 400)
      end
    end

    private

    attr_reader :snippet, :perform_spam_check

    # If the snippet is a "project snippet", specifically set it to nil to override the default database value of 1.
    # We only want organization_id on the PersonalSnippet subclass.
    #
    # See https://gitlab.com/gitlab-org/gitlab/-/issues/460827
    def build_from_params
      if project
        project.snippets.build(
          create_params.merge(organization_id: nil)
        )
      else
        PersonalSnippet.new(
          create_params.merge(organization_id: organization_id)
        )
      end
    end

    def organization_id
      params[:organization_id].presence || Organizations::Organization::DEFAULT_ORGANIZATION_ID
    end

    # If the snippet_actions param is present
    # we need to fill content and file_name from
    # the model
    def create_params
      return params if snippet_actions.empty?

      params.merge(content: snippet_actions[0].content, file_name: snippet_actions[0].file_path)
    end

    def save_and_commit
      snippet_saved = @snippet.save

      if snippet_saved
        create_repository
        create_commit
      end

      snippet_saved
    rescue StandardError => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ...
      Gitlab::ErrorTracking.log_exception(e, service: 'Snippets::CreateService')

      # If the commit action failed we need to remove the repository if exists
      delete_repository(@snippet) if @snippet.repository_exists?

      # If the snippet was created, we need to remove it as we
      # would do like if it had had any validation error
      # and reassign a dupe so we don't return the deleted snippet
      if @snippet.persisted?
        @snippet.delete
        @snippet = @snippet.dup
      end

      add_snippet_repository_error(snippet: @snippet, error: e)

      false
    end

    def create_repository
      @snippet.create_repository

      raise CreateRepositoryError, 'Repository could not be created' unless @snippet.repository_exists?
    end

    def create_commit
      attrs = commit_attrs(@snippet, INITIAL_COMMIT_MSG)

      @snippet.snippet_repository.multi_files_action(current_user, files_to_commit(@snippet), **attrs)
    end

    def move_temporary_files
      return unless @snippet.is_a?(PersonalSnippet)

      uploaded_assets.each do |file|
        FileMover.new(file, from_model: current_user, to_model: @snippet).execute
      end
    end

    def build_actions_from_params(_snippet)
      [{ file_path: params[:file_name], content: params[:content] }]
    end

    def restricted_files_actions
      :create
    end

    def commit_attrs(snippet, msg)
      super.merge(skip_target_sha: true)
    end
  end
end