<commit_before># RESTful API for interacting with the Government Message Queue (GMQ) of PR.Gov
# Some code and ideas recycled with permission from:
# github.com/mindware/Automata
# CAP API 2014 (c) Office of the CIO of Puerto Rico
# Developed by: <NAME> for Office of the CIO
# For: Estado Libre Asociado de Puerto Rico

# Load our external libraries
require 'json'	  												# gives us JSON parse and to_json
# Load our Settings and Helper Methods:
require 'app/helpers/store'								# our storage subsystem.
require 'app/helpers/library'							# useful helper methods
require 'app/helpers/config'							# configuration helper
require 'app/helpers/authentication'			# authentication class
require 'app/helpers/errors'							# defines and catches errors
require 'app/helpers/validations'					# validates user input
require 'app/helpers/transaction_id_factory' # to generate ids

# Load our Models. Models contain information stored in an Object:
require 'app/models/base'
require 'app/models/transaction'
require 'app/models/user'
require 'app/models/statistics'

# Load our Entities. Grape-Entities are API representations of a Model:
require 'app/entities/transaction'
require 'app/entities/transaction_created'

module PRGMQ
	module CAP
		class API < Grape::API

			include LibraryHelper

			def initialize
				begin
					puts "#{"API".bold.yellow}: Loading CAP API."
					# We don't validate store here, because it's not necessarily
					# available at this point.
					Store.connected?
					status = Store.connected? if !Store.nil?
					debug "Storage is #{status ? "online".bold.yellow : "offline".bold.red}!"
					super
				rescue Exception => e
					puts "Error initializing CAP API (Grape), was: #{e.message}\nBacktrace: #{e.backtrace[0]}"
					exit
				end
			end

			version 'v1'
	    content_type :json, "application/json;charset=UTF-8"
			format :json

			# Our middleware to trap exception and return json gracefully.
			use ApiErrorHandler

			# Our helpers
			helpers do
				include PRGMQ::CAP::LibraryHelper	# General Helper Methods
			end

			http_basic do |username, password|
			  # verify user's password here
			  if(Authentication.valid?(username, password))
					debug("Authentication Successful "+
							  "for user: #{username.yellow}")
					true
				else
					debug("#{"Invalid credentials".bold.red}, provided user: "+
								"'#{username}'")
					raise InvalidCredentials
					# false
				end
			end


			before do
				# If we're in verbose mode, print everything to STDOUT
				# Print a set of dashes to make viewing output easier:
				debug "#{ ("-" * 80).bold.yellow }\n", false, false
				puts "Request Id: #{env["VISIT_ID"]}" unless env["VISIT_ID"].to_s.length == 0

				# If the system is set up for downtime/maintenance:
				if(Config.downtime)
					# get the path:
					path = route.route_path.gsub("/:version/cap", "")
					# Only admin resources are allowed during maintenance mode.
					# Throw an error if this isn't an admin path.
					if !path.start_with?("/admin/")
						# Let the users know this system is down for maintenance:
						raise ServiceUnavailable
					end
				else
						# We add visits when we're outside of maintenance mode, as the
						# storage could be offline during maintenance.

						# We need to track every request, so we can safely tag each debug()
						# line, so that logs can be tracked when there are multiple
						# concurrent requests

						# Exclude specific paths from adding visits
						# TODO: consider using start_with? here.
						path = route.route_path.gsub("(.:format)", "")
						begin
							if !path.start_with? "/:version/cap/admin/maintenance" and
							 	 !path.start_with? "/:version/cap/health" #and !Config.downtime
										# Add a visit, and save it as string.
										env["VISIT_ID"] = add_visit.to_s
							else
										# No id for empty visits
										env["VISIT_ID"] = "-"
							end
						rescue
							  # Storage could not be accessed. Instead of erroring out here
								# we'll let this pass so we can print request_info, and the
								# error will be caught by the API resource in question.
						end
				end
				debug "#{request_info}"
			end

			# After every request
			after do
				# print dashes signifying end of output
				debug "#{ ("-" * 80).bold.yellow }\n", false, false
			end

			# From here on the user is authenticated. Any checks should be for
			# specific access.

			## Resource cap:
			## All the request below require a /v1/ before the resource, ie:
			## GET /v1/cap/...
			resources :cap do
				desc "Returns current version and status information. This is "+
						 "mainly used to test credentials and in the future could be used "+
						 "to see health information."
				# Get cap/
				get '/' do
					# specify a list of user groups than can access this resource
					# validate if this user belongs to the alllowed groups,
					# if it does, get the User Object, else: we'll safely error out.
					user = allowed?(["all"])
					# logger.info "#{user} requested #{route.route_params[params]}"
					result ({ :api	=> "CAP", :versions => "#{API::versions}"})
				end # end of get '/'

				# Check the health for the system
				desc "Performs an API health check"
				get '/health' do
					# the only reason where we don't check for authentication
					# since if the storage server is down, we won't be able to
					# even get to the authentication in order to answer the health
					# check:
					result ({ :storage_online => Store.connected?, :maintenance_mode => Config.downtime })
				end

				## Resource cap/transaction:
				group :transaction do

						## Resource cap/transaction/status:
						group :status do
							# GET cap/transaction/status/:status:
							# TODO: add this support
							desc "Returns an estimated number of existing transactions in "+
									 "the CAP Transaction system that match a specific status."
							params do
								requires :status, type: String, desc: "A valid status id."
							end
							get "/" do
								user = allowed?(["all"])
								result ({
										     :transactions =>
												 {
						                :status =>
														{
				                      	:status => "20"
							              }
										     }
											})
							end
						end

						## Resource cap/transaction/:id:
						desc "Returns all available information on a specific "+
								 "transaction id."
						params do
							requires :id, type: String, desc: "A valid transaction id."
						end
						namespace ":id" do
							# Resource cap/transaction/:id/status:
							group :status do
								# GET cap/transaction/:id/status/
								# TODO: code this action
								desc "Returns relevant status information on a specific "+
										 "transaction id."
								get "/" do
										# so long as we only return general status info, we can
										# leave this open to all systems. If we start providing
										# specific user information, then only admin and workers.
										user = allowed?(["all"])
										result ({
										    "transaction" => {
										        "id" => "0-123-456",
										        "current_error_count" => 0,
										        "total_error_count" => 1,
										        "action" => {
										             "id" => 10,
										             "description" => "sending certificate via email"
										         },
										        "status" => "processing",
										        "location" => "prgmq_email_certificate_queue",
										    }
										})
								end
							end

							# GET /v1/cap/transaction/:id/
							desc "Returns all available information on a specific "+
									 "transaction id."
							params do
								optional :id, type: String, desc: "A valid transaction id."
							end
							get do
									user = allowed?(["admin", "worker"])
									transaction = Transaction.find(params[:id])
									result (present transaction, with: CAP::Entities::Transaction) #, type: :hi
							end

							# DELETE /v1/cap/transaction/:id
							# TODO: code this
							desc "Deletes a specific transaction id so it cannot be processed. "+
									"This will likely move the transaction to an alternative "+
									"repository in the future, in order to archive it. This is "+
									"mainly to be used by workers, or for administrative purposes"+
									" such as when duplicates are detected."
							params do
								requires :id, type: String, desc: "A valid transaction id."
							end
							delete do
								user = allowed?(["admin", "worker"])
								result ({
										"0-123-456" => "deleted"
								})
							end # end of DELETE
						end # end of namespace: Resource cap/transaction/:id:

						# GET /v1/cap/transaction/
						# TODO: code this!
						desc "Returns an estimated number of existing transactions in "+
								"the CAP Transaction system."
						get '/' do
							user = allowed?(["all"])
							result({
								"transactions" =>
								{
										:pending => total_pending,
										:completed => total_completed,
										:visits => total_visits,
										"failed" => total_failed,
										"retry" => false,
										"processing" => false,
								}
							})
						end

						# POST /v1/cap/transaction/
						# this creates a transaction
						desc "Request to generate a new transaction, which involves "+
								 "attempting to enqueue the request with a unique transaction "+
								 "id and returning said id."
						post '/' do
							user = allowed?(["prgov", "admin"])
							# We store the IP of the system that made the direct request.
							# even if they will forward the originating IP, we grab theirs
							# in case we need to find out what server has been submitting
							# specific requests.
							# return params.class

							params[:system_address] = env['REMOTE_ADDR']
							params[:created_by] 		= user.name
							# Try to create it - if this fails, our error middleware catches it
							transaction = Transaction.create(params)
							# check if we are able to save it
							if transaction.save
								# Temporarily commented out until we can track the entities + pools
								# bug that is causing present to completely fail.
								# result present(transaction, with: CAP::Entities::TransactionCreated)

								# this fails sporadically
								# present transaction

								# this works perfectly, but doesn't limit the data we show prgov
								result transaction
								# transaction
							else
								# if the item is not found, raise an error that it could not be saved
								raise TransactionNotFound
							end
						end

						# PUT /v1/cap/transaction/review_complete
						desc "Confirms that an analyst at PRPD has completed manual revision of "+
						"this transaction, and requests that their decision_code be stored"+
						" and processed accordingly."
						params do
							# Remove the following and add actual parameters later.
							requires :id, type: String, desc: "A valid transaction id."
						end
						put 'review_complete' do
							user = allowed?(["sijc", "admin"])
							# try to find. If not found, an error is raised and sent.
							transaction = Transaction.find(params["id"])
							# once found, we grab the base64 parameters and update the object
							transaction.review_complete(params)
							# we try to save the transaction
							transaction.save
							result present(transaction, with: Entities::Transaction)
						end # end of review_complete


						# PUT /v1/cap/transaction/certificate_ready
						desc "Confirms that the enclosed base64 "+<commit_msg>Adding method for submitting email.<commit_after># RESTful API for interacting with the Government Message Queue (GMQ) of PR.Gov
# Some code and ideas recycled with permission from:
# github.com/mindware/Automata
# CAP API 2014 (c) Office of the CIO of Puerto Rico
# Developed by: <NAME> for Office of the CIO
# For: Estado Libre Asociado de Puerto Rico

# Load our external libraries
require 'json'	  												# gives us JSON parse and to_json
# Load our Settings and Helper Methods:
require 'app/helpers/store'								# our storage subsystem.
require 'app/helpers/library'							# useful helper methods
require 'app/helpers/config'							# configuration helper
require 'app/helpers/authentication'			# authentication class
require 'app/helpers/errors'							# defines and catches errors
require 'app/helpers/validations'					# validates user input
require 'app/helpers/transaction_id_factory' # to generate ids

# Load our Models. Models contain information stored in an Object:
require 'app/models/base'
require 'app/models/transaction'
require 'app/models/user'
require 'app/models/statistics'

# Load our Entities. Grape-Entities are API representations of a Model:
require 'app/entities/transaction'
require 'app/entities/transaction_created'

module PRGMQ
	module CAP
		class API < Grape::API

			include LibraryHelper

			def initialize
				begin
					puts "#{"API".bold.yellow}: Loading CAP API."
					# We don't validate store here, because it's not necessarily
					# available at this point.
					Store.connected?
					status = Store.connected? if !Store.nil?
					debug "Storage is #{status ? "online".bold.yellow : "offline".bold.red}!"
					super
				rescue Exception => e
					puts "Error initializing CAP API (Grape), was: #{e.message}\nBacktrace: #{e.backtrace[0]}"
					exit
				end
			end

			version 'v1'
	    content_type :json, "application/json;charset=UTF-8"
			format :json

			# Our middleware to trap exception and return json gracefully.
			use ApiErrorHandler

			# Our helpers
			helpers do
				include PRGMQ::CAP::LibraryHelper	# General Helper Methods
			end

			http_basic do |username, password|
			  # verify user's password here
			  if(Authentication.valid?(username, password))
					debug("Authentication Successful "+
							  "for user: #{username.yellow}")
					true
				else
					debug("#{"Invalid credentials".bold.red}, provided user: "+
								"'#{username}'")
					raise InvalidCredentials
					# false
				end
			end


			before do
				# If we're in verbose mode, print everything to STDOUT
				# Print a set of dashes to make viewing output easier:
				debug "#{ ("-" * 80).bold.yellow }\n", false, false
				puts "Request Id: #{env["VISIT_ID"]}" unless env["VISIT_ID"].to_s.length == 0

				# If the system is set up for downtime/maintenance:
				if(Config.downtime)
					# get the path:
					path = route.route_path.gsub("/:version/cap", "")
					# Only admin resources are allowed during maintenance mode.
					# Throw an error if this isn't an admin path.
					if !path.start_with?("/admin/")
						# Let the users know this system is down for maintenance:
						raise ServiceUnavailable
					end
				else
						# We add visits when we're outside of maintenance mode, as the
						# storage could be offline during maintenance.

						# We need to track every request, so we can safely tag each debug()
						# line, so that logs can be tracked when there are multiple
						# concurrent requests

						# Exclude specific paths from adding visits
						# TODO: consider using start_with? here.
						path = route.route_path.gsub("(.:format)", "")
						begin
							if !path.start_with? "/:version/cap/admin/maintenance" and
							 	 !path.start_with? "/:version/cap/health" #and !Config.downtime
										# Add a visit, and save it as string.
										env["VISIT_ID"] = add_visit.to_s
							else
										# No id for empty visits
										env["VISIT_ID"] = "-"
							end
						rescue
							  # Storage could not be accessed. Instead of erroring out here
								# we'll let this pass so we can print request_info, and the
								# error will be caught by the API resource in question.
						end
				end
				debug "#{request_info}"
			end

			# After every request
			after do
				# print dashes signifying end of output
				debug "#{ ("-" * 80).bold.yellow }\n", false, false
			end

			# From here on the user is authenticated. Any checks should be for
			# specific access.

			## Resource cap:
			## All the request below require a /v1/ before the resource, ie:
			## GET /v1/cap/...
			resources :cap do
				desc 'Allows for sending email messages.'
				get '/mail'
						user = allowed?(["webapp"])
						mail = Message.email(params)
						result { :result => "ok" }
				end

				desc "Returns current version and status information. This is "+
						 "mainly used to test credentials and in the future could be used "+
						 "to see health information."
				# Get cap/
				get '/' do
					# specify a list of user groups than can access this resource
					# validate if this user belongs to the alllowed groups,
					# if it does, get the User Object, else: we'll safely error out.
					user = allowed?(["all"])
					# logger.info "#{user} requested #{route.route_params[params]}"
					result ({ :api	=> "CAP", :versions => "#{API::versions}"})
				end # end of get '/'

				# Check the health for the system
				desc "Performs an API health check"
				get '/health' do
					# the only reason where we don't check for authentication
					# since if the storage server is down, we won't be able to
					# even get to the authentication in order to answer the health
					# check:
					result ({ :storage_online => Store.connected?, :maintenance_mode => Config.downtime })
				end

				## Resource cap/transaction:
				group :transaction do

						## Resource cap/transaction/status:
						group :status do
							# GET cap/transaction/status/:status:
							# TODO: add this support
							desc "Returns an estimated number of existing transactions in "+
									 "the CAP Transaction system that match a specific status."
							params do
								requires :status, type: String, desc: "A valid status id."
							end
							get "/" do
								user = allowed?(["all"])
								result ({
										     :transactions =>
												 {
						                :status =>
														{
				                      	:status => "20"
							              }
										     }
											})
							end
						end

						## Resource cap/transaction/:id:
						desc "Returns all available information on a specific "+
								 "transaction id."
						params do
							requires :id, type: String, desc: "A valid transaction id."
						end
						namespace ":id" do
							# Resource cap/transaction/:id/status:
							group :status do
								# GET cap/transaction/:id/status/
								# TODO: code this action
								desc "Returns relevant status information on a specific "+
										 "transaction id."
								get "/" do
										# so long as we only return general status info, we can
										# leave this open to all systems. If we start providing
										# specific user information, then only admin and workers.
										user = allowed?(["all"])
										result ({
										    "transaction" => {
										        "id" => "0-123-456",
										        "current_error_count" => 0,
										        "total_error_count" => 1,
										        "action" => {
										             "id" => 10,
										             "description" => "sending certificate via email"
										         },
										        "status" => "processing",
										        "location" => "prgmq_email_certificate_queue",
										    }
										})
								end
							end

							# GET /v1/cap/transaction/:id/
							desc "Returns all available information on a specific "+
									 "transaction id."
							params do
								optional :id, type: String, desc: "A valid transaction id."
							end
							get do
									user = allowed?(["admin", "worker"])
									transaction = Transaction.find(params[:id])
									result (present transaction, with: CAP::Entities::Transaction) #, type: :hi
							end

							# DELETE /v1/cap/transaction/:id
							# TODO: code this
							desc "Deletes a specific transaction id so it cannot be processed. "+
									"This will likely move the transaction to an alternative "+
									"repository in the future, in order to archive it. This is "+
									"mainly to be used by workers, or for administrative purposes"+
									" such as when duplicates are detected."
							params do
								requires :id, type: String, desc: "A valid transaction id."
							end
							delete do
								user = allowed?(["admin", "worker"])
								result ({
										"0-123-456" => "deleted"
								})
							end # end of DELETE
						end # end of namespace: Resource cap/transaction/:id:

						# GET /v1/cap/transaction/
						# TODO: code this!
						desc "Returns an estimated number of existing transactions in "+
								"the CAP Transaction system."
						get '/' do
							user = allowed?(["all"])
							result({
								"transactions" =>
								{
										:pending => total_pending,
										:completed => total_completed,
										:visits => total_visits,
										"failed" => total_failed,
										"retry" => false,
										"processing" => false,
								}
							})
						end

						# POST /v1/cap/transaction/
						# this creates a transaction
						desc "Request to generate a new transaction, which involves "+
								 "attempting to enqueue the request with a unique transaction "+
								 "id and returning said id."
						post '/' do
							user = allowed?(["prgov", "admin"])
							# We store the IP of the system that made the direct request.
							# even if they will forward the originating IP, we grab theirs
							# in case we need to find out what server has been submitting
							# specific requests.
							# return params.class

							params[:system_address] = env['REMOTE_ADDR']
							params[:created_by] 		= user.name
							# Try to create it - if this fails, our error middleware catches it
							transaction = Transaction.create(params)
							# check if we are able to save it
							if transaction.save
								# Temporarily commented out until we can track the entities + pools
								# bug that is causing present to completely fail.
								# result present(transaction, with: CAP::Entities::TransactionCreated)

								# this fails sporadically
								# present transaction

								# this works perfectly, but doesn't limit the data we show prgov
								result transaction
								# transaction
							else
								# if the item is not found, raise an error that it could not be saved
								raise TransactionNotFound
							end
						end

						# PUT /v1/cap/transaction/review_complete
						desc "Confirms that an analyst at PRPD has completed manual revision of "+
						"this transaction, and requests that their decision_code be stored"+
						" and processed accordingly."
						params do
							# Remove the following and add actual parameters later.
							requires :id, type: String, desc: "A valid transaction id."
						end
						put 'review_complete' do
							user = allowed?(["sijc", "admin"])
							# try to find. If not found, an error is raised and sent.
							transaction = Transaction.find(params["id"])
							# once found, we grab the base64 parameters and update the object
							transaction.review_complete(params)
							# we try to save the transaction
							transaction.save
							result present(transaction, with: Entities::Transaction)
						end # end of review_complete

						# PUT /v1/cap/transaction/certificate_ready
						desc "Confirms that the enclosed base64 "+
{'fraction_non_alphanumeric': 0.09016041863258129, 'fraction_numerical': 0.0037745560607360383, 'mean_word_length': 2.2301191465779993, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1810957', 'n_tokens_mistral': 9286, 'n_tokens_neox': 6106, 'n_words': 2430}