text:
require 'lims-api'
require 'rack/test'
require 'hashdiff'

# setup test environment
set :environment, :test
set :run, false
set :raise_errors, true
set :logging, false

def app
  Lims::Api::Server
end

def json_version
  3
end

module Helper
    # converts a structure or a json string to a structure.
    # Transforms as well key to string.
    # @param  [String, Hash, Array]
    # @retun [Hash<String,String>]
    def self.parse_json(arg)
      case arg
      when String then JSON.parse(arg)
      when Array, Hash then arg
      end.recurse{|h| h.rekey { |k| k.to_s } }
    end
end

class Hash

  def deep_diff(b)
    a = self
    (a.keys | b.keys).inject({}) do |diff, k|
      if a[k] != b[k]
        if a[k].respond_to?(:deep_diff) && b[k].respond_to?(:deep_diff)
          diff[k] = a[k].deep_diff(b[k])
        else
          diff[k] = [a[k], b[k]]
        end
      end
      diff
    end
  end

end

RSpec.configure do |config|
  config.include Rack::Test::Methods
  config.filter_run_excluding :benchmark => true, :logger => true
end

RSpec::Matchers.define :io_stream do |content|
  match { |stream| content == stream.read }
end

RSpec::Matchers.define :match_json_response do |status, body|
  match { |to_match| to_match.status == status && Helper::parse_json(to_match.body) == Helper::parse_json(body) }

  failure_message_for_should do |actual|
    hactual = {:status => status, :body =>  Helper::parse_json(actual.body)}
    hcontent = {:status => actual.status, :body => Helper::parse_json(body) }
    diff = hactual ? hactual.deep_diff(hcontent) : hcontent
    "expected: \n#{JSON::pretty_generate(hcontent)}\nto match: \n#{JSON::pretty_generate(hactual)},\ndiff:\n#{JSON::pretty_generate(diff)} "
  end

end

RSpec::Matchers.define :match_json do |content|

  match { |to_match| Helper::parse_json(to_match) == Helper::parse_json(content) }

  failure_message_for_should do |actual|
    hactual = Helper::parse_json(actual)
    hcontent = Helper::parse_json(content)
    diff = hactual ? hactual.deep_diff(hcontent) : hcontent
    "expected: \n#{JSON::pretty_generate(hcontent)}\nto match: \n#{JSON::pretty_generate(hactual)},\ndiff:\n#{JSON::pretty_generate(diff)} "
  end
end

RSpec::Matchers.define :include_json do |content|
  match do |actual|
    # content is what we specified in the spec
    # it is what needs to be checked as included
    hactual = Helper::parse_json(actual)
    hcontent = Helper::parse_json(content)

    hcontent.inject(true) do |r, (key, value)|
      r && (hactual[key] == value)
    end

  end

  failure_message_for_should do |actual|
    errors = []
    hactual = Helper::parse_json(actual)
    hcontent = Helper::parse_json(content)

    diffs = HashDiff.diff(hcontent, hactual)
    "expected: \n#{hcontent}\nto match: \n#{hactual},\ndiff:\n#{
    diffs.map { |d| d.join(' ') }.join("\n")}"
  end
end

shared_context "clean store" do
  after(:each) do
    store.with_session(:backend_application_id => "lims-laboratory-app/spec", :parameters => {action: "purge"}) do
    %w{items orders batches searches labels labellables tube_aliquots filter_paper_aliquots spin_column_aliquots windows wells lanes fluidigm_wells tag_group_associations aliquots tube_rack_slots tube_racks tubes spin_columns gels plates flowcells filter_papers fluidigms samples oligos snp_assays tag_groups studies users locations uuid_resources }.each do |table|
      db[table.to_sym].delete
    end
  end
    db.disconnect
  end
end

def set_uuid(session, object, uuid)
  session << object
  # save $uuid_sequence in case it's modify by the following block of code
  uuid_sequence = $uuid_sequence
  ur = session.new_uuid_resource_for(object)
  ur.send(:uuid=, uuid).tap do
    $uuid_sequence = uuid_sequence
  end
end

redpajama_set_name: RedPajamaGithub


text:
package me.hao0.wechat.model.message.receive.msg;

import me.hao0.wechat.model.message.receive.RecvMessageType;

/**
 * 链接消息
 * Author: haolin
 * Email: haolin.h0@gmail.com
 * Date: 9/11/15
 */
public class RecvLinkMessage extends RecvMsg {

    private static final long serialVersionUID = -8070100690774814611L;

    /**
     * 消息标题
     */
    private String title;

    /**
     * 消息描述
     */
    private String description;

    /**
     * 消息链接
     */
    private String url;

    public RecvLinkMessage(RecvMsg m){
        super(m);
        this.msgId = m.msgId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public String getMsgType() {
        return RecvMessageType.LINK.value();
    }

    @Override
    public String toString() {
        return "RecvLinkMessage{" +
                "title='" + title + '\'' +
                ", description='" + description + '\'' +
                ", url='" + url + '\'' +
                "} " + super.toString();
    }
}


text:
classdef Intersection < handle
    %Intersection

    properties
        % Properties
        % ----------
        % - radius: double = 1

        radius
    end

    methods
        function obj = Intersection(radius)
            % Constructor
            if ~exist('radius', 'var')
                radius = 1;
            end

            obj.radius = radius;
        end
        function tf = haveIntersection(obj, c1, c2)
            % Check two given hyper spheres have intersection
            %
            % Parameters
            % ----------
            % - c1: double vector
            %   Center of first hyper shpere
            % - c2: double vector
            %   Center of second hyper shpere
            %
            % Results
            % -------
            % - tf: logical
            %   True or false result

            % euclidean distance between two centers
            d = norm(c1 - c2);

            tf = d < 2 * obj.radius;
        end
    end

end


text:
class Shja::Client::Pondo < Shja::D2PassClient

  def agent_class
    Shja::Agent::Pondo
  end

  def db_class
    Shja::Db::Pondo
  end

  def movies_class
    Shja::MovieManager::Pondo
  end

  def html_class
    Shja::Html::Pondo
  end

  def refresh_cache!
    movies.all do |movie|
      moview.download_metadata
    end
  end

  def refresh_by_actor_id!(actor_id)
    movies.download_actor_index(actor_id)
  end

  def refresh_by_series_id!(series_id)
    movies.download_series_index(series_id)
  end

end


text:
package fr.adrienbrault.idea.symfony2plugin.util.yaml.visitor;

import org.jetbrains.annotations.NotNull;

/**
 * @author Daniel Espendiller <daniel@espendiller.net>
 */
public interface YamlTagVisitor {
    void visit(@NotNull YamlServiceTag args);
}


text:
import json
import time
import requests

import settings
from utils import three_digit, create_paired_list

class Search(object):

    def __init__(self):
        self.api_url = settings.API_URL

    def search(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=None, cloud_min=None,
               cloud_max=None, limit=1):
        """
        The main method of Search class. It searches the DevSeed Landsat API

        Returns python dictionary

        Arguments:
            paths_rows -- A string in this format: "003,003,004,004". Must be in pairs
            lat -- The latitude
            lon -- The longitude
            start_date -- date string. format: YYYY-MM-DD
            end_date -- date string. format: YYYY-MM-DD
            cloud_min -- float specifying the minimum percentage. e.g. 4.3
            cloud_max -- float specifying the maximum percentage. e.g. 78.9
            limit -- integer specigying the maximum results return.

        Example:

            search('003,003', '2014-01-01', '2014-06-01')

            will return:

            {
                'status': u'SUCCESS',
                'total_returned': 1,
                'total': 1,
                'limit': 1
                'results': [
                    {
                        'sat_type': u'L8',
                        'sceneID': u'LC80030032014142LGN00',
                        'date': u'2014-05-22',
                        'path': u'003',
                        'thumbnail': u'http://....../landsat_8/2014/003/003/LC80030032014142LGN00.jpg',
                        'cloud': 33.36,
                        'row': u'003
                    }
                ]
            }
        """

        search_string = self.query_builder(paths_rows, lat, lon, start_date, end_date, cloud_min, cloud_max)

        # Have to manually build the URI to bypass requests URI encoding
        # The api server doesn't accept encoded URIs

        r = requests.get('%s?search=%s&limit=%s' % (self.api_url, search_string, limit))

        r_dict = json.loads(r.text)
        result = {}

        if 'error' in r_dict:
            result['status'] = u'error'
            result['code'] = r_dict['error']['code']
            result['message'] = r_dict['error']['message']

        elif 'meta' in r_dict:
            result['status'] = u'SUCCESS'
            result['total'] = r_dict['meta']['results']['total']
            result['limit'] = r_dict['meta']['results']['limit']
            result['total_returned'] = len(r_dict['results'])
            result['results'] = [{'sceneID': i['sceneID'],
                                  'sat_type': u'L8',
                                  'path': three_digit(i['path']),
                                  'row': three_digit(i['row']),
                                  'thumbnail': i['browseURL'],
                                  'date': i['acquisitionDate'],
                                  'cloud': i['cloudCoverFull']}
                                 for i in r_dict['results']]

        return result

    def query_builder(self, paths_rows=None, lat=None, lon=None, start_date=None, end_date=None,
                      cloud_min=None, cloud_max=None):
        """ Builds the proper search syntax (query) for Landsat API """

        query = []
        or_string = ''
        and_string = ''
        search_string = ''

        if paths_rows:
            # Coverting rows and paths to paired list
            new_array = create_paired_list(paths_rows)
            paths_rows = ['(%s)' % self.row_path_builder(i[0], i[1]) for i in new_array]
            or_string = '+OR+'.join(map(str, paths_rows))

        if start_date and end_date:
            query.append(self.date_range_builder(start_date, end_date))
        elif start_date:
            query.append(self.date_range_builder(start_date, '2100-01-01'))
        elif end_date:
            query.append(self.date_range_builder('2009-01-01', end_date))

        if cloud_min and cloud_max:
            query.append(self.cloud_cover_prct_range_builder(cloud_min, cloud_max))
        elif cloud_min:
            query.append(self.cloud_cover_prct_range_builder(cloud_min, '100'))
        elif cloud_max:
            query.append(self.cloud_cover_prct_range_builder('-1', cloud_max))

        if lat and lon:
            query.append(self.lat_lon_builder(lat, lon))

        if query:
            and_string = '+AND+'.join(map(str, query))

        if and_string and or_string:
            search_string = and_string + '+AND+(' + or_string + ')'
        else:
            search_string = or_string + and_string

        return search_string

    def row_path_builder(self, path='', row=''):
        """
        Builds row and path query
        Accepts row and path in XXX format, e.g. 003
        """
        return 'path:%s+AND+row:%s' % (path, row)

    def date_range_builder(self, start='2013-02-11', end=None):
        """
        Builds date range query
        Accepts start and end date in this format YYYY-MM-DD
        """
        if not end:
            end = time.strftime('%Y-%m-%d')

        return 'acquisitionDate:[%s+TO+%s]' % (start, end)

    def cloud_cover_prct_range_builder(self, min=0, max=100):
        """
        Builds cloud cover percentage range query
        Accepts bottom and top range in float, e.g. 1.00
        """
        return 'cloudCoverFull:[%s+TO+%s]' % (min, max)

    def lat_lon_builder(self, lat=0, lon=0):
        """ Builds lat and lon query """
        return ('upperLeftCornerLatitude:[%s+TO+1000]+AND+lowerRightCornerLatitude:[-1000+TO+%s]'
                '+AND+lowerLeftCornerLongitude:[-1000+TO+%s]+AND+upperRightCornerLongitude:[%s+TO+1000]'
                % (lat, lat, lon, lon))


text:
%
% Manual for NodePoint - (C) 2015 Patrick Lambert
% Compile with XeLaTeX
%

% Document class
\documentclass[11pt]{article}
\usepackage{geometry}
\usepackage{listings}

% Conditional compilation for Windows and Linux
\usepackage{ifthen}
\newboolean{Windows}
\setboolean{Windows}{false} % change to false for Linux
\ifthenelse{\boolean{Windows}}
{\def\NodePointManual{NodePoint IT System - Windows version 1.7.4}}
{\def\NodePointManual{NodePoint IT System - Linux version 1.7.4}}

% Margins and paper type
\geometry{a4paper, right=1.5cm, left=1.5cm, top=2cm, bottom=1.5cm}

% Color definitions
\usepackage{color}
\usepackage{hyperref}
\definecolor{headings}{rgb}{0,0.53,0.84}
\hypersetup{colorlinks, linkcolor={blue}, citecolor={blue}, urlcolor={blue}}

% Paragraph indent and spacing
\usepackage[english]{babel}
\setlength{\parindent}{0em}
\setlength{\parskip}{1em}
\usepackage{indentfirst}
\usepackage{setspace}

% Section titles
\usepackage{titlesec}
\titleformat{\section}
{\color{headings}\normalfont\LARGE\bfseries}
{\color{headings}\thesection}{1em}{}
\titleformat{\subsection}
{\color{headings}\normalfont\Large\bfseries}
{\color{headings}\thesubsection}{1em}{}
\titleformat{\subsubsection}
{\color{headings}\normalfont\large\bfseries}
{\color{headings}\thesubsubsection}{1em}{}

% Font definition
\usepackage{fontspec}
\setmainfont{Arial}

% Header setup
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\rhead{\large{\textsc{\color{headings}{\thepage}}}}
\lhead{\large{\textsc{\color{headings}{\NodePointManual{}}}}}     % text in footer middle

% Document starts
\begin{document}

\begin{titlepage}
\bigskip
\begin{center}
\begin{figure}
\includegraphics{splash.jpg}
\end{figure}
\vspace*{1cm}
{\setstretch{5.0}
{\Huge \color{headings}{\NodePointManual}}\\
\bigskip
{\Large Patrick Lambert}\\
\texttt{\url{http://nodepoint.ca}}\\
\bigskip
\today}
\vspace*{\fill}
\end{center}
\end{titlepage}

\tableofcontents

\newpage

\section{Introduction}

NodePoint is an IT platform based on the Bootstrap framework providing a number of components for tickets management, inventory control, support articles, tasks and more. It is meant to be simple to setup and use, yet still offers most features one would expect in such a system. It is provided as freely available open source software under the MIT License.

Here are some of the available features:

\begin{itemize}
\item \textbf{Users management:}  Allow users to register accounts, reset their passwords, email addresses and more.
\item \textbf{Access level permissions:} Six access levels allow you to control who has access to add tickets, modify their statuses, manage projects, and so on.
\item \textbf{Active Directory integration:} Use the stand alone users component or synchronize with your AD domain.
\item \textbf{Projects and milestone control:} Add projects and create milestones against those projects.
\item \textbf{Statistics and reports:} Monitor your instance through a number of reports.
\item \textbf{Custom home page:} See the tickets you created, are assigned to, or follow on your home page.
\item \textbf{Custom forms:} Use the default form or create custom ones to customize the ticket entry page.
\item \textbf{Full text search and filters:} Filter tickets by product, status or do a custom search.
\item \textbf{Follow and auto assignment:} Users can follow tickets, assign themselves to work on issues, or auto assign themselves to projects.
\item \textbf{Comments and file uploads:} Leave comments or attach files related to tickets.
\item \textbf{Knowledge base articles:} Create articles to build a knowledge base linked to your products.
\item \textbf{Tasks management:} Create tasks and assign them to users, with due dates and completion rates.
\item \textbf{Inventory control:} Add items that users can checkout, with full approval control.
\item \textbf{Clients directory:} Make a clients listing which can then be used with tickets and items.
\item \textbf{Time tracking:} Users assigned to tickets can enter the time they took to solve issues and track their time.
\item \textbf{Easy to install:} NodePoint works on Windows and Linux, is easy to install, and can be configured in minutes.
\item \textbf{Trivial to update:} See when an update is available and update your instance with a single copy operation.
\item \textbf{Responsive design:} Using the Bootstrap framework, NodePoint works just as well on the desktop as on mobile.
\item \textbf{Simple and elegant interface:} The interface is easy to understand and elegant for your users to use.
\item \textbf{Email notifications:} NodePoint comes with built-in email notifications.
\item \textbf{Automation engine:} Run background processes without user input.
\item \textbf{JSON API:} Create custom scripts or link other systems to enter tickets automatically.
\end{itemize}

\clearpage
\subsection{Requirements}
\ifthenelse{\boolean{Windows}}
{
NodePoint was created for small to medium sized organizations (less than 1,000 users, less than 50,000 tickets). It runs on a single server and does not require any external resource. Configuration settings are stored in the Windows Registry while data is stored in a file database from a customizable location (the \texttt{../db} folder by default). As such, only two requirements are needed to install:
\begin{itemize}
\item A 64 bits Windows host (7, 8, 10, Server 2008, Server 2012 should all work).
\item IIS installed with \textit{CGI} and \textit{ISAPI Extensions} features.
\end{itemize}
}
{
NodePoint was created for small to medium sized organizations (less than 1,000 users, less than 10,000 tickets). It runs on a single server and does not require any external resource. Configuration settings are stored in the \texttt{../nodepoint.cfg} file while data is stored in a file database from a customizable location (the \texttt{../db} folder by default). As such, only two requirements are needed to install:
\begin{itemize}
\item A 64 bits Linux host.
\item Apache web server.
\end{itemize}
}

\subsection{Installation}
Follow these steps to complete a new installation:

\ifthenelse{\boolean{Windows}}
{
\begin{enumerate}
\item Download NodePoint from \url{http://nodepoint.ca} and unzip the folder where you want files to live, for example \texttt{C:\textbackslash nodepoint}.
\item Run \texttt{setup.bat} and follow the instructions. This will create a new user for NodePoint to run under, configure IIS, and restart the web server.
\item Access the site from \textit{localhost} (e.g. \texttt{http://localhost/nodepoint}) and complete the first time configuration (see below).
\end{enumerate}
}
{
\begin{enumerate}
\item Download NodePoint from \url{http://nodepoint.ca} and untar the folder: \texttt{tar xvf nodepoint.tar}
\item Run \texttt{./setup.sh} as root, from the folder you just extracted. This will copy the files to the proper location, add some Apache configuration options, and setup the Automation schedule. Optionally, you may need to edit your Apache configuration or setup a virtual host.
\item Access the site from \textit{localhost} (e.g. \texttt{http://localhost/nodepoint}) and complete the first time configuration (see below).
\end{enumerate}
}

\subsection{Upgrading}
NodePoint is designed to be fully upgradable from any minor version to another. For example, \textit{1.x.x} to \textit{1.x.x}. To upgrade from a previous NodePoint version, simply copy the new \texttt{www} folder onto the old one. This will overwrite all product files but leave your configuration, data and uploads.

\subsection{Uninstalling}
You can remove a NodePoint installation from a host in two steps:
\begin{enumerate}
\item Remove the folder where your NodePoint files live (backup your database if you wish to retain your data).
\item Remove the virtual host entry from your web server.
\end{enumerate}

\subsection{Troubleshooting}

\ifthenelse{\boolean{Windows}}
{
\subsubsection{If setup.bat fails}

Make sure you are running it as a local administrator. Make sure the file \texttt{\%windir\%\textbackslash system32\textbackslash inetsrv\textbackslash appcmd.exe} exists, and that your web site is named \textit{Default Web Site}. Also try running setup from the installation folder. Optionally, you may have to modify the script to suit your environment.

\subsubsection{If you get the error Could not access Registry}

Make sure the virtual folder has credentials that has access to the Registry. Go to \textit{Administrative Tools -> IIS Manager}, then select the NodePoint site under \textit{Default Web Site} and go to Advanced Settings. Once there, select a Specific User that has access to the Registry.

This can be your own user name, or you can create a dedicated user in \textit{Administrative Tools -> Computer Management -> Local Users and Groups}. Make sure that user is part of the \texttt{Administrators} local group, and that you restart the IIS server after:

\begin{figure}[h]
\includegraphics{iisaccount.jpg}
\end{figure}

\subsubsection{If you get the error Could not access database file}

Make sure that NodePoint has Read/Write access to the database file specified in the initial configuration. By default this is \texttt{C:\textbackslash nodepoint\textbackslash db\textbackslash nodepoint.db}. This should be the user you configured in the initial setup. You can use Windows Explorer to right click on the file and check under the Security tab.

\subsubsection{If you get the error The page you are requesting cannot be served because of the ISAPI and CGI Restriction list settings on the Web server}

Go to \textit{IIS Manager -> ISAPI and CGI Restrictions} and make sure NodePoint is listed. If not, click on \textbf{Edit Feature Settings} and enable unspecified CGI modules.

\subsubsection{If the server tries to download the file instead of executing it}

Make sure you have the \textit{CGI} and \textit{ISAPI Extensions} IIS features installed and enable \textbf{Execute} under \textit{IIS Manager -> Handler Mappings -> CGI-exe -> Edit Feature Permissions}:

\begin{figure}[h]
\includegraphics{handlermapping.jpg}
\end{figure}

\subsubsection{If you forgot your admin password}

You will need to remove the initial configuration settings and recreate them in order to recover access. This configuration is stored locally on the server inside the Windows Registry. Use \texttt{regedit.exe} and navigate to \texttt{HKLM/SOFTWARE/NodePoint}.

\subsubsection{If automation tasks do not run}

Make sure the \textbf{nodepoint-automate} process is running every 5 minutes. Open the \textit{Task Scheduler} and look for the \textit{NodePoint Automation} task, which is added during installation.
}
{
\subsubsection{If you get a 500 server error}

Check in your server log what the likely error might be. Try to run \texttt{\textasciitilde/nodepoint/www/nodepoint.cgi} from a shell. Make sure you have a 64 bits OS.

\subsubsection{You see a list of files in your browser instead of the proper interface}

Make sure the \texttt{.htaccess} file is present, and that your Apache configuration allows overrides. Look for the line \textit{AllowOverride All} in your default site configuration file.

\subsubsection{If you get the error Could not access configuration file or initial settings don't seem to save}

Make sure NodePoint has write access to \texttt{../nodepoint.cfg}. Make sure the file only contains one line with the text \textit{dummy value}.

\subsubsection{If you get the error Could not access database}

Make sure NodePoint has write access to the database, by default \texttt{../db/nodepoint.db}.

\subsubsection{If you forgot your admin password}

Simply delete the \texttt{nodepoint.cfg} configuration file on the server and recreate it through the web interface from \textit{localhost}.

\subsubsection{If automation tasks do not run}

Make sure the \textbf{nodepoint-automate} process is running every 5 minutes. You should have a listing under \texttt{crontab -L} that shows the proper path, under a user that has access to the NodePoint database file.
}

\subsubsection{If email notifications don't work}

This is typically because of permission issues. Make sure that:
\begin{itemize}
\item The user that NodePoint runs under has permission to make network connections.
\item The firewall is allowing outgoing connections.
\item The SMTP server is accepting connections from the NodePoint server and email address you set.
\end{itemize}

\subsection{Best practices}
\subsubsection{Backup and restore}
NodePoint is likely to hold data you may not want to lose. The product stores information in three different locations:

\ifthenelse{\boolean{Windows}}{First, configuration data is stored in the Windows Registry.}{First, configuration data is stored in the \texttt{nodepoint.cfg} file.} There is very little information stored there, and you can view all of it on the \textbf{Settings} screen. Some of the information you may wish to backup includes:

\begin{itemize}
\item The administrator password.
\item The API read, write and encryption keys.
\item Information for your SMTP and Active Directory servers.
\item Any other changes made such as default access levels, database location etc.
\end{itemize}

The most important data is held in the database file. This includes tickets, articles, user information, and so on. This file should be backed up on a regular basis. You can simply copy the file somewhere through a script, or as part of a backup strategy.

Finally, all uploads are stored in the \texttt{../uploads} folder. This includes both images used with products, along with files uploaded by users as part of ticket comments. These should also be backed up as part of any backup strategy you may have.

If you use \textbf{Automation}, a backup module is provided that will make regular backups of your database and uploads.

Restoring a NodePoint installation is fairly simple. Once you have finished a new installation, simply restore your backed up data before accessing the NodePoint site. There is nothing to restart and no service to worry about.

\subsubsection{Data integrity}
Data stored in the \texttt{nodepoint.db} file is stored in SQL format, using very efficient and proven technology. Locks are used to avoid data duplication. Data corruption is unlikely, but backups should still be done in the rare case that something happens to the file. Partial recovery is possible but not something to be attempted on a live system.

This does mean that you can access the data directly using a SQLite client. This however is highly discouraged without first contacting us for support. Doing the \textit{vacuum} operation for example will corrupt your data.

\subsection{Security concerns}
By default, data stored in the database is accessible from anyone with login access to the server. As such, only administrators should be given physical access to the server used. User passwords are stored in a hashed format, both for the administrator and regular users. Administrative passwords for SMTP and automation tasks are stored in RC4 encrypted using the encryption key.

Using SSL is highly recommended and does not impact the operation of NodePoint at all. Simply add an encryption certificate to your web server and access the product normally.

The API provided uses a read and write key. This means that all operations are done as a special \textit{api} user. Different amounts of secrecy can be used for the two keys, such as users having access to a script that exposes the read key, but the write key only being used on a server side script. If \textbf{User Impersonation} is configured, the API calls can specify a specific user for a number of operations as well.

NodePoint provides a file upload system. This can be used to upload project images, file attachments to comments, and file upload fields on custom forms. Users may use this feature to upload malicious files. You should use an anti-virus software to scan for potential threats. You can also restrict the access level required to upload files on the \textbf{Settings} page, what file extensions are allowed, or disable uploads completely.

Finally, while NodePoint offers Active Directory integration, note that this is done using LDAP Simple Bind, not Kerberos, in order to allow both the Windows and Linux version to support this feature. LDAP over SSL can be implemented to provide encryption.

\subsection{Support}
If you need any functionality that is not covered by the base product, check out the \textbf{Resources} section of the web site where various scripts can be found. You can use the plugin options along with the JSON API to extend the functionality of the product.

Here are some examples of scripts available:

\begin{itemize}
\item Batch import of tickets from a CSV file.
\item Desktop ticket support client.
\item Create a ticket from a PowerShell prompt.
\end{itemize}

\subsection{Additional customization}

While NodePoint was created to support many features and provide several customization options, there are cases where you may have additional needs. This is why the source is freely available on GitHub for you to modify.

Alternatively, you can contact the author at \texttt{dendory@live.ca} if you have any suggestions for future versions of the product.

\clearpage
\section{Configuration}
Basic configuration options are defined the first time you access the product. The same information is available both from this initial configuration and at any point from the \textbf{Settings} page while logged in as the product administrator.

\subsection{Initial configuration}
When you first access NodePoint, you are presented with this screen:

\begin{figure}[h]
\includegraphics{initial.jpg}
\end{figure}

The page includes reasonable defaults along with some explanations of what each option does. You should read them carefully. At a minimum, you must specify an administrator password. Do not lose it, as it is required to change the configuration at a later point. Not all configuration options are shown on that page.

\subsection{Configuration options}

These are the configuration options for NodePoint. They have to be defined during first use and can be modified by the product administrator later from the \textbf{Settings} tab.

\begin{itemize}
\item \textbf{Database file:} This is the NodePoint database file.
\item \textbf{Upload folder:} Where product images and comment files are stored. Leave empty to disable uploads.
\item \textbf{Admin name:} The product administrator.
\item \textbf{Admin password:} The password for the administrator.
\item \textbf{Site name:} The name shown at the top left of the interface.
\item \textbf{Public notice:} This notice is shown on every page. Leave empty to remove the notice.
\item \textbf{Email notifications signature:} Replace the default signature at the bottom of all notification emails with this text.
\item \textbf{Bootstrap template:} This is a CSS file used by NodePoint. It should be a valid Bootstrap template, or could contain extra CSS elements you wish to use. The default file is \texttt{default.css} and contains a few such elements.
\item \textbf{Interface theme color:} The color of panels in the interface.
\item \textbf{Favicon:} This is the icon shown by web browsers in the URL bar.
\item \textbf{Main page logo:} Image file shown on the login page.
\item \textbf{Pinned article:} Article ID that will be pinned to the home page of all users when they log in.
\item \textbf{Ticket visibility:} When set to \textit{Public}, all tickets will be visible by logged in and guest users. When set to \textit{Private}, people need to log in to view tickets. \textit{Restricted} prevents people without at least the Restricted View access level from viewing others' tickets. This is a good choice for IT/Helpdesk systems.
\item \textbf{Disable Getting Started screen:} Do not show the getting started information to users who haven't logged in before.
\item \textbf{Hide closed tickets:} Do not show closed tickets on the Tickets page. This is mostly for performance and does not hide them from reports or prevents users from manually entering a closed ticket number.
\item \textbf{Allow HTML in articles:} Do not escape quotes and tags from articles.
\item \textbf{Allow registrations:} Allow guest users to register a new account. If set to no, the only way to add new accounts is if a user with Users Management access adds one.
\item \textbf{Allow guest tickets:} Allow guest users to file tickets.
\item \textbf{Maximum file upload:} Maximum file size for uploads to project images, ticket comments and the File Management component.
\item \textbf{Must be assigned to project:} Project specific functions like adding releases, adding tasks, adding, deleting or viewing secrets or uploading files are only available to users auto-assigned to the project.
\item \textbf{Allowed upload extensions:} A space separated list of file extensions allowed for uploads. All file types are allowed if left empty.
\item \textbf{Number of row per page:} Number of rows to show per page on tables.
\item \textbf{Session expiry time:} Number of hours before a user session is invalidated.
\item \textbf{API read key:} Used for some API operations.
\item \textbf{API write key:} Used for some API operations.
\item \textbf{Encryption key:} Used to encrypt some configuration values along with secrets.
\item \textbf{Allow user impersonation:} Allows API calls to specify a \textit{from\_user} parameter when creating a ticket.
\item \textbf{SMTP server:} The hostname for email notifications. Leave empty to disable email notifications.
\item \textbf{SMTP port:} The port number for SMTP connections, defaults to 25.
\item \textbf{SMTP username:} User for SMTP connections, if your mail server requires authentication.
\item \textbf{SMTP password:} Password for SMTP connections, if your mail server requires authentication.
\item \textbf{Support email:} The email from which email notifications come from.
\item \textbf{Type of portal:} The type of items NodePoint should manage. This is purely a UI customization.
\item \textbf{Active Directory server:} Enter your domain controller address to enable AD integration. Users will be created dynamically as they first log on, and passwords will be checked against AD using LDAP.
\item \textbf{Active Directory domain:} The domain to use for AD integration (in NT4 format).
\item \textbf{New users access level:} The access level for new users. The default is 1.
\item \textbf{Can upload files:} The minimum access level a user must have to attach files to comments.
\item \textbf{Can modify past ticket changes:} Below that level, users who edit tickets cannot change the previous description of the ticket.
\item \textbf{Can edit custom forms / routing:} The minimum access level to create and edit custom ticket forms or ticket routing under the Tools menu.
\item \textbf{Can view reports and statistics:} The minimum access level to access the statistics screen under the Tools menu.
\item \textbf{Can assign tasks to users:} The minimum access level to assign tasks to users on the Projects page.
\item \textbf{Can view user details:} Users of this access level or higher can view a list of users along with summary pages listing email addresses, access levels, tickets entered and checked out items.
\item \textbf{Can view client details:} The minimum access level to view details about clients, including billing information.
\item \textbf{Can add client events:} Users of this level can add and edit events.
\item \textbf{Can configure automation:} Users of this level can configure automation modules.
\item \textbf{Can add secrets:} Users of this level can add project secrets if the proper component is turned on.
\item \textbf{Can view secrets:} Users of this level can view project secrets if the proper component is turned on.
\item \textbf{Authentication plugin:} Use an external script or application to authenticate users. This can be used instead of the built-in users management or Active Directory integration if you have some other process to authenticate users. It must return 0 for success, 1 for failure. The following variables can be used: \textit{\%user\%} and \textit{\%pass\%}. For example: \texttt{python auth.py -userid \%user\% -passwd \%pass\%}
\item \textbf{Notifications plugin:} Call an application or script when a notification is sent. You can use the following variables here: \textit{\%user\%}, \textit{\%title\%} and \textit{\%message\%}. For example: \texttt{echo \%message\% >> ..\textbackslash \%user\%.log}
\item \textbf{Checkout plugin:} Call an application or script when an inventory item is checked out or approved. You can use the following variables here: \textit{\%user\%} and \textit{\%serial\%}. For example: \texttt{python add\_license.py -item \%serial\% -user \%user\%}
\item \textbf{Task completion plugin:} Call an application or script when a user completes (100\%) a task assigned to them. Variables allowed are: \textit{\%user\%}, \textit{\%task\%}, \textit{\%product\%} and \textit{\%due\%}. For example: \texttt{echo \%user\% completed task \%task\% due on \%due\% >> tasks.log}
\item \textbf{New tickets plugin:} You can call a script or application when a ticket is created. The available variables are \%ticket\%, \%user\%, \%title\%, \%description\%, \%product\% and \%release\%. For example: \texttt{curl --data 'ticketid=\%ticket\%\&user=\%user\%' https://hr.company.com/track.cgi}
\item \textbf{Ticket resolution plugin:} You can call a script or application every time a ticket is updated, along with the \%user\% who made the change, the new \%status\% of the \%ticket\%, the billable \%client\% and the \%resolution\%. Example: \texttt{echo \%ticket\%,\%user\%,\%status\%,\%resolution\%,\%client\% >> tickets.csv}
\item \textbf{Components}: Turn component on or off. See chapter 4 for information about components.
\end{itemize}

\clearpage

\section{Users and activity}
NodePoint provides a simple way to manage users based on their access levels. Users have a specific level, which determines what shows up to them in the interface and what they can do. They can also enter an optional email address for notifications. Email addresses must be confirmed through the sending of an automated token when they first register, if email notifications are turned on. Users also have a password which can be changed by the user under the \textbf{Settings} page, through the \textit{lost password} form, or reset by someone with the Users Management level. Users can be disabled but not deleted.

These are the access levels used by NodePoint along with a short description:

\def\arraystretch{1.3}
\begin{tabular}{ |p{15mm}|p{50mm}|p{100mm}| }
\hline
\textbf{Level} & \textbf{Name} & \textbf{Description}\\
\hline
6 & NodePoint Admin & Can change basic NodePoint settings.\\
5 & Users management & Can manage users, reset passwords, edit clients.\\
4 & Projects management & Can add, retire and edit projects, edit articles and items.\\
3 & Tickets management & Can create releases, update tickets, track time.\\
2 & Restricted view & Can view statistics, restricted tickets and products.\\
1 & Authorized users & Can create tickets and comments.\\
0 & Unauthorized users & Can view private tickets.\\
\hline
\end{tabular}

NodePoint has a few special user accounts. The \textit{admin} account is used as product administrator and acts differently than normal user accounts. It should only be used for administrative functions. \textit{Guest} is used to specify people who are not logged in, and cannot be registered as a user account. The name \textit{api} refers to any change made using the JSON API while \textit{System} is used for automation. The \textit{demo} account can be registered normally but cannot change its own password, and is meant for product demos.

\subsection{Users management}
Management of user passwords and access levels can be done by anyone with access level 5 or higher. With the right access level, this panel will then be available in the \textbf{Tools} menu:

\begin{figure}[h]
\includegraphics{userman.jpg}
\end{figure}

From there you can see a list of users, their email addresses, access levels, last logins along with the options to change their access levels and reset their passwords. Note that email notifications will only be sent out to users who confirmed their email addresses. This panel also allows passwords to be reset. You can see a summary page about a user by clicking on their names, and users can be disabled from that page.

Finally, new users can be created from that panel. If the \textbf{Allow Registrations} option is enabled, people can also register new user accounts from the login screen.

Here are the character restrictions for users:

\begin{itemize}
\item \textbf{User names:} Between 3 and 50 characters, using alphanumeric characters, plus the dot, at, underscore and dash. User names are not case sensitive during login authentication.
\item \textbf{Passwords:} At least 6 characters.
\item \textbf{Email addresses:} At the most 99 characters.
\end{itemize}

\subsection{Active Directory integration}
Active Directory integration can be enabled in the initial configuration, which will link the users management panel to your domain controller. Passwords will no longer be stored in the NodePoint database. Once a user attempts to login, their credentials will be validated, then NodePoint will check if a user entry exists in the database. A new entry will be created on first login. Users still need to set their email addresses for email notifications to work. Access levels can still be assigned to users to control their access to the NodePoint interface.

Authentication uses the LDAP protocol. You must specify a valid domain controller hostname or IP address for logins to work. If a login fails, you can view the log on the \textbf{System log} page to see the error message.

\subsection{Reports}
User activity can be monitored through two graphs along with a number of reports. The first graph shows the number of tickets entered per day for the last 7 days. The second one shows an overview of all tickets based on status. A calendar will also show time spent on tickets, due tasks and item expiration dates. Reports can be shown on the screen or downloaded as CSV documents to open in a spreadsheet application. Both are available to users of level 2 and higher (configurable) on the \textbf{Statistics} page.

\begin{figure}[h]
\includegraphics{reports.jpg}
\end{figure}

\subsection{Automation}
NodePoint ships with an automation engine called \textbf{nodepoint-automate}. This engine is set as a scheduled task during the installation process and controls the background automation modules, processes that can run without user intervention. Note that this is different from Ticket Routing which defines actions applied whenever someone submits a ticket. Automation modules instead run in the background on a specific schedule.

Each module can be enabled or disabled individually, and scheduled on its own schedule of 5 minutes, 15 minutes, hourly, daily or weekly. Configuration is done under the \textbf{Tools} menu by users with the proper access level. A log of these processes is also available there.

Note that some tasks may take a significant amount of time to run and slow the interface down for users while they are running. It is recommended that you set up resource intensive tasks outside of peak hours. You can also run the automation binary manually from the command line to get an idea of the time delay, or to troubleshoot any unexpected error. Clicking the \textit{Process all modules on next run} button will run every enabled module regardless of whether their schedule is up or not.

These are the available automation modules:

\subsubsection{Backup}
This module allows you to backup the database along with the uploads folder to another location for safe keeping. The folder must be a local or network path, and the type of backup can be a single archive file or a series of time stamped files.

\subsubsection{Email to Ticket}
This module can fetch emails from an IMAP account and turn them into tickets automatically. The mail server and inbox information must be provided in order to log in, along with information about the ticket to create, and whether to delete the email afterward.

\subsubsection{Bulk export}
This module will export a specific table to a file location automatically in CSV format. Specify the full path of a local file and the table to export.

\subsubsection{Users sync}
This module can keep the internal users list in sync with your Active Directory server. This requires credentials of a user with object listing rights. The Base DN should be the OU where your users are kept, and the filter can be used to filter specific object types. Emails can optionally be updated for all users as well.

\subsubsection{Computers sync}
This module will list computer objects from your Active Directory server and create entries in the Inventory Control component. The Base DN should be the OU where your computers are kept. The serial will be set to the machine's hostname. This requires credentials of a user with object listing rights.

\subsubsection{Ticket expiration}
This module interacts with active tickets that were modified more than x days ago. It can notify assigned users, and close the ticket.

\subsubsection{Reminder notifications}
This module will notify users about expired items checked out, overdue tasks and tickets requiring attention.

\subsubsection{ServiceNow CMDB}
This module can import CMDB data from a ServiceNow instance and import the data into the Inventory Control component. You need to enter valid credentials and the name of the table to read, along with the table header mappings.

\subsubsection{Update MOTD}
This module will dynamically update the MOTD from a text file.

\subsubsection{File expiration}
This module can remove files uploaded using the Files component after a set amount of days.

\subsubsection{Log export}
This module will export the system log, access log and automation log to a CSV file, and optionally clear the logs afterward.

\subsubsection{CSV inventory}
This module will import items from a CSV file. You must specify the file name and the column mappings.

\subsubsection{CSV projects}
This module fetches projects, releases and auto-assignments from a CSV file. You must specific column mappings and whether current assignments should be overwritten. The CSV should contain one line per release. Assignments must be comma separated usernames. Non-existent projects or releases will be created.

\subsubsection{ODBC inventory}
This module will connect to an ODBC database using the specified DSN or connection string and the credentials provided. You also need to supply the column numbers (in order retrieved by a SELECT statement, starting at 0) to map against the inventory component.

\clearpage
\section{Components}
NodePoint is divided into multiple components that you can enable or disable in the configuration as a product administrator. You can turn them on or off depending on your needs. For example, a help desk portal could have the \textbf{Tickets Management}, \textbf{Time Tracking} and \textbf{Support Articles} components enabled, while a project management portal could have only the \textbf{Tasks Management} component enabled.

\subsection{Tickets Management}
Users who have an access level of 1 or higher can enter tickets on the \textbf{Tickets} page. The default form asks for a title and description. Custom forms can also be designed and assigned to projects by users of access level 4 (configurable) and above. These custom forms can have up to 10 fields, each field can be a text area, a check box, satisfaction rating, priority, and so on. Projects that have a custom form assigned will display that form to users instead of the default one.

Users with access level 3 or above can modify tickets and be assigned to them. When a ticket is modified, the description is updated to show who modified it, and what parameters were changed, such as the title, description, status and resolution. Open tickets do not require a resolution, but each other status does. The list of tickets can be filtered based on project, status and so on.

\subsubsection{Custom forms}
To use custom forms, follow these steps:

\begin{enumerate}
\item Add at least one project on the \textbf{Projects} page.
\item Create a custom form on the \textbf{Custom forms} page.
\item Assign that form to the project.
\end{enumerate}

Here is an example of a custom form being built along with the result:

\begin{figure}[h]
\includegraphics[scale=0.5]{customform.jpg}
\includegraphics[scale=0.5]{customform2.jpg}
\end{figure}

\subsubsection{Favorites and assignment}
Any logged in user can favorite a ticket by clicking the proper option on the ticket's screen, which will add that ticket to their home screen for easy reference. This is meant as a way to follow an issue you are not actively working on.

Users with access level 3 or higher can assign themselves to a ticket by clicking the proper checkbox on the ticket's screen, or be assigned by a custom form. This is meant as a way to assign resources to a particular issue.

Auto assignment can also be used to assign specific users to any ticket filed against a project. You can add yourself to the auto-assign list of a product by clicking the button on the project's screen:

\begin{figure}[h]
\includegraphics{autoassign.jpg}
\end{figure}

Both favorite and assigned tickets are listed on the user's home page. Finally, email notifications are sent out when a ticket is created, modified or a comment is added. The notifications are sent to the ticket author and users assigned to that ticket.

\subsubsection{Ticket routing}
A number of automatic actions can be applied to new tickets based on a number of conditions. Note that this is different than the scheduled background processes provided by Automation modules. Routing is done every time a new ticket is submitted through the web interface.

An unlimited number of routes can be created under the \textbf{Tools} menu by users with the proper access level. Each route has a priority and will be evaluated in that order. When you edit a route, you have to specify which conditions the ticket has to evaluate against, and which actions to apply if the conditions are true. Any condition left blank will not be evaluated.

Some conditions and actions accept the following variables:

\begin{itemize}
\item \textbf{\%user\%} - The user who created the ticket.
\item \textbf{\%priority\%} - The priority of the ticket.
\item \textbf{\%ticket\%} - The ticket ID number.
\item \textbf{\%assigned\%} - The people auto-assigned to the ticket.
\item \textbf{\%title\%} - The title of the ticket.
\item \textbf{\%description\%} - The description of the ticket.
\item \textbf{\%product\%} - The project ID the ticket is filed with.
\item \textbf{\%release\%} - The release/milestone the ticket is associated to.
\item \textbf{\%field1\%} - The content of field 1 (custom forms only).
\item \textbf{\%field2\%} - The content of field 2 (custom forms only).
\item \textbf{\%field3\%} - The content of field 3 (custom forms only).
\item \textbf{\%field4\%} - The content of field 4 (custom forms only).
\item \textbf{\%field5\%} - The content of field 5 (custom forms only).
\item \textbf{\%field6\%} - The content of field 6 (custom forms only).
\item \textbf{\%field7\%} - The content of field 7 (custom forms only).
\item \textbf{\%field8\%} - The content of field 8 (custom forms only).
\item \textbf{\%field9\%} - The content of field 9 (custom forms only).
\end{itemize}

\subsection{Support Articles}
The \textbf{Articles} page hosts articles that can be used to document common use cases, common issues, or to build a knowledge base for your organization. Articles are created in \textit{draft} mode, which hides them from users with an access level lower than 4. Only once they are published are they visible to all users.

Users with at least access level 3 can link articles to tickets. These can be used to assign related documentation to issues, or used as categories. Linked tickets (except closed ones) are listed on the article's page, which makes it easy to track which tickets are assigned to a specific article.

\subsection{Time Tracking}
When updating a ticket, users can track the time they spent on the issue, which will be added to the time table for that ticket. A total of all the time spent on tickets by a specific user is shown on the \textbf{Settings} page. A report is also available with the total time spent by all users.

Time entries cannot be removed since it acts as a timestamp for work done. Corrections can be made by entering negative values.

\subsection{Shoutbox}
The shoutbox is a simple chat area on the home page that all users can use. The box shows the last few entries made by users, and individual entries can be deleted by users with access level 5 or higher.

\subsection{Tasks Management}
The tasks management component can be used to manage products or projects, create new tasks needed to be completed, and assign these tasks to users with due dates. Users with access level 4 (configurable) or higher can create new tasks on the \textbf{Projects} page.

\begin{figure}[h]
\includegraphics{projectmanagement.jpg}
\end{figure}

If \textit{Must be assigned to project} is on, users also need to be auto-assigned to that particular project to add tasks. New tasks created will send an email notification to the user assigned to the task, and all tasks will show on the user's home page. There, users can modify the completion rate for each item. Completed and overdue tasks will also be highlighted.

\subsection{Clients Directory}
The clients directory can be used to keep track of clients or contacts from the \textbf{Clients} page. New clients can be added along with specific information such as email, phone number or notes by users of access level 5, details seen by users of level 2 (configurable) and higher. The following statuses can be assigned to clients:

\begin{itemize}
\item \textbf{Prospect:} A prospective client.
\item \textbf{Supplier:} A vendor.
\item \textbf{Contact:} A contact entry.
\item \textbf{Paid:} A paid client.
\item \textbf{Unpaid:} An unpaid or default client.
\item \textbf{Closed:} A closed entry (no ticket can be filed against closed clients).
\end{itemize}

If the clients directory is enabled, the \textit{Clients} field type can be added to custom ticket forms, which will show a drop down list of available clients when creating new tickets, to assign issues to specific clients. Events can be added by users of the right access level to log phone calls, emails and other communications. This can be used as a CRM portal to keep track of interactions with clients.

\subsection{Secrets Vault}
The Secrets Vault is a component that allows users assigned to a project to store secrets (credentials, phone numbers, anything that should be restricted) on a project basis. This will add a tab to any project page where users of a specific level can add secrets, and others can view them. Accessing secrets is logged in the system log for auditing purposes.

Both the level to add and to view secrets are configured in the product base configuration. If \textit{Must be assigned to project} is on, users also need to be auto-assigned to that particular project to be able to add or view secrets. The secrets are encrypted inside of the database using the encryption key. Changing that key will make the data irretrievable.

\subsection{Inventory Control}
The Inventory Control component allows users with access level 4 or higher to add items to the \textbf{Items} page. These items can be linked to projects or clients, and can be checked out by other users. Approval can also be required, in which case users will need to request access, and someone with access level 4 or higher will need to approve the request. This component can be used to keep track of physical items such as computers, keyboards and phones, or even software licenses. Bulk items can be added with a script using the API, or from a number of sources using Automation modules.

\begin{figure}[h]
\includegraphics{items.jpg}
\end{figure}

When a user checks out an item, additional information such as item location, lock codes or other secret data can be displayed on the item's page and sent through email to that user, so that the person can retrieve it. This information field is otherwise hidden from other users. If approval is required, a notification will be sent to all available users with level 4 or higher. They can then decide to approve or deny the request. Items can have an expiration date, after which checkouts will be disabled.

Users with checked out items will see a list of items on their home page, and can then return the item, making it available to other users. A checkout history is kept for each item and shown on the item page, showing all past requests and events. Items can also be manually assigned to users, or made unavailable, preventing any user from requesting or checking them out.

An \textit{items} field is available to be used in custom ticket forms, which will display a list of checked out items that the user can file tickets about. Finally, a checkout plugin can be used to accomplish some automated task when an item is checked out, approved or assigned to a user.

\subsection{Billing}
The Billing component allows you to assign tickets to clients with either a fixed rate or an hourly rate. As such, this component requires the Tickets Management and Clients Directory components to be enabled. For hourly rates, the Time Tracking component must also be enabled.

When editing a ticket, a user of level 3 of higher can select a client from the drop down list, among clients that have been entered in the directory. Once tickets have been assigned to a client, the list of tickets with hours spent are listed on that client's page. The cost and currency can also be adjusted on that page.

\subsection{Files Management}
NodePoint provides a files upload feature for things like project images and file attachments to comments. If you enable the Files Management component, an option will be available under the \textbf{Tools} menu for users to upload arbitrary files as well. These files will be listed on that page, along with a direct link to download them, which can be used to send to clients or partners who may not have a user account configured.

\begin{figure}[h]
\includegraphics{files.jpg}
\end{figure}

Note that the files uploaded here, along with those attached to projects or tickets, must be below the configured maximum file size value. Also, only users with an access level above the one configured in the initial settings will be able to upload files. The files themselves will be available to anyone with the link.

Files can also be uploaded on a project specific basis on the project page, and will show up both under \textit{Tools} and that project's page.  If \textit{Must be assigned to project} is on, users also need to be auto-assigned to that particular project to upload files.

\clearpage
\section{JSON API}
NodePoint provides a JSON based REST API that can be used to accomplish some basic tasks. This can be useful to integrate the system with existing products, or to use with custom scripts. Each API call requires either the \textit{read} or \textit{write} key. The calls can be made using GET or POST.

Here is a list of commands. Arguments in \textit{italics} are optional:

\subsection{List users}

\begin{itemize}
\item api=list\_users
\item key=<read key>
\end{itemize}

\subsection{List projects}

\begin{itemize}
\item api=list\_products
\item key=<read key>
\end{itemize}

\subsection{Add new user}

\begin{itemize}
\item api=add\_user
\item key=<write key>
\item user=<user name>
\item password=<password>
\item email=<email>
\end{itemize}

\subsection{Verify user password}

\begin{itemize}
\item api=verify\_password
\item key=<read key>
\item user=<user name>
\item password=<password>
\end{itemize}

\subsection{Change user password}

\begin{itemize}
\item api=change\_password
\item key=<write key>
\item user=<user name>
\item password=<password>
\end{itemize}

\subsection{List tickets by product}

\begin{itemize}
\item api=list\_tickets
\item key=<read key>
\item product\_id=<product id>
\end{itemize}

\subsection{Show ticket}

\begin{itemize}
\item api=show\_ticket
\item key=<read key>
\item id=<ticket id>
\end{itemize}

\subsection{Add release}

\begin{itemize}
\item api=add\_release
\item key=<write key>
\item product\_id=<product id>
\item release\_id=<release id>
\item notes=<notes>
\end{itemize}

\subsection{Add ticket}

\begin{itemize}
\item api=add\_ticket
\item key=<write key>
\item product\_id=<product id>
\item release\_id=<release id>
\item title=<ticket title>
\item description=<ticket description>
\item \textit{priority=<Low|Normal|High>}
\item \textit{from\_user=<valid user>}
\end{itemize}

\subsection{Update ticket}

\begin{itemize}
\item api=update\_ticket
\item key=<write key>
\item id=<ticket id>
\item \textit{summary=<summary of changes>}
\item \textit{status=<New|Open|Invalid|Hold|Duplicate|Resolved|Closed>}
\item \textit{resolution=<new resolution>}
\item \textit{priority=<Low|Normal|High>}
\item \textit{time\_spent=<number of hours>}
\item \textit{from\_user=<valid user>}
\end{itemize}

\subsection{Assign ticket}

\begin{itemize}
\item api=assign\_ticket
\item key=<write key>
\item id=<ticket id>
\item user=<valid user>
\item \textit{from\_user=<valid user>}
\end{itemize}

\subsection{Add comment}

\begin{itemize}
\item api=add\_comment
\item key=<write key>
\item id=<ticket id>
\item comment=<comment>
\end{itemize}

\subsection{Show time tracking}

\begin{itemize}
\item api=show\_time
\item id=<ticket id>
\item key=<read key>
\end{itemize}

\subsection{Show billing information}

\begin{itemize}
\item api=show\_billing
\item client=<client name>
\item key=<read key>
\end{itemize}

\subsection{Add task}

\begin{itemize}
\item api=add\_task
\item key=<write key>
\item product\_id=<product id>
\item description=<task description>
\item user=<assigned user>
\item due=<mm/dd/yyyy>
\end{itemize}

\subsection{List tasks}

\begin{itemize}
\item api=list\_tasks
\item key=<read key>
\item user=<user>
\end{itemize}

\subsection{Add client}

\begin{itemize}
\item api=add\_client
\item key=<write key>
\item name=<client name>
\item status=<client status>
\item contact=<contact address>
\item \textit{notes=<notes>}
\end{itemize}

\subsection{List clients}

\begin{itemize}
\item api=list\_clients
\item key=<read key>
\end{itemize}

\subsection{List client events}

\begin{itemize}
\item api=list\_events
\item key=<read key>
\item client\_id=<client id>
\end{itemize}

\subsection{Add client event}

\begin{itemize}
\item api=add\_event
\item key=<write key>
\item client\_id=<client id>
\item summary=<event summary>
\item type=<event type>
\item \textit{notes=<notes>}
\item \textit{from\_user=<valid user>}
\end{itemize}

\subsection{Add item}

\begin{itemize}
\item api=add\_item
\item key=<write key>
\item product\_id=<0|product id>
\item client\_id=<0|client id>
\item approval=<0|1>
\item type=<item type>
\item name=<item name>
\item serial=<serial number>
\item info=<additional information>
\end{itemize}

\subsection{List files}

\begin{itemize}
\item api=list\_files
\item key=<read key>
\end{itemize}

\subsection{List items}

\begin{itemize}
\item api=list\_items
\item key=<read key>
\end{itemize}

\subsection{Assign item}

\begin{itemize}
\item api=assign\_item
\item key=<write key>
\item id=<item id>
\item user=<user>
\end{itemize}

\subsection{Return item}

\begin{itemize}
\item api=return\_item
\item key=<write key>
\item id=<item id>
\end{itemize}

\subsection{Update item}

\begin{itemize}
\item api=update\_item
\item key=<write key>
\item id=<item id>
\item \textit{product\_id=<product id>}
\item \textit{client\_id=<client id>}
\item \textit{approval=<0|1>}
\item \textit{type=<item type>}
\item \textit{name=<item name>}
\item \textit{serial=<serial number>}
\item \textit{info=<additional information>}
\item \textit{expiration=<mm/dd/yyyy>}
\end{itemize}

\subsection{Code samples}
The following code snippets are Python 3.x examples of how to access the API.

\subsubsection{List users}
This script connects to a local NodePoint instance and lists all the users:

\begin{lstlisting}
import json
import urllib.request
import urllib.parse

post_params = {'api': 'list_users', 'key': '0qvG4DkOz8NZnkuhn4aczck7r3qQVYS4'}

post_args = urllib.parse.urlencode(post_params)
data = post_args.encode()
stream = urllib.request.urlopen('http://localhost/nodepoint/', data)
result = stream.read()
charset = stream.info().get_param('charset', 'utf8')
users = json.loads(result.decode(charset))
for user in users['users']:
    print(user['name'])
\end{lstlisting}

\subsubsection{Add ticket}
This script connects to a local NodePoint instance and adds a ticket:

\begin{lstlisting}
import json
import urllib.request
import urllib.parse

post_params = {'api': "add_ticket", 'key': "c5BT44108cYxBRHOYLnykazACsjkkQP3",
 'product_id': "1", 'release_id': "1.0", 'title': "This is a test ticket",
 'description': "Some description"}

data = urllib.parse.urlencode(post_params)
conn = urllib.request.urlopen("http://127.0.0.1/nodepoint/?" + data)
stream = conn.read()
result = json.loads(stream.decode(conn.info().get_param('charset', 'utf8')))
print(str(result))
\end{lstlisting}

\clearpage
\section{License}
The MIT License (MIT)

Copyright \textcopyright \thinspace 2014-\the\year \thinspace Patrick Lambert

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

\end{document}


text:
Conway's Game of Life implemented with Python and Pygame

Initial commit by Tom Burrows - September 2017

Run individual files by editing the global variable `FILE` at the top of `game_of_life.py` or run `demo.py` which will go through all the files in `demo_levels/`. The first number on the first line of the demo files is how many iterations to run it for, the second number is how many seconds in between each iteration.


text:
dumps_version="dumps_3"
ARTIFACTS_USER='cidownload'
ARTIFACTS_PASSWORD='AP6JaG9ToerxBc7gWP5LcU1CNpb'
ARTIFACTS_URL="https://sharpdebug.jfrog.io/sharpdebug/api/storage/generic-local/$dumps_version/"

command -v curl >/dev/null 2>&1 || { echo "Please install 'curl'." >&2; exit 1; }
command -v unzip >/dev/null 2>&1 || { echo "Please install 'unzip'." >&2; exit 1; }

files=$(curl -u$ARTIFACTS_USER:$ARTIFACTS_PASSWORD $ARTIFACTS_URL 2>/dev/null | grep -Po '(?<="uri" : "/)[^"]*')
for file in $files ; do
    url="https://sharpdebug.jfrog.io/sharpdebug/generic-local/$dumps_version/$file"
    echo $url '-->' $file
    curl -u$ARTIFACTS_USER:$ARTIFACTS_PASSWORD $url --output $file 2>/dev/null
    extract_path=$(pwd)
    if grep -q clr "$file"; then
        subfolder="${file%.*}"
        extract_path="$extract_path/$subfolder"
    fi
    unzip -qo $file -d $extract_path
    rm $file
done


text:
/*jslint browser: true, indent: 2 */
(function (global) {
  "use strict";
  var HTTPED = new Object();

  /*
   * Perform an http GET operation and return the results in
   * either an "onComplete" or "onStateChange" callback.
   * @method Get
   * @param {string} a URL to fetch
   * @param {function} an "onComplete" callback. Callback will
   * receive a http status code as the first parameter followed by the
   * response received from the xhr request.
   * @param {function} an "onStateChange" callback. Callback will
   * receive a "state" code and the response object from the xhr request.
   * States can be
   * 0. unsent
   * 1. opened
   * 2. headers received
   * 3. loading
   * State 4 is given to the "onComplete" callback instead of "onStateChange".
   */
  function Get(url, onComplete, onStateChange) {
    var xhr;

    // xhr handling is inspired by code samples at Mozilla's dev site
    // https://developer.mozilla.org/en-US/docs/AJAX/Getting_Started
    if (window.XMLHttpRequest) { // Mozilla, Safari, ...
      xhr = new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE 8 and older
      try {
        xhr = new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          xhr = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
      }
    }
    if (!xhr) {
      throw("remote xhr not supported.");
    }

    // Manage the state changes and trigger appropriate callback.
    function manageStateChange () {
      if (xhr.readyState === 4) {
        // Two params, http status, response
        onComplete(xhr.status, xhr.response);
      } else {
        onStateChange(xhr.readyState, xhr.response);
      }
    }

    xhr.onreadystatechange = manageStateChange;
    xhr.open('GET', url);
    xhr.send(null);
  };
  HTTPED.Get = Get;

  /*
   * Post will prep and send a post to the URL provided.
   * @method Post
   * @param {string} the URL to post to.
   */
  function Post(url, data) {
    //FIXME: implement data encoding and transmition.
    thow("Post not implemented.");
  }
  HTTPED.Post = Post;

  global.HTTPED = HTTPED;
}(this));


text:
.. _markup:

The markup plugin
=================

The `markup` plugin provides support for lightweight "markup" languages.

  .. image:: /images/plugins/markup-admin.*
     :width: 732px
     :height: 191px

The markup language is rendered as HTML:

  .. image:: /images/plugins/markup-html.*
     :width: 690px
     :height: 190px

The plugin provided support for the markup languages:

* **reStructuredText**: The syntax known for Python documentation.
* **Markdown**: The syntax known for GitHub and Stack Overflow comments (both do have a dialect/extended version)
* **Textile**: The syntax known for Redmine and partially used in Basecamp and Google+.

For technical savy users, this makes it easy to write pages in a consistent style,
or publish wiki/RST documentation online.

The plugin uses django.contrib.markup_ internally to provide the translation of the markup to HTML,
hence it supports any markup language that django.contrib.markup_ has a filter for.

Installation
------------

Install the dependencies via *pip*::

    pip install django-fluent-contents[markup]

Depending on the choosen markup language, the required dependencies can also installed separately using `pip`:

For **reStructuredText**, use::

    pip install docutils

For **Markdown**, use::

    pip install Markdown

For **Textile**, use::

    pip install textile

Add the following settings to ``settings.py``:

.. code-block:: python

    INSTALLED_APPS += (
        'fluent_contents.plugins.markup',
    )

    FLUENT_MARKUP_LANGUAGES = ['restructuredtext', 'markdown', 'textile']

Configuration
-------------

The following settings are available:

.. code-block:: python

    FLUENT_MARKUP_LANGUAGES = ['restructuredtext', 'markdown', 'textile']
    FLUENT_MARKUP_MARKDOWN_EXTRAS = ["extension1_name", "extension2_name", "..."]

FLUENT_MARKUP_LANGUAGES
~~~~~~~~~~~~~~~~~~~~~~~

Define which markup language should be used.

This is a list/tuple, which can contain the following values:

* *restructuredtext*
* *markdown*
* *textile*

By default, all languages are added.

FLUENT_MARKUP_MARKDOWN_EXTRAS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Define the markdown extensions to use.

.. _django.contrib.markup: https://docs.djangoproject.com/en/dev/ref/contrib/markup/


text:
<?php
/**
 * Interception filter to check if conditions are fulfilled.
 *
 * @package LydiaCore
 */
class CInterceptionFilter extends CObject implements ISingleton {

  /**
   * Members
   */
  private static $instance = null;

 /**
   * Constructor
   */
  public function __construct() { parent::__construct(); }

  /**
   * Singleton pattern. Get the instance of the latest created object or create a new one.
   *
   * @return CLydia The instance of this class.
   */
  public static function Instance() {
    return is_null(self::$instance) ? self::$instance = new static : self::$instance;
  }

  /**
   * Check if user is logged in and has administrator role, redirect to login-page or
   * display forbidden depending on privilegies.
   *
   * @return CInterceptionFilter to allow chaining.
   */
  public function AuthenticatedOrLogin() {
    if(!$this->user->IsAuthenticated()) {
      $this->session->SetFlash('redirect_on_success', $this->request->request);
      $this->RedirectTo('user/login');
    }
    return $this;
  }

  /**
   * Check if user is logged in and has administrator role, redirect to login-page or
   * display forbidden depending on privilegies.
   *
   * @param string $msg to display.
   * @return CInterceptionFilter to allow chaining.
   */
  public function AdminOrForbidden($msg=null) {
    $msg = isset($msg) ? $msg : t('You need admin-privileges to access this content.');
    if(!$this->user->IsAdmin()) {
      $this->ShowErrorPage(403, $msg);
    }
    return $this;
  }

  /**
   * Check if user is owner of content, or has administrator role else redirect to forbidden.
   *
   * @param CMContent $content to display.
   * @return CInterceptionFilter to allow chaining.
   */
  public function OwnerAdminOrForbidden($content) {
    if($this->user->IsAdmin() || $content->CurrentUserIsOwner()) {
      return $this;
    }
    $msg = t('You do not have privileges to access this content.');
    return $this->ShowErrorPage(403, $msg);
  }

  /**
   * Check if secret matches in config.php. This enables remote management of some features
   * without being loggedin.
   *
   * @param string $key secret key to match in config.php.
   * @return CInterceptionFilter to allow chaining.
   */
  public function MatchSecretKey($secret=null) {
    if($this->config['secret_key'] != $secret) {
      $this->ShowErrorPage(403, t('You need admin-privileges to access this page.'));
    }
    return $this;
  }

  /**
   * Check if user acronym is same as current session acronym.
   *
   * @param string $acronym
   * @return boolean true or false.
   */
  public function SessionUserMatches($acronym) {
    $user = $this->session->GetAuthenticatedUser();
    if($acronym == $user['acronym']) {
      return true;
    }
    $this->AddMessage('error', t('User conflict between value and session.'));
    return false;
  }

  /**
   * Allow creating new users or display forbidden.
   *
   * @return CInterceptionFilter to allow chaining.
   */
  public function CreateNewUserOrForbidden() {
    if(!$this->config['create_new_users']) {
      $this->ShowErrorPage(403, t('Create new user is disabled by site owner.'));
    }
    return $this;
  }

  /**
   * Is regular user or display forbidden.
   *
   * @return CInterceptionFilter to allow chaining.
   */
  public function IsRegularUserOrForbidden() {
    if(!$this->user->IsUser()) {
      $this->ShowErrorPage(403, t('You are just visiting this site and have no profile.'));
    }
    return $this;
  }

  /**
   * Display forbidden for all anonomous users.
   *
   * @return CInterceptionFilter to allow chaining.
   */
  public function AnonomousToForbidden() {
    if($this->user->IsAnonomous()) {
      $this->ShowErrorPage(403, t('You need to login to access this content.'));
    }
    return $this;
  }

}


text:
<?php
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace App\Tests;

use App\Entity\Interest;
use App\Entity\Service;
use App\Entity\ServiceDraft;
use App\Entity\User;
use App\Tests\ApiTestCase;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;

class ServiceDraftsTest extends ApiTestCase
{
    // This trait provided by HautelookAliceBundle will take care of refreshing the database content to a known state before each test
    use RefreshDatabaseTrait;

    const KNOWN_SERVICE_NAME_1 = 'Senior Center Fitness Discounts';
    const KNOWN_SERVICE_NAME_2 = 'Food Tax Rebate Program';
    const KNOWN_DRAFT_NAME = 'Food Tax Rebate Program';

    public function testGetCollectionAdminUser(): void
    {
        // The client implements Symfony HttpClient's `HttpClientInterface`, and the response `ResponseInterface`
        $response = $this->client->request('GET', '/api/service_drafts', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->adminToken,
            ],
        ]);

        $this->assertResponseIsSuccessful();
        // Asserts that the returned content type is JSON-LD (the default)
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');

        // Asserts that the returned JSON is a superset of this one
        $this->assertJsonContains([
            '@context' => '/api/contexts/ServiceDraft',
            '@id' => '/api/service_drafts',
            '@type' => 'hydra:Collection',
            'hydra:totalItems' => 3,
        ]);

        // Because test fixtures are automatically loaded between each test, you can assert on them
        $this->assertCount(3, $response->toArray()['hydra:member']);

        // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
        // This generated JSON Schema is also used in the OpenAPI spec!
        $this->assertMatchesResourceCollectionJsonSchema(ServiceDraft::class);
    }

    public function testGetCollection(): void
    {
        // The client implements Symfony HttpClient's `HttpClientInterface`, and the response `ResponseInterface`
        $response = $this->client->request('GET', '/api/service_drafts', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseIsSuccessful();
        // Asserts that the returned content type is JSON-LD (the default)
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');

        // Asserts that the returned JSON is a superset of this one
        $this->assertJsonContains([
            '@context' => '/api/contexts/ServiceDraft',
            '@id' => '/api/service_drafts',
            '@type' => 'hydra:Collection',
            'hydra:totalItems' => 2,
        ]);

        // Because test fixtures are automatically loaded between each test, you can assert on them
        $this->assertCount(2, $response->toArray()['hydra:member']);

        // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
        // This generated JSON Schema is also used in the OpenAPI spec!
        $this->assertMatchesResourceCollectionJsonSchema(ServiceDraft::class);
    }

    public function testShareDraft(): void
    {
        $response = $this->client->request('GET', '/api/service_drafts', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->adminToken,
            ],
        ]);

        $adminDrafts = $response->toArray()['hydra:member'];

        $this->assertResponseIsSuccessful();
        $this->assertCount(3, $adminDrafts);

        // Verify that the last draft was not created by, nor shared with 'user'
        $this->assertArraySubset(
            ['createdBy' => 'guest1',
                'shares' => [],
            ], $adminDrafts[2]);

        // Share the last draft with 'user'
        $response = $this->client->request('POST', '/api/service_draft_shares', [
            'json' => [
                'draftService' => $adminDrafts[2]['@id'],
                'sharedWithUsername' => 'user',
            ],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->adminToken,
            ],
        ]);
        $this->assertResponseIsSuccessful();

        // Now retrieve all the drafts for 'user'.
        $response = $this->client->request('GET', '/api/service_drafts', [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains([
            '@context' => '/api/contexts/ServiceDraft',
            '@id' => '/api/service_drafts',
            '@type' => 'hydra:Collection',
            'hydra:totalItems' => 3, // With newly shared service, now there are 3.
        ]);
        $userDrafts = $response->toArray()['hydra:member'];
        $this->assertCount(3, $userDrafts);

        // Verify that the newly shared draft is now there.
        $this->assertArraySubset(
            ['createdBy' => 'guest1',
                'shares' => [[
                    'sharedByUsername' => 'admin',
                    'sharedByDisplayName' => 'admin',
                    'sharedWithUsername' => 'user',
                ]],
            ], $userDrafts[2]);
    }

    public function testCreateDraft(): void
    {
        $user = $this->em->getRepository(User::class)->findOneBy(['username' => 'user']);
        $numEdits = $user->numEdits;

        $newServiceName = 'A wonderful service available to all.';
        $newServiceUrl = 'http://myservice.com';
        $serviceIri = static::findIriBy(Service::class, ['name' => static::KNOWN_SERVICE_NAME_1]);
        $interestIri = static::findIriBy(Interest::class, ['name' => 'Housing']);
        $response = $this->client->request('POST', '/api/service_drafts', [
            'json' => [
                'name' => $newServiceName,
                'applicationOnline' => $newServiceUrl,
                'interests' => [$interestIri],
                'service' => $serviceIri,
            ],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/api/contexts/ServiceDraft',
            '@type' => 'ServiceDraft',
            'name' => $newServiceName,
            'applicationOnline' => $newServiceUrl,
            'interests' => [[
                '@id' => $interestIri,
                '@type' => 'Interest',
                'name' => 'Housing',
                'materialIcon' => 'home',
            ]],
            'lastModifiedBy' => 'user',
            'lastModifiedAgo' => '1 second ago',
            'numEdits' => 1,
        ]);

        $this->assertArraySubset(
            ['@id' => $serviceIri], $response->toArray()['service']);

        // Ensure the id is a valid uuid.
        $this->assertRegExp('~^/api/service_drafts/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$~', $response->toArray()['@id']);
        $this->assertMatchesResourceItemJsonSchema(ServiceDraft::class);

        /** @var User */
        $user = $this->em->getRepository(User::class)->findOneBy(['username' => 'user']);
        $this->assertArraySubset([
            'username' => 'user',
            'numEdits' => $numEdits + 1,
        ], get_object_vars($user));
    }

    public function testUpdateDraft(): void
    {
        $user = $this->em->getRepository(User::class)->findOneBy(['username' => 'user']);
        $numEdits = $user->numEdits;

        $iri = static::findIriBy(ServiceDraft::class, ['name' => static::KNOWN_DRAFT_NAME]);

        $updatedDescription = 'A newly updated description for testing.';
        $this->client->request('PUT', $iri, [
            'json' => ['description' => $updatedDescription],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains([
            '@id' => $iri,
            'name' => static::KNOWN_DRAFT_NAME,
            'description' => $updatedDescription,
            'lastModifiedBy' => 'user',
            'lastModifiedAgo' => '1 second ago',
            'numEdits' => 2,
        ]);

        /** @var User */
        $user = $this->em->getRepository(User::class)->findOneBy(['username' => 'user']);
        $this->assertArraySubset([
            'username' => 'user',
            'numEdits' => $numEdits + 1,
        ], get_object_vars($user));
    }

    public function testCreateDraft_CollisionWithExistingDraft(): void
    {
        $newServiceName = 'A wonderful service available to all.';
        $newServiceUrl = 'http://myservice.com';
        $serviceIri = static::findIriBy(Service::class, ['name' => static::KNOWN_SERVICE_NAME_2]);
        $interestIri = static::findIriBy(Interest::class, ['name' => 'Housing']);
        $response = $this->client->request('POST', '/api/service_drafts', [
            'json' => [
                'name' => $newServiceName,
                'applicationOnline' => $newServiceUrl,
                'interests' => [$interestIri],
                'service' => $serviceIri,
            ],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseStatusCodeSame(400);
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/api/contexts/ConstraintViolationList',
            '@type' => 'ConstraintViolationList',
            'hydra:title' => 'An error occurred',
            'hydra:description' => "service: This value is already used.",
        ]);
    }

    public function testDraftPropertyInService(): void
    {
        $serviceIri = static::findIriBy(Service::class, ['name' => static::KNOWN_SERVICE_NAME_2]);
        $draftIri = static::findIriBy(ServiceDraft::class, ['name' => static::KNOWN_SERVICE_NAME_2]);
        $response = $this->client->request('GET', $serviceIri, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseStatusCodeSame(200);
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/api/contexts/Service',
            '@type' => 'Service',
            'name' => static::KNOWN_SERVICE_NAME_2,
        ]);

        $draft = $response->toArray()['draft'];

        // Verify that the newly shared draft is now there.
        $this->assertArraySubset([
            '@id' => $draftIri,
            '@type' => 'ServiceDraft',
        ], $draft);
    }

    public function testCreateInvalidServiceDraft_missingRequiredFields(): void
    {
        $this->client->request('POST', '/api/service_drafts', [
            'json' => [
                'name' => '',
            ],
            'headers' => [
                'Authorization' => 'Bearer ' . $this->userToken,
            ],
        ]);

        $this->assertResponseStatusCodeSame(400);
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/api/contexts/ConstraintViolationList',
            '@type' => 'ConstraintViolationList',
            'hydra:title' => 'An error occurred',
            'hydra:description' => "name: This value should not be blank.",
        ]);
    }

}


text:
This program allows the user to view ASCII .ply files on their phone
using OpenGL ES.

![PLY Reader GUI](https://j.gifs.com/n5Z0AW.gif)

## How to Use ##

This project is written with Gradle support, and does not use
JNI. This should make it a breeze to compile in Android Studio. There
is only one .ply located in memory, but the parser is not unique to
this file; the only requirement is that it be in ASCII format.

- Drag to rotate the object around the screen.
- Move two fingers towards/away each other for a scaling effect. It's
  recommended that your first finger stay in one place on the screen,
  to avoid simultaneous rotation of the object.

And that's it! Since there are so many vertices, it takes a couple of
seconds for anything to show on the screen (~2 sec on my HTC One
M8). There are myriad improvements to be made to the app, but it's
complete enough for a proof-of-concept.


text:
import gobject

class Signalizable(gobject.GObject):
    """This class represents a GObject-like signalized object"""

    def __init__(self):
        gobject.GObject.__init__(self)

    def install_signal(self, signal):
        if not gobject.signal_lookup(signal, self.__class__):
            gobject.signal_new(signal, self.__class__, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT,))


text:
package com.nimbusware.mypersonalbiketrainer;

public interface SensorListener {

	public void updateConnectionState(int status, int newState);
}


text:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace DivineInject.FactoryGenerator
{
    internal interface IFactoryMethod
    {
        ConstructorInfo Constructor { get; }
        string Name { get; }
        Type ReturnType { get; }
        Type ReturnImplType { get; }
        Type[] ParameterTypes { get; }
        IList<IConstructorArgDefinition> ConstructorArgs { get; }
        void EmitMethod(TypeBuilder tb, IConstructorArgList constructorArgList);
    }
}


text:
package org.assertj.core.api;

import static org.mockito.Mockito.mock;

import java.time.LocalDateTime;

import org.assertj.core.internal.Comparables;

public abstract class LocalDateTimeAssertBaseTest extends BaseTestTemplate<LocalDateTimeAssert, LocalDateTime> {

  protected Comparables comparables;
  protected LocalDateTime now = LocalDateTime.now();

  @Override
  protected void inject_internal_objects() {
    super.inject_internal_objects();
    comparables = mock(Comparables.class);
    assertions.comparables = comparables;
  }

  @Override
  protected LocalDateTimeAssert create_assertions() {
    return new LocalDateTimeAssert(now);
  }

}


text:
function S = catstruct(dim, varargin)
%CATSTRUCT Concatenate structures with dissimilar fields
%
%  S = catstruct(dim, A1, A2, A3, ...)
%
% This function concatenates the input structures along the dimension dim.
% The resulting structures contains all fields found in any of the input
% structures.  If an input structure did not originally have that field,
% the new field is left empty.

% Copyright 2008 Kelly Kearney

dims = cell2mat(cellfun(@size, varargin, 'uni', 0)');

ndim = length(dims);
checkdim = dims;
checkdim(:,dim) = [];
if length(unique(checkdim, 'rows')) ~= 1
    error('Inconsistent dimensions along concatenation');
end

allfields = cellfun(@fieldnames, varargin, 'uni', 0);
allfields = unique(cat(1, allfields{:}));

for istruct = 1:length(varargin)
    s{istruct} = varargin{istruct};
    for ifield = 1:length(allfields)
        if ~isfield(s{istruct},allfields{ifield})
            [s{istruct}.(allfields{ifield})] = deal([]);
        end
    end
end

S = cat(dim, s{:});


text:
@interface ResultViewController : UIViewController {
    IBOutlet UITextView *textView;
}
@property (readonly)UITextView *textView;
@end