<commit_before>'use strict'
let component = require('omniscient')
let h = require('react-hyperscript')
let R = require('ramda')
let logger = require('js-logger-aknudsen').get('createProject')
let S = require('underscore.string.fp')
let React = require('react')
let ReactAutoComplete = React.createFactory(require('@arve.knudsen/react-autocomplete'))
let immstruct = require('immstruct')
let Promise = require('bluebird')

let userManagement = require('../../userManagement')
let licenses = require('../../licenses')
let FocusingInput = require('../focusingInput')
let {nbsp,} = require('../../specialChars')
let ajax = require('../../ajax')
let {Loading, InputLoading,} = require('./loading')
let {DescriptionEditor, InstructionsEditor, PicturesEditor,
  FilesEditor,} = require('./editors')
let {trimWhitespace,} = require('../../stringUtils')
let {InvalidProjectId, InvalidTag,} = require('../../validators')

let uploadProject
let router
if (__IS_BROWSER__) {
  uploadProject = require('./uploadProject')
  router = require('../../router')

  require('./editAndCreate.styl')
  require('./createProject.styl')
  require('dropzone/src/dropzone.scss')
  require('../dropzone.styl')
}

let createProject = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let input = createCursor.toJS()
  let username = userManagement.getLoggedInUser(cursor).username

  let inputExtended = R.merge(input, {
    projectId: input.id,
    owner: username,
  })

  uploadProject(inputExtended, createCursor, cursor)
    .then(({title, description, instructions, tags, licenseId, username, pictureFiles,
        files,}) => {
      logger.debug(`Picture files:`, pictureFiles)
      logger.debug(`Files:`, files)
      logger.debug(`title: ${title}, description: ${description}, tags: ${S.join(`,`, tags)}`)
      let qualifiedProjectId = `${username}/${input.id}`
      let data = {
        id: input.id,
        title,
        description,
        instructions,
        tags,
        licenseId,
        pictures: pictureFiles,
        files,
      }
      logger.debug(`Creating project '${qualifiedProjectId}'...:`, data)
      cursor.cursor('createProject').set('isLoading', 'Saving project...')
      ajax.postJson(`/api/projects/${username}`, data)
        .then(() => {
          logger.info(`Successfully created project '${qualifiedProjectId}' on server`)
          router.goTo(`/u/${qualifiedProjectId}`)
        }, (error) => {
          cursor.cursor('createProject').set('isLoading', false)
          logger.warn(`Failed to create project '${qualifiedProjectId}' on server: ${error}`,
            error.stack)
        })
      }, (error) => {
        logger.warn(`Uploading files/pictures failed: ${error}`, error.stack)
        cursor.cursor('createProject').set('isLoading', false)
      })
}

let createProjectFromGitHub = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let repositoryName = createCursor.get(`gitHubRepositoryName`)
  let username = userManagement.getLoggedInUser(cursor).username
  let [gitHubOwner, gitHubProject,] = S.wordsDelim('/', repositoryName)
  ajax.postJson(`/api/projects/${username}`, {
    gitHubOwner,
    gitHubProject,
  })
    .then(({qualifiedProjectId,}) => {
      logger.info(
        `Successfully created project '${qualifiedProjectId}' from GitHub repository` +
          `'${repositoryName}' on server`)
      router.goTo(`/u/${qualifiedProjectId}`)
    })
}

let renderFieldError = (errors, fieldName) => {
  let errorText = errors[fieldName]
  return errorText != null ? h(`#${fieldName}-error.field-error`, `* ${errorText}`) : null
}

let inputChangeHandler = (fieldName, handler) => {
  return (event) => {
    let createCursor = immstruct.instance('state').reference('createProject').cursor()
    createCursor = createCursor.set('isReady', false)
    Promise.method(handler)(event, createCursor)
      .then((validationError) => {
        logger.debug(`Input change handler completed, validation error: ${validationError}`)
        createCursor = immstruct.instance('state').reference('createProject').cursor()
        createCursor = createCursor.update((current) => {
          if (validationError != null) {
            current = current.setIn(['errors', fieldName,], validationError)
          } else {
            current = current.deleteIn(['errors', fieldName,])
          }

          let currentErrors = current.get('errors').toJS()
          if (R.isEmpty(currentErrors)) {
            logger.debug(`No input errors detected, enabling project creation`, currentErrors)
            current = current.set('isReady', true)
          } else {
            logger.debug(`Input errors detected, not enabling project creation`, currentErrors)
            current = current.set('isReady', false)
          }
          return current
        })
      })
  }
}

let checkProjectIdTimeout = null

let renderCreateStandaloneProject = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let input = createCursor.toJS()
  let errors = input.errors
  logger.debug(`Rendering validation errors:`, errors)
  return [
    h('.input-group', [
      FocusingInput({
        id: 'id-input', placeholder: 'Project ID',
        value: input.id,
        onChange: inputChangeHandler('id', (event, createCursor) => {
          if (checkProjectIdTimeout != null) {
            clearTimeout(checkProjectIdTimeout)
          }

          let projectId = trimWhitespace(event.target.value)
          logger.debug(`Project ID changed: '${projectId}'`)
          createCursor = createCursor.set('id', projectId)

          let projectIdValidator = new InvalidProjectId(projectId)
          if (projectIdValidator.isInvalid) {
            return projectIdValidator.errorText
          } else {
            if (!S.isBlank(projectId)) {
              return new Promise((resolve, reject) => {
                checkProjectIdTimeout = setTimeout(() => {
                  createCursor.set(`isCheckingId`, true)
                  logger.debug(`Checking whether project ID ${projectId} is available`)
                  ajax.getJson(`/api/isProjectIdAvailable?projectId=${projectId}`)
                    .then((isAvailable) => {
                      if (isAvailable) {
                        logger.debug(`Project ID ${projectId} is available`)
                        resolve()
                      } else {
                        logger.debug(`Project ID ${projectId} is not available`)
                        resolve(`ID ${projectId} is not available`)
                      }
                    }, reject)
                    .finally(() => {
                      createCursor.set(`isCheckingId`, false)
                    })
                }, 500)
              })
            } else {
              return `ID must be filled in`
            }
          }
        }),
      }),
      input.isCheckingId ? InputLoading() : null,
      renderFieldError(errors, 'id'),
    ]),
    h('.input-group', [
      h('input#title-input', {
        type: 'text',
        placeholder: 'Project title',
        value: input.title,
        onChange: inputChangeHandler('title', (event, createCursor) => {
          let title = trimWhitespace(event.target.value)
          logger.debug(`Project title changed: '${title}'`)
          createCursor.set('title', title)
          if (S.isBlank(title)) {
            return `Title must be filled in`
          } else {
            return null
          }
        }),
      }),
      renderFieldError(errors, 'title'),
    ]),
    h('.input-group', [
      h('input#tags-input', {<commit_msg>Fix project ID validation<commit_after>'use strict'
let component = require('omniscient')
let h = require('react-hyperscript')
let R = require('ramda')
let logger = require('js-logger-aknudsen').get('createProject')
let S = require('underscore.string.fp')
let React = require('react')
let ReactAutoComplete = React.createFactory(require('@arve.knudsen/react-autocomplete'))
let immstruct = require('immstruct')
let Promise = require('bluebird')

let userManagement = require('../../userManagement')
let licenses = require('../../licenses')
let FocusingInput = require('../focusingInput')
let {nbsp,} = require('../../specialChars')
let ajax = require('../../ajax')
let {Loading, InputLoading,} = require('./loading')
let {DescriptionEditor, InstructionsEditor, PicturesEditor,
  FilesEditor,} = require('./editors')
let {trimWhitespace,} = require('../../stringUtils')
let {InvalidProjectId, InvalidTag,} = require('../../validators')

let uploadProject
let router
if (__IS_BROWSER__) {
  uploadProject = require('./uploadProject')
  router = require('../../router')

  require('./editAndCreate.styl')
  require('./createProject.styl')
  require('dropzone/src/dropzone.scss')
  require('../dropzone.styl')
}

let createProject = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let input = createCursor.toJS()
  let username = userManagement.getLoggedInUser(cursor).username

  let inputExtended = R.merge(input, {
    projectId: input.id,
    owner: username,
  })

  uploadProject(inputExtended, createCursor, cursor)
    .then(({title, description, instructions, tags, licenseId, username, pictureFiles,
        files,}) => {
      logger.debug(`Picture files:`, pictureFiles)
      logger.debug(`Files:`, files)
      logger.debug(`title: ${title}, description: ${description}, tags: ${S.join(`,`, tags)}`)
      let qualifiedProjectId = `${username}/${input.id}`
      let data = {
        id: input.id,
        title,
        description,
        instructions,
        tags,
        licenseId,
        pictures: pictureFiles,
        files,
      }
      logger.debug(`Creating project '${qualifiedProjectId}'...:`, data)
      cursor.cursor('createProject').set('isLoading', 'Saving project...')
      ajax.postJson(`/api/projects/${username}`, data)
        .then(() => {
          logger.info(`Successfully created project '${qualifiedProjectId}' on server`)
          router.goTo(`/u/${qualifiedProjectId}`)
        }, (error) => {
          cursor.cursor('createProject').set('isLoading', false)
          logger.warn(`Failed to create project '${qualifiedProjectId}' on server: ${error}`,
            error.stack)
        })
      }, (error) => {
        logger.warn(`Uploading files/pictures failed: ${error}`, error.stack)
        cursor.cursor('createProject').set('isLoading', false)
      })
}

let createProjectFromGitHub = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let repositoryName = createCursor.get(`gitHubRepositoryName`)
  let username = userManagement.getLoggedInUser(cursor).username
  let [gitHubOwner, gitHubProject,] = S.wordsDelim('/', repositoryName)
  ajax.postJson(`/api/projects/${username}`, {
    gitHubOwner,
    gitHubProject,
  })
    .then(({qualifiedProjectId,}) => {
      logger.info(
        `Successfully created project '${qualifiedProjectId}' from GitHub repository` +
          `'${repositoryName}' on server`)
      router.goTo(`/u/${qualifiedProjectId}`)
    })
}

let renderFieldError = (errors, fieldName) => {
  let errorText = errors[fieldName]
  return errorText != null ? h(`#${fieldName}-error.field-error`, `* ${errorText}`) : null
}

let inputChangeHandler = (fieldName, handler) => {
  return (event) => {
    let createCursor = immstruct.instance('state').reference('createProject').cursor()
    createCursor = createCursor.set('isReady', false)
    Promise.method(handler)(event, createCursor)
      .then((validationError) => {
        logger.debug(`Input change handler completed, validation error: ${validationError}`)
        createCursor = immstruct.instance('state').reference('createProject').cursor()
        createCursor = createCursor.update((current) => {
          if (validationError != null) {
            current = current.setIn(['errors', fieldName,], validationError)
          } else {
            current = current.deleteIn(['errors', fieldName,])
          }

          let currentErrors = current.get('errors').toJS()
          if (R.isEmpty(currentErrors)) {
            logger.debug(`No input errors detected, enabling project creation`, currentErrors)
            current = current.set('isReady', true)
          } else {
            logger.debug(`Input errors detected, not enabling project creation`, currentErrors)
            current = current.set('isReady', false)
          }
          return current
        })
      })
  }
}

let checkProjectIdTimeout = null

let renderCreateStandaloneProject = (cursor) => {
  let createCursor = cursor.cursor('createProject')
  let input = createCursor.toJS()
  let errors = input.errors
  logger.debug(`Rendering validation errors:`, errors)
  return [
    h('.input-group', [
      FocusingInput({
        id: 'id-input', placeholder: 'Project ID',
        value: input.id,
        onChange: inputChangeHandler('id', (event, createCursor) => {
          if (checkProjectIdTimeout != null) {
            clearTimeout(checkProjectIdTimeout)
          }

          let projectId = trimWhitespace(event.target.value)
          logger.debug(`Project ID changed: '${projectId}'`)
          createCursor = createCursor.set('id', projectId)

          if (S.isBlank(projectId)) {
            return `ID must be filled in`
          } else {
            let projectIdValidator = new InvalidProjectId(projectId)
            if (projectIdValidator.isInvalid) {
              return projectIdValidator.errorText
            } else {
              if (!S.isBlank(projectId)) {
                return new Promise((resolve, reject) => {
                  checkProjectIdTimeout = setTimeout(() => {
                    createCursor.set(`isCheckingId`, true)
                    logger.debug(`Checking whether project ID ${projectId} is available`)
                    ajax.getJson(`/api/isProjectIdAvailable?projectId=${projectId}`)
                      .then((isAvailable) => {
                        if (isAvailable) {
                          logger.debug(`Project ID ${projectId} is available`)
                          resolve()
                        } else {
                          logger.debug(`Project ID ${projectId} is not available`)
                          resolve(`ID ${projectId} is not available`)
                        }
                      }, reject)
                      .finally(() => {
                        createCursor.set(`isCheckingId`, false)
                      })
                  }, 500)
                })
              }
            }
          }
        }),
      }),
      input.isCheckingId ? InputLoading() : null,
      renderFieldError(errors, 'id'),
    ]),
    h('.input-group', [
      h('input#title-input', {
        type: 'text',
        placeholder: 'Project title',
        value: input.title,
        onChange: inputChangeHandler('title', (event, createCursor) => {
          let title = trimWhitespace(event.target.value)
          logger.debug(`Project title changed: '${title}'`)
          createCursor.set('title', title)
          if (S.isBlank(title)) {
            return `Title must be filled in`
          } else {
            return null
          }
        }),
      }),
      renderFieldError(errors, 'title'),
    ]),
    h('.input-group', [
      h('input#tags-input', {
{'fraction_non_alphanumeric': 0.14424709416220452, 'fraction_numerical': 0.00039179835444691133, 'mean_word_length': 2.554188906938965, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 41, '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': '4420611', 'n_tokens_mistral': 4135, 'n_tokens_neox': 4050, 'n_words': 867}