<commit_before>{ ObjectId, signature }  = require 'bongo'
{ Module, Relationship } = require 'jraphical'
KodingError              = require '../../error'
helpers                  = require './helpers'
async                    = require 'async'
clientRequire            = require '../../clientrequire'

module.exports = class JStackTemplate extends Module

  { slugify }  = require '../../traits/slugifiable'
  { permit }   = require '../group/permissionset'
  Validators   = require '../group/validators'
  { revive, checkTemplateUsage } = require './computeutils'

  @trait __dirname, '../../traits/protected'
  @trait __dirname, '../../traits/notifiable'


  @share()

  @set

    softDelete        : yes

    permissions       :

      'create stack template'     : ['member', 'moderator']
      'list stack templates'      : ['member', 'moderator']

      'delete own stack template' : ['member', 'moderator']
      'update own stack template' : ['member', 'moderator']

      'delete stack template'     : []
      'update stack template'     : []

      'force stacks to reinit'    : []

      'check own stack usage'     : ['member']
      'check stack usage'         : []

    # we need a compound index here
    # since bongo is not supporting them
    # we need to manually define following:
    #
    #   - originId, group, slug (unique)
    #

    sharedMethods     :

      static          :
        create        :
          (signature Object, Function)
        one           : [
          (signature Object, Function)
          (signature Object, Object, Function)
        ]
        some          : [
          (signature Object, Function)
          (signature Object, Object, Function)
        ]

      instance        :
        delete        :
          (signature Function)
        setAccess     :
          (signature String, Function)
        update        :
          (signature Object, Function)
        clone         :
          (signature Function)
        generateStack :
          (signature Function)
        forceStacksToReinit :
          (signature String, Function)
        hasStacks     :
          (signature Function)

    sharedEvents      :
      static          : []
      instance        : []

    schema            :

      machines        :
        type          : [ Object ]
        _description  : 'Machines found in stack template'
        default       : -> []

      title           :
        type          : String
        required      : yes
        _description  : 'Title of this stack template'

      slug            :
        type          : String
        required      : yes
        _description  : 'Unique slug of stack template'

      description     :
        type          : String
        _description  : 'Stack template description'

      config          :
        type          : Object
        _description  : 'Private config data for stack template'

      accessLevel     :
        type          : String
        enum          : ['Wrong level specified!',
          ['private', 'group', 'public']
        ]
        default       : 'private'

      originId        :
        type          : ObjectId
        required      : yes

      meta            : require 'bongo/bundles/meta'

      group           :
        type          : String
        required      : yes

      template        :
        content       :
          type        : String
          _description: 'Stack template content in JSON format'
        sum           :
          type        : String
          _description: 'Sum of stringified JSON content, auto-generated'
        details       :
          type        : Object
          _description: 'Details data for stack template'
        rawContent    :
          type        : String
          _description: 'Stack template content in YAML format'

      # Identifiers of JCredentials
      # structured like following;
      #  { Provider: [ JCredential.identifier ] }
      #  ---
      #  {
      #    aws: [123123, 123124]
      #    github: [234234]
      #  }
      credentials     :
        type          : Object
        default       : -> {}


  generateTemplateObject = (content, rawContent, details) ->

    crypto     = require 'crypto'
    content    = ''  unless typeof content is 'string'
    rawContent = ''  unless typeof rawContent is 'string'

    details ?= {}

    return {
      content
      details
      rawContent
      sum: crypto.createHash 'sha1'
        .update content
        .digest 'hex'
    }


  updateConfigForTemplate = (config, template) ->

    supportedProviders = Object.keys (require './computeprovider').providers

    providersParser    = clientRequire 'app/lib/util/stacks/providersparser'
    requirementsParser = clientRequire 'app/lib/util/stacks/requirementsparser'

    config.groupStack       ?= no
    config.requiredData      = requirementsParser template
    config.requiredProviders = providersParser template, supportedProviders

    return config


  generateSlugFromTitle = ({ originId, group, title, index }, callback) ->

    unless title
      return callback null

    slug = _title = if index? then "#{title} #{index}" else title
    slug = slugify slug

    if index >= 20
      return callback new KodingError \
        'Too many occurrences, please provide a different title'

    JStackTemplate.count { originId, group, slug }, (err, count) ->
      return callback err  if err?

      if count is 0
        callback null, { slug, title: _title }
      else
        index ?= 0
        index += 1
        generateSlugFromTitle { originId, group, title, index }, callback


  validateTemplateData = (data, options, callback) ->

    unless data
      return callback new KodingError 'Template data is required!'

    { config = {}, title, template, rawContent, templateDetails } = data

    if template and rawContent
      template = generateTemplateObject template, rawContent, templateDetails
      if config
        config = updateConfigForTemplate config, template.rawContent
        title ?= generateTemplateTitle config.requiredProviders[0]

    { originId, group } = options
    limitConfig = helpers.getLimitConfig group
    group       = group.slug

    generateSlugFromTitle { group, originId, title }, (err, res = {}) ->

      return callback err  if err

      { slug, title } = res
      replacements = { template, config, title, slug }

      if not data.template     or \
         not limitConfig.limit or \
             limitConfig.limit is 'unlimited' # No limit, no pain.
        return callback null, replacements

      ComputeProvider = require './computeprovider'
      ComputeProvider.validateTemplateContent \
        data.template, limitConfig, (err) ->
          return callback err  if err
          callback null, replacements


  generateTemplateTitle = (provider) ->

    { capitalize } = require 'lodash'
    getPokemonName = clientRequire 'app/lib/util/getPokemonName'

    return "#{getPokemonName()} #{capitalize provider} Stack"


  # creates a JStackTemplate with requested content
  #
  # @param {Object} data
  #   data object describes new JStackTemplate
  #
  # @option data [String] template template's content as stringified JSON
  # @option data [String] title template's title
  # @option data [Object] credentials template's credentials
  # @option data [Object] config template's config
  #
  # @return {JStackTemplate} created JStackTemplate instance
  #
  # @example api
  #
  #   {
  #     "template": "{\"provider\": {\"aws\": {}}}",
  #     "title": "My AWS template",
  #     "credentials": {},
  #     "config": {}
  #   }
  #
  @create = ->
  @create = permit 'create stack template',

    success: revive

      shouldReviveClient    : yes
      shouldReviveProvider  : no
      shouldFetchGroupLimit : yes

    , (client, data, callback) ->

      { group }    = client.r # we have revived JGroup and JUser here ~ GG
      { delegate } = client.connection
      originId     = delegate.getId()
      options      = { group, originId }

      if not data.template or not data.rawContent
        return callback new KodingError 'Template content is required!'

      validateTemplateData data, options, (err, replacements) ->
        return callback err  if err

        stackTemplate = new JStackTemplate
          originId    : originId
          group       : client.context.group
          description : data.description ? ''
          machines    : data.machines    ? []
          accessLevel : data.accessLevel ? 'private'
          credentials : data.credentials ? {}

        stackTemplate.slug     = replacements.slug
        stackTemplate.title    = replacements.title

        stackTemplate.config   = replacements.config
        stackTemplate.template = replacements.template

        stackTemplate.save (err) ->
          if err
          then callback new KodingError 'Failed to save stack template', err
          else callback null, stackTemplate


  @some$: permit 'list stack templates',

    success: (client, selector, options, callback) ->

      [options, callback] = [callback, options]  unless callback
      options ?= {}

      { delegate } = client.connection

      unless typeof selector is 'object'
        return callback new KodingError 'Invalid query'

      # By default return stack templates that requester owns
      if (Object.keys selector).length is 0<commit_msg>JStackTemplate: samples: to fetch sample template for given provider<commit_after>{ ObjectId, signature } = require 'bongo'
{ Module, Relationship } = require 'jraphical'

async = require 'async'
helpers = require './helpers'
KodingError = require '../../error'
clientRequire = require '../../clientrequire'


module.exports = class JStackTemplate extends Module

  { slugify }  = require '../../traits/slugifiable'
  { permit }   = require '../group/permissionset'
  Validators   = require '../group/validators'
  { revive, checkTemplateUsage } = require './computeutils'

  @trait __dirname, '../../traits/protected'
  @trait __dirname, '../../traits/notifiable'


  @share()

  @set

    softDelete        : yes

    permissions       :

      'list defaults'             : ['guest']

      'create stack template'     : ['member', 'moderator']
      'list stack templates'      : ['member', 'moderator']

      'delete own stack template' : ['member', 'moderator']
      'update own stack template' : ['member', 'moderator']

      'delete stack template'     : []
      'update stack template'     : []

      'force stacks to reinit'    : []

      'check own stack usage'     : ['member']
      'check stack usage'         : []

    # we need a compound index here
    # since bongo is not supporting them
    # we need to manually define following:
    #
    #   - originId, group, slug (unique)
    #

    sharedMethods     :

      static          :
        create        :
          (signature Object, Function)
        samples       :
          (signature Object, Function)
        one           : [
          (signature Object, Function)
          (signature Object, Object, Function)
        ]
        some          : [
          (signature Object, Function)
          (signature Object, Object, Function)
        ]

      instance        :
        delete        :
          (signature Function)
        setAccess     :
          (signature String, Function)
        update        :
          (signature Object, Function)
        clone         :
          (signature Function)
        generateStack :
          (signature Function)
        forceStacksToReinit :
          (signature String, Function)
        hasStacks     :
          (signature Function)

    sharedEvents      :
      static          : []
      instance        : []

    schema            :

      machines        :
        type          : [ Object ]
        _description  : 'Machines found in stack template'
        default       : -> []

      title           :
        type          : String
        required      : yes
        _description  : 'Title of this stack template'

      slug            :
        type          : String
        required      : yes
        _description  : 'Unique slug of stack template'

      description     :
        type          : String
        _description  : 'Stack template description'

      config          :
        type          : Object
        _description  : 'Private config data for stack template'

      accessLevel     :
        type          : String
        enum          : ['Wrong level specified!',
          ['private', 'group', 'public']
        ]
        default       : 'private'

      originId        :
        type          : ObjectId
        required      : yes

      meta            : require 'bongo/bundles/meta'

      group           :
        type          : String
        required      : yes

      template        :
        content       :
          type        : String
          _description: 'Stack template content in JSON format'
        sum           :
          type        : String
          _description: 'Sum of stringified JSON content, auto-generated'
        details       :
          type        : Object
          _description: 'Details data for stack template'
        rawContent    :
          type        : String
          _description: 'Stack template content in YAML format'

      # Identifiers of JCredentials
      # structured like following;
      #  { Provider: [ JCredential.identifier ] }
      #  ---
      #  {
      #    aws: [123123, 123124]
      #    github: [234234]
      #  }
      credentials     :
        type          : Object
        default       : -> {}


  generateTemplateObject = (content, rawContent, details) ->

    crypto     = require 'crypto'
    content    = ''  unless typeof content is 'string'
    rawContent = ''  unless typeof rawContent is 'string'

    details ?= {}

    return {
      content
      details
      rawContent
      sum: crypto.createHash 'sha1'
        .update content
        .digest 'hex'
    }


  updateConfigForTemplate = (config, template) ->

    supportedProviders = Object.keys (require './computeprovider').providers

    providersParser    = clientRequire 'app/lib/util/stacks/providersparser'
    requirementsParser = clientRequire 'app/lib/util/stacks/requirementsparser'

    config.groupStack       ?= no
    config.requiredData      = requirementsParser template
    config.requiredProviders = providersParser template, supportedProviders

    return config


  generateSlugFromTitle = ({ originId, group, title, index }, callback) ->

    unless title
      return callback null

    slug = _title = if index? then "#{title} #{index}" else title
    slug = slugify slug

    if index >= 20
      return callback new KodingError \
        'Too many occurrences, please provide a different title'

    JStackTemplate.count { originId, group, slug }, (err, count) ->
      return callback err  if err?

      if count is 0
        callback null, { slug, title: _title }
      else
        index ?= 0
        index += 1
        generateSlugFromTitle { originId, group, title, index }, callback


  validateTemplateData = (data, options, callback) ->

    unless data
      return callback new KodingError 'Template data is required!'

    { config = {}, title, template, rawContent, templateDetails } = data

    if template and rawContent
      template = generateTemplateObject template, rawContent, templateDetails
      if config
        config = updateConfigForTemplate config, template.rawContent
        title ?= generateTemplateTitle config.requiredProviders[0]

    { originId, group } = options
    limitConfig = helpers.getLimitConfig group
    group       = group.slug

    generateSlugFromTitle { group, originId, title }, (err, res = {}) ->

      return callback err  if err

      { slug, title } = res
      replacements = { template, config, title, slug }

      if not data.template     or \
         not limitConfig.limit or \
             limitConfig.limit is 'unlimited' # No limit, no pain.
        return callback null, replacements

      ComputeProvider = require './computeprovider'
      ComputeProvider.validateTemplateContent \
        data.template, limitConfig, (err) ->
          return callback err  if err
          callback null, replacements


  generateTemplateTitle = (provider) ->

    { capitalize } = require 'lodash'
    getPokemonName = clientRequire 'app/lib/util/getPokemonName'

    return "#{getPokemonName()} #{capitalize provider} Stack"


  # creates a JStackTemplate with requested content
  #
  # @param {Object} data
  #   data object describes new JStackTemplate
  #
  # @option data [String] template template's content as stringified JSON
  # @option data [String] title template's title
  # @option data [Object] credentials template's credentials
  # @option data [Object] config template's config
  #
  # @return {JStackTemplate} created JStackTemplate instance
  #
  # @example api
  #
  #   {
  #     "template": "{\"provider\": {\"aws\": {}}}",
  #     "title": "My AWS template",
  #     "credentials": {},
  #     "config": {}
  #   }
  #
  @create = ->
  @create = permit 'create stack template',

    success: revive

      shouldReviveClient    : yes
      shouldReviveProvider  : no
      shouldFetchGroupLimit : yes

    , (client, data, callback) ->

      { group }    = client.r # we have revived JGroup and JUser here ~ GG
      { delegate } = client.connection
      originId     = delegate.getId()
      options      = { group, originId }

      if not data.template or not data.rawContent
        return callback new KodingError 'Template content is required!'

      validateTemplateData data, options, (err, replacements) ->
        return callback err  if err

        stackTemplate = new JStackTemplate
          originId    : originId
          group       : client.context.group
          description : data.description ? ''
          machines    : data.machines    ? []
          accessLevel : data.accessLevel ? 'private'
          credentials : data.credentials ? {}

        stackTemplate.slug     = replacements.slug
        stackTemplate.title    = replacements.title

        stackTemplate.config   = replacements.config
        stackTemplate.template = replacements.template

        stackTemplate.save (err) ->
          if err
          then callback new KodingError 'Failed to save stack template', err
          else callback null, stackTemplate


  @samples = permit 'list defaults',

    success: revive

      shouldReviveClient    : no
      shouldReviveProvider  : yes

    , (client, options, callback) ->

      { provider, useDefaults = no } = options

      if useDefaults
      then callback null, provider.templateWithDefaults
      else callback null, provider.template


  @some$: permit 'list stack templates',

    success: (client, selector, options, callback) ->

      [options, callback] = [callback, options]  unless callback
      options ?= {}

      { delegate } = client.connection

      unless typeof selector is 'object'
        return callback new KodingError 'Invalid query'

      # By default return stack templates that requester owns
      if (Object.keys selector).length is 0
{'fraction_non_alphanumeric': 0.08797882279663656, 'fraction_numerical': 0.002699055330634278, 'mean_word_length': 1.9042809767862527, 'pattern_counts': {'":': 12, '<': 3, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5137145', 'n_tokens_mistral': 5048, 'n_tokens_neox': 4788, 'n_words': 1499}