<commit_before>
_ = require 'lodash'
rdb = require 'rethinkdb'
moment = require 'moment'
utils = require './utils'


module.exports = (con,config) ->
  Group = (require './groups')(con, config)
  Exercises = (require './exercises')(con, config)
  Users = (require './users')(con, config)

  # For pdfs
  API =
    storeTutor: (tutor) ->
      if !tutor.name? or !tutor.password? or !tutor.contingent?
        Promise.reject("You must provide a name, password and contingent field")
      else if typeof tutor.contingent isnt "number"
        Promise.reject("The tutor contingent must be a number")
      else
        (rdb.table("Tutors").insert tutor, conflict: "update").run con

    get: ->
      utils.toArray(rdb.table('Exercises').run(con))

    getById: (id)->
      rdb.table('Exercises').get(id).run(con)

    storeExercise: (exercise) ->
      exercise.activationDate = rdb.ISO8601(exercise.activationDate)
      exercise.dueDate = rdb.ISO8601(exercise.dueDate)
      (rdb.table("Exercises").insert exercise, conflict: "update").run con

    listExercises: ->
      rdb.table("Exercises").coerceTo('array').run(con)

    # Lists users and the group. also adds their total points
    listUsers: ->
      rdb.table("Users").map( (user) ->
        user.merge(
          group: Group.getGroupForUserQuery(user("id"))
          totalPoints: Users.getTotalPointsQuery(user)
        )
      ).without("solutions").coerceTo('array').run(con)

    listTutors: ->
      utils.toArray rdb.table("Tutors").without("pw").run(con)

    listGroups: ->
      utils.toArray rdb.table("Groups").run(con)

    getStudentsSolutions: (user_id) ->
      (Group.getGroupForUser(user_id)).then (group) ->
        rdb.table("Solutions").getAll(group.id, {index: "group"}).coerceTo('array')
        .without("pdf").run(con)

    querySolutions: (solution_id) ->
      rdb.table("Solutions").filter( (s) ->
        s("id").match(solution_id)).without("pdf").limit(10).coerceTo('array').run(con)

    # Also stores the last sharejs data
    lockSpecificSolutionForPdfProcessor: (id) ->
      rdb.do(
        API.storeSolution(id),
        rdb.table("Solutions").get(id).update({"processingLock": true}, {returnChanges: true})
      )

    ############################################################################
    # Locks a single solution which is due.
    # Returns the locked solution, or undefined if none is available
    #
    # A solution sample must fulfill the following conditions, to be processable
    # - the exercise must be due
    # - it must not have benn processed so far
    # - it must not be in process (processingLock)
    ############################################################################
    lockSolutionForPdfProcessor: ->
      rdb.do(
        rdb
        .table("Solutions")
        .eqJoin("exercise", rdb.table("Exercises"))
        .filter((sol) ->
          # Is due, not processed so far and not in process?
          sol("right")("dueDate").lt(new Date()).and(
            sol("left")("processed").default(false).ne(true)
          ).and(
            sol("left")("processingLock").default(false).ne(true)
          )
        ).sample(1).nth(0).default(null), (solution) ->
          rdb.branch(
            rdb.expr(solution).ne(null),
            API.lockSpecificSolutionForPdfProcessor(solution("left")("id")).do(
              rdb.expr(solution)("left")
            ),
            rdb.expr(solution) # return null
          )
      ).run con

    resetPdfForSolution: (solutionId) ->
      rdb.table("Solutions").get(solutionId).run(con).then( (result) ->
        if !result?
          Promise.reject "No such solution."
        else
          rdb.table("Solutions").get(solutionId).replace( (solution) ->
            solution.without("processed").without("processingLock").without("pdf")
          ).run con
      )

    resetPdfForAllSolutions: () ->
      rdb.table("Solutions").replace((solution) ->
        solution.without("processed").without("processingLock").without("pdf")
      ).run con

    # Save the finished pdf file into the solution
    insertFinishedPdf: (solutionId, pdfData)->
      rdb.table("Solutions").get(solutionId).update(
        processed: true
        processingLock: false,
        pdf: pdfData
      ).run con

    getTestsFromExercise: (exerciseId) ->
      rdb.table("Exercises").get(exerciseId)("tasks").pluck("number", "solutionTests", "tests")
      .coerceTo("array").run con

    pluckSolution: (solutionId, pluckArr) ->
      rdb.table("Solutions").get(solutionId).pluck(rdb.args(pluckArr)).run con

    # Store all solutions that are due and haven't been stored yet.
    storeAllFinalSolutions: () ->
      (rdb.table("Solutions").eqJoin("exercise", rdb.table("Exercises")).filter(
        # has not been stored before
        rdb.row("left")("finalSolutionStored")
        .default(false)
        .eq(false)
        .and(rdb.row("right")("dueDate").lt(rdb.now()))
      ).map ( (solution) -><commit_msg>Inserting a pdf now also generates a time stamp.<commit_after>
_ = require 'lodash'
rdb = require 'rethinkdb'
moment = require 'moment'
utils = require './utils'


module.exports = (con,config) ->
  Group = (require './groups')(con, config)
  Exercises = (require './exercises')(con, config)
  Users = (require './users')(con, config)

  # For pdfs
  API =
    storeTutor: (tutor) ->
      if !tutor.name? or !tutor.password? or !tutor.contingent?
        Promise.reject("You must provide a name, password and contingent field")
      else if typeof tutor.contingent isnt "number"
        Promise.reject("The tutor contingent must be a number")
      else
        (rdb.table("Tutors").insert tutor, conflict: "update").run con

    get: ->
      utils.toArray(rdb.table('Exercises').run(con))

    getById: (id)->
      rdb.table('Exercises').get(id).run(con)

    storeExercise: (exercise) ->
      exercise.activationDate = rdb.ISO8601(exercise.activationDate)
      exercise.dueDate = rdb.ISO8601(exercise.dueDate)
      (rdb.table("Exercises").insert exercise, conflict: "update").run con

    listExercises: ->
      rdb.table("Exercises").coerceTo('array').run(con)

    # Lists users and the group. also adds their total points
    listUsers: ->
      rdb.table("Users").map( (user) ->
        user.merge(
          group: Group.getGroupForUserQuery(user("id"))
          totalPoints: Users.getTotalPointsQuery(user)
        )
      ).without("solutions").coerceTo('array').run(con)

    listTutors: ->
      utils.toArray rdb.table("Tutors").without("pw").run(con)

    listGroups: ->
      utils.toArray rdb.table("Groups").run(con)

    getStudentsSolutions: (user_id) ->
      (Group.getGroupForUser(user_id)).then (group) ->
        rdb.table("Solutions").getAll(group.id, {index: "group"}).coerceTo('array')
        .without("pdf").run(con)

    querySolutions: (solution_id) ->
      rdb.table("Solutions").filter( (s) ->
        s("id").match(solution_id)).without("pdf").limit(10).coerceTo('array').run(con)

    # Also stores the last sharejs data
    lockSpecificSolutionForPdfProcessor: (id) ->
      rdb.do(
        API.storeSolution(id),
        rdb.table("Solutions").get(id).update({"processingLock": true}, {returnChanges: true})
      )

    ############################################################################
    # Locks a single solution which is due.
    # Returns the locked solution, or undefined if none is available
    #
    # A solution sample must fulfill the following conditions, to be processable
    # - the exercise must be due
    # - it must not have benn processed so far
    # - it must not be in process (processingLock)
    ############################################################################
    lockSolutionForPdfProcessor: ->
      rdb.do(
        rdb
        .table("Solutions")
        .eqJoin("exercise", rdb.table("Exercises"))
        .filter((sol) ->
          # Is due, not processed so far and not in process?
          sol("right")("dueDate").lt(new Date()).and(
            sol("left")("processed").default(false).ne(true)
          ).and(
            sol("left")("processingLock").default(false).ne(true)
          )
        ).sample(1).nth(0).default(null), (solution) ->
          rdb.branch(
            rdb.expr(solution).ne(null),
            API.lockSpecificSolutionForPdfProcessor(solution("left")("id")).do(
              rdb.expr(solution)("left")
            ),
            rdb.expr(solution) # return null
          )
      ).run con

    resetPdfForSolution: (solutionId) ->
      rdb.table("Solutions").get(solutionId).run(con).then( (result) ->
        if !result?
          Promise.reject "No such solution."
        else
          rdb.table("Solutions").get(solutionId).replace( (solution) ->
            solution.without("processed").without("processingLock").without("pdf")
          ).run con
      )

    resetPdfForAllSolutions: () ->
      rdb.table("Solutions").replace((solution) ->
        solution.without("processed").without("processingLock").without("pdf")
      ).run con

    # Save the finished pdf file into the solution
    insertFinishedPdf: (solutionId, pdfData)->
      rdb.do(
        rdb.table("Solutions").get(solutionId).update(
          processed: true
          pdf: pdfData,
          pdfGenerationDate: rdb.now()
        ),
        rdb.table("Solutions").get(solutionId).replace((solution) ->
          solution.without("processingLock")
        )
      ).run con

    getTestsFromExercise: (exerciseId) ->
      rdb.table("Exercises").get(exerciseId)("tasks").pluck("number", "solutionTests", "tests")
      .coerceTo("array").run con

    pluckSolution: (solutionId, pluckArr) ->
      rdb.table("Solutions").get(solutionId).pluck(rdb.args(pluckArr)).run con

    # Store all solutions that are due and haven't been stored yet.
    storeAllFinalSolutions: () ->
      (rdb.table("Solutions").eqJoin("exercise", rdb.table("Exercises")).filter(
        # has not been stored before
        rdb.row("left")("finalSolutionStored")
        .default(false)
        .eq(false)
        .and(rdb.row("right")("dueDate").lt(rdb.now()))
      ).map ( (solution) ->
{'fraction_non_alphanumeric': 0.1797774933543369, 'fraction_numerical': 0.0023629024318204193, 'mean_word_length': 2.8521046643913537, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 60, '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': '1022491', 'n_tokens_mistral': 3181, 'n_tokens_neox': 2996, 'n_words': 599}