<commit_before>/* eslint-disable no-unused-vars */

// 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
//
//     https://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.

const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'];
const WEEK_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
    'Friday', 'Saturday'];

let eventsDictionary = {};

/** Class used to define the basic characteristics of a group. */
class Group {
  /**
   * Creates a new group with the given parameters.
   * @param {String} university The name of the university.
   * @param {String} degree The name of the degree.
   * @param {int} year The year of study.
   */
  constructor(university, degree, year) {
    /** @private @const {String} */
    this.university_ = university;
    /** @private @const {String} */
    this.degree_ = degree;
    /** @private @const {int} */
    this.year_ = year;
  }
}

/** Class used to define the basic characteristics of an event. */
class Event {
  /**
   * Creates a new event with the given parameters.
   * @param {String} title The title of the event.
   * @param {String} start The start date of the event.
   * @param {int} end The end date of the event.
   */
  constructor(title, start, end) {
    this.title = title;
    this.start = start;
    this.end = end;
  }
}

/**
 * Adds the events associated with that date to the element.
 * @param {Date} date The date for which the events will be added.
 * @param {Element} dateElement The element in which the events will be added.
 */
function addDayEvents(date, dateElement) {
  const events = eventsDictionary[date];
  if (events === undefined) {
    return;
  }
  events.sort(compareEventsByStartDate);
  dateElement.classList.add('day-with-events');
  dateElement.addEventListener('click', function () {
    displayEvents(date);

    const currentlySelectedDay = document.getElementById('selected-day');
    if (currentlySelectedDay !== null) {
      currentlySelectedDay.id = '';
    }
    dateElement.id = 'selected-day';
  });
}

/**
 * Adds the days of the current month into the calendar table.
 * @param {Element} calendarTable The table that is part of the calendar.
 * @param {Date} date The date for whose month we will add the days.
 */
function addMonthDays(calendarTable, date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const numberOfDaysInMonth = getNumberOfDaysInMonth(date);
  const firstWeekDay = new Date(year, month, 1).getDay();

  // It will be used to display the days in groups of the same size as the
  // WEEK_DAYS array.
  let currentRowElement = createElement('tr', '', '');

  // Add padding for the days of the week that were part of the previous month.
  for (let day = 0; day < firstWeekDay; day++) {
    currentRowElement.appendChild(createElement('td', '', ''));
  }

  for (let day = 1; day <= numberOfDaysInMonth; day++) {
    // Add the row and begin a new one if the current one is already full.
    if ((day + firstWeekDay - 1) % WEEK_DAYS.length === 0) {
      calendarTable.appendChild(currentRowElement);
      currentRowElement = createElement('tr', '', '');
    }
    const dayElement = createElement('td', 'calendar-day', day);
    addDayEvents(new Date(year, month, day), dayElement);
    currentRowElement.appendChild(dayElement);
  }

  // Add the final row that was created.
  calendarTable.appendChild(currentRowElement);
}

/**
 * Adds a header containing details about the current month and two buttons
 * to switch between the previous and the next months.
 * @param {Element} calendarContainer The container that will incorporate the
 * calendar.
 * @param {Date} date The date for whose month we will display the days and
 * the events associated.
 */
function addMonthHeader(calendarContainer, date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const calendarHeader = createElement('div', 'calendar-header', '');

  addNewMonthButton('arrow_back_ios', calendarHeader, date, getPreviousMonthDate);
  calendarHeader.appendChild(createElement('p', 'calendar-details',
      MONTHS[month] + ' ' + year));
  addNewMonthButton('arrow_forward_ios', calendarHeader, date, getNextMonthDate);

  calendarContainer.appendChild(calendarHeader);
}

/**
 * Adds a new group to the database.
 */
function addNewGroup() {
  const params = new URLSearchParams();
  const university = document.getElementById('new-group-university').value;
  const degree = document.getElementById('new-group-degree').value;
  const year = document.getElementById('new-group-year').value;

  params.append('university', university);
  params.append('degree', degree);
  params.append('year', year);

  fetch('/groups', {method: 'POST', body: params});
}

/**
 * Adds a button that will load the new month determined by the
 * function received (e.g. previous / next month).
 * @param {String} buttonClass The class of the button used to identify
 * the icon.
 * @param {Element} calendarHeader The header of the calendar.
 * @param {Date} date The date used to compute the new month.
 * @param {Function} functionToCreateNewMonth The function used to create
 * the new month.
 */
function addNewMonthButton(buttonClass, calendarHeader, date,
    functionToCreateNewMonth) {
  const newMonthButton = createElement('i', 'material-icons', buttonClass);
  newMonthButton.addEventListener('click', function() {
    loadNewMonth(date, functionToCreateNewMonth)
  });
  calendarHeader.appendChild(newMonthButton);
}

/**
 * Adds the week days header in the calendar's table.
 * @param {Element} calendarTable The table that is part of the calendar.
 */
function addWeekDays(calendarTable) {
  const weekDaysRow = createElement('tr', 'week-days-row', '');
  for (let i = 0; i < WEEK_DAYS.length; i++) {
    weekDaysRow.appendChild(createElement('th', 'week-day', WEEK_DAYS[i]));
  }
  calendarTable.appendChild(weekDaysRow);
}

/**
 * Changes the display property of the container with the one received.
 * @param {String} containerID The ID of the container.
 * @param {String} displayType The new display value.
 */
function changeContainerDisplay(containerID, displayType) {
  const container = document.getElementById(containerID);
  container.style.display = displayType;
}

/**
 * Closes the new group form and displays the "Create new group" button.
 */
function closeGroupForm() {
  changeContainerDisplay('group-form', 'none');
  changeContainerDisplay('open-group-form', 'initial');
}

/**
 * Compares two events based on their start date.
 * @param {Object} firstEvent First event to be compared.
 * @param {Object} secondEvent Second event to be compared.
 * @return {Integer} A positive number if the two events should be switched.
 */
function compareEventsByStartDate(firstEvent, secondEvent) {
  const firstEventStartDate = firstEvent.start;
  const secondEventStartDate = secondEvent.start;

  return firstEventStartDate < secondEventStartDate;
}

/**
 * Creates a Date JavaScript object based on the Java object received.
 * @param {Object} dateObject The Java object received.
 * @return {Date} The JavaScript object associated.
 */
function createDate(dateObject) {
  let date = new Date();

  date.setFullYear(dateObject.year);
  date.setMonth(dateObject.month);
  date.setDate(dateObject.day);
  date.setHours(dateObject.hour);
  date.setMinutes(dateObject.minute);

  return date;
}

/**
 * Creates a custom message based on the details provided (degree, year of
 * study).
 * @param {String} degree The degree that will be included in the message.
 * @param {String} year The year that will be included in the message.
 * @return {String} The message created.
 */
function createDetailsMessage(degree, year) {
  let message = year;

  switch (year) {
    case 1:
      message += 'st';
      break;
    case 2:
      message += 'nd';
      break;
    case 3:
      message += 'rd';
      break;
    default:
      message += 'th';
  }

  message += ' year ' + degree + ' students';
  return message;
}

/**
 * Creates the element associated with a given event.
 * @param {object} group The event for which we will create a new element.
 * @return {Element} The element created.
 */
function createEventElement(event) {
  const eventElement = createElement('div', 'event', '');

  eventElement.appendChild(createElement('div', 'event-title', event.title));
  eventElement.appendChild(createElement('hr', '', ''));
  eventElement.appendChild(createElement('div', 'event-time',
      event.start + ' - ' + event.end));

  return eventElement;
}

/**
 * Creates the element associated with a given group.
 * @param {object} group The group for which we will create a new element.
 * @return {Element} The element created.
 */
function createGroupElement(group) {
  const groupElement = createElement('div', 'group', '');

  groupElement.appendChild(createElement('div', 'group-university',
      group.university));
  groupElement.appendChild(createElement('hr', '', ''));
  groupElement.appendChild(createElement('div', 'group-details',
      createDetailsMessage(group.degree, group.year)));

  return groupElement;
}

/**
 * Creates the calendar associated with the date given.
 * @param {Date} date The date for which we want to display the calendar.
 */
function createMonthCalendar(date) {
  const calendarContainer = document.getElementById('calendar');
  const calendarTable = createElement('table', 'calendar-table', '');

  addMonthHeader(calendarContainer, date);
  addWeekDays(calendarTable);
  addMonthDays(calendarTable, date);

  calendarContainer.appendChild(calendarTable);
}

/**
 * Displays the events happening on the given date in the 'events'
 * container.
 * @param {Date} date The date for which the events will be displayed.
 */
function displayEvents(date) {
  const events = eventsDictionary[date];
  if (events === undefined) {
    return;
  }

  const eventsContainer = document.getElementById('events');
  eventsContainer.innerHTML = '';

  for(let i = 0; i < events.length; i++) {
    eventsContainer.appendChild(createEventElement(events[i]));
  }
}

/**
 * Gets the next month date of the date received.
 * @param {Date} date The date for which the new date will be created.
 * @return {Date} The date of the next month.
 */
function getNextMonthDate(date) {
  const currentMonth = date.getMonth();
  const currentYear = date.getFullYear();

  if (currentMonth === 11) {
    return new Date(currentYear + 1, 0);
  }

  return new Date(currentYear, currentMonth + 1);
}

/**
 * Gets the number of days present in the month of the date received.
 * @param {Date} date The date for which we will compute the number of days.
 * @return {Integer} The number of days.
 */
function getNumberOfDaysInMonth(date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const nextMonthDate = getNextMonthDate(new Date(year, month));
  const nextMonthYear = nextMonthDate.getFullYear();
  const nextMonth = nextMonthDate.getMonth();

  return new Date(nextMonthYear, nextMonth, 0).getDate();
}

/**
 * Gets the previous month date of the date received.
 * @param {Date} date The date for which the new date will be created.
 * @return {Date} The date of the previous month.
 */
function getPreviousMonthDate(date) {
  const currentMonth = date.getMonth();
  const currentYear = date.getFullYear();

  if (currentMonth === 0) {
    return new Date(currentYear - 1, 11);
  }

  return new Date(currentYear, currentMonth - 1);
}

/**
 * Loads the calendar associated with the current date and displays the events
 * of the current day.
 */
function loadCalendar() {
  const currentDate = new Date();
  const today = new Date(currentDate.getFullYear(), currentDate.getMonth(),
      currentDate.getDate());

  createMonthCalendar(currentDate);
  displayEvents(today);
}

/**
 * Fetches events from the server and stores them in the dictionary.
 */
async function loadEvents() {
  const response = await fetch('/events');
  const events = await response.json();

  events.forEach((event) => {
    const eventStartDate = createDate(event.start);
    const eventEndDate = createDate(event.end);
    const eventStartDay = new Date(eventStartDate.getFullYear(),
        eventStartDate.getMonth(), eventStartDate.getDate());
    const eventObject = new Event(event.name, eventStartDate,
        eventEndDate);

    if (eventStartDay in eventsDictionary) {
      eventsDictionary[eventStartDay].push(eventObject);
    } else {
      eventsDictionary[eventStartDay] = [eventObject];
    }
  });
}

/**
 * Fetches groups from the server and adds them to the groups section.
 */
function loadGroups() {
  const groupsList = document.getElementById('groups');

  fetch('/groups')
      .then((response) => response.json())
      .then((groups) => {
        groups.forEach((group) => {
          const groupObject = new Group(group.university, group.degree,
              group.year);
          groupsList.appendChild(createGroupElement(groupObject));
        });
      });
}

/**
 * Loads the new month determined by the function provided.
 * @param {Date} date The date used to compute the new month.
 * @param {Function} functionToCreateNewMonth The function used to create
 * the new month.
 */
function loadNewMonth(date, functionToCreateNewMonth) {
  const newMonthDate = functionToCreateNewMonth(date);
  document.getElementById('calendar').innerHTML = '';
  createMonthCalendar(newMonthDate);
}

/**
 * Loads the calendar and events associated with the user profile.
 */
async function loadProfile() {
  loadGroups();
  await loadEvents();
  loadCalendar();
}

/**
 * Opens the new group form and hides the "Create new group" button.
 */
function openGroupForm() {
  changeContainerDisplay('group-form', 'block');
  changeContainerDisplay('open-group-form', 'none');
}
<commit_msg>Adapt to the private fields of the Group class<commit_after>/* eslint-disable no-unused-vars */

// 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
//
//     https://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.

const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'];
const WEEK_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
    'Friday', 'Saturday'];

let eventsDictionary = {};

/** Class used to define the basic characteristics of a group. */
class Group {
  /**
   * Creates a new group with the given parameters.
   * @param {String} university The name of the university.
   * @param {String} degree The name of the degree.
   * @param {int} year The year of study.
   */
  constructor(university, degree, year) {
    /** @private @const {String} */
    this.university_ = university;
    /** @private @const {String} */
    this.degree_ = degree;
    /** @private @const {int} */
    this.year_ = year;
  }
}

/** Class used to define the basic characteristics of an event. */
class Event {
  /**
   * Creates a new event with the given parameters.
   * @param {String} title The title of the event.
   * @param {String} start The start date of the event.
   * @param {int} end The end date of the event.
   */
  constructor(title, start, end) {
    this.title = title;
    this.start = start;
    this.end = end;
  }
}

/**
 * Adds the events associated with that date to the element.
 * @param {Date} date The date for which the events will be added.
 * @param {Element} dateElement The element in which the events will be added.
 */
function addDayEvents(date, dateElement) {
  const events = eventsDictionary[date];
  if (events === undefined) {
    return;
  }
  events.sort(compareEventsByStartDate);
  dateElement.classList.add('day-with-events');
  dateElement.addEventListener('click', function () {
    displayEvents(date);

    const currentlySelectedDay = document.getElementById('selected-day');
    if (currentlySelectedDay !== null) {
      currentlySelectedDay.id = '';
    }
    dateElement.id = 'selected-day';
  });
}

/**
 * Adds the days of the current month into the calendar table.
 * @param {Element} calendarTable The table that is part of the calendar.
 * @param {Date} date The date for whose month we will add the days.
 */
function addMonthDays(calendarTable, date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const numberOfDaysInMonth = getNumberOfDaysInMonth(date);
  const firstWeekDay = new Date(year, month, 1).getDay();

  // It will be used to display the days in groups of the same size as the
  // WEEK_DAYS array.
  let currentRowElement = createElement('tr', '', '');

  // Add padding for the days of the week that were part of the previous month.
  for (let day = 0; day < firstWeekDay; day++) {
    currentRowElement.appendChild(createElement('td', '', ''));
  }

  for (let day = 1; day <= numberOfDaysInMonth; day++) {
    // Add the row and begin a new one if the current one is already full.
    if ((day + firstWeekDay - 1) % WEEK_DAYS.length === 0) {
      calendarTable.appendChild(currentRowElement);
      currentRowElement = createElement('tr', '', '');
    }
    const dayElement = createElement('td', 'calendar-day', day);
    addDayEvents(new Date(year, month, day), dayElement);
    currentRowElement.appendChild(dayElement);
  }

  // Add the final row that was created.
  calendarTable.appendChild(currentRowElement);
}

/**
 * Adds a header containing details about the current month and two buttons
 * to switch between the previous and the next months.
 * @param {Element} calendarContainer The container that will incorporate the
 * calendar.
 * @param {Date} date The date for whose month we will display the days and
 * the events associated.
 */
function addMonthHeader(calendarContainer, date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const calendarHeader = createElement('div', 'calendar-header', '');

  addNewMonthButton('arrow_back_ios', calendarHeader, date, getPreviousMonthDate);
  calendarHeader.appendChild(createElement('p', 'calendar-details',
      MONTHS[month] + ' ' + year));
  addNewMonthButton('arrow_forward_ios', calendarHeader, date, getNextMonthDate);

  calendarContainer.appendChild(calendarHeader);
}

/**
 * Adds a new group to the database.
 */
function addNewGroup() {
  const params = new URLSearchParams();
  const university = document.getElementById('new-group-university').value;
  const degree = document.getElementById('new-group-degree').value;
  const year = document.getElementById('new-group-year').value;

  params.append('university', university);
  params.append('degree', degree);
  params.append('year', year);

  fetch('/groups', {method: 'POST', body: params});
}

/**
 * Adds a button that will load the new month determined by the
 * function received (e.g. previous / next month).
 * @param {String} buttonClass The class of the button used to identify
 * the icon.
 * @param {Element} calendarHeader The header of the calendar.
 * @param {Date} date The date used to compute the new month.
 * @param {Function} functionToCreateNewMonth The function used to create
 * the new month.
 */
function addNewMonthButton(buttonClass, calendarHeader, date,
    functionToCreateNewMonth) {
  const newMonthButton = createElement('i', 'material-icons', buttonClass);
  newMonthButton.addEventListener('click', function() {
    loadNewMonth(date, functionToCreateNewMonth)
  });
  calendarHeader.appendChild(newMonthButton);
}

/**
 * Adds the week days header in the calendar's table.
 * @param {Element} calendarTable The table that is part of the calendar.
 */
function addWeekDays(calendarTable) {
  const weekDaysRow = createElement('tr', 'week-days-row', '');
  for (let i = 0; i < WEEK_DAYS.length; i++) {
    weekDaysRow.appendChild(createElement('th', 'week-day', WEEK_DAYS[i]));
  }
  calendarTable.appendChild(weekDaysRow);
}

/**
 * Changes the display property of the container with the one received.
 * @param {String} containerID The ID of the container.
 * @param {String} displayType The new display value.
 */
function changeContainerDisplay(containerID, displayType) {
  const container = document.getElementById(containerID);
  container.style.display = displayType;
}

/**
 * Closes the new group form and displays the "Create new group" button.
 */
function closeGroupForm() {
  changeContainerDisplay('group-form', 'none');
  changeContainerDisplay('open-group-form', 'initial');
}

/**
 * Compares two events based on their start date.
 * @param {Object} firstEvent First event to be compared.
 * @param {Object} secondEvent Second event to be compared.
 * @return {Integer} A positive number if the two events should be switched.
 */
function compareEventsByStartDate(firstEvent, secondEvent) {
  const firstEventStartDate = firstEvent.start;
  const secondEventStartDate = secondEvent.start;

  return firstEventStartDate < secondEventStartDate;
}

/**
 * Creates a Date JavaScript object based on the Java object received.
 * @param {Object} dateObject The Java object received.
 * @return {Date} The JavaScript object associated.
 */
function createDate(dateObject) {
  let date = new Date();

  date.setFullYear(dateObject.year);
  date.setMonth(dateObject.month);
  date.setDate(dateObject.day);
  date.setHours(dateObject.hour);
  date.setMinutes(dateObject.minute);

  return date;
}

/**
 * Creates a custom message based on the details provided (degree, year of
 * study).
 * @param {String} degree The degree that will be included in the message.
 * @param {String} year The year that will be included in the message.
 * @return {String} The message created.
 */
function createDetailsMessage(degree, year) {
  let message = year;

  switch (year) {
    case 1:
      message += 'st';
      break;
    case 2:
      message += 'nd';
      break;
    case 3:
      message += 'rd';
      break;
    default:
      message += 'th';
  }

  message += ' year ' + degree + ' students';
  return message;
}

/**
 * Creates the element associated with a given event.
 * @param {object} group The event for which we will create a new element.
 * @return {Element} The element created.
 */
function createEventElement(event) {
  const eventElement = createElement('div', 'event', '');

  eventElement.appendChild(createElement('div', 'event-title', event.title));
  eventElement.appendChild(createElement('hr', '', ''));
  eventElement.appendChild(createElement('div', 'event-time',
      event.start + ' - ' + event.end));

  return eventElement;
}

/**
 * Creates the element associated with a given group.
 * @param {object} group The group for which we will create a new element.
 * @return {Element} The element created.
 */
function createGroupElement(group) {
  const groupElement = createElement('div', 'group', '');

  groupElement.appendChild(createElement('div', 'group-university',
      group.university_));
  groupElement.appendChild(createElement('hr', '', ''));
  groupElement.appendChild(createElement('div', 'group-details',
      createDetailsMessage(group.degree_, group.year_)));

  return groupElement;
}

/**
 * Creates the calendar associated with the date given.
 * @param {Date} date The date for which we want to display the calendar.
 */
function createMonthCalendar(date) {
  const calendarContainer = document.getElementById('calendar');
  const calendarTable = createElement('table', 'calendar-table', '');

  addMonthHeader(calendarContainer, date);
  addWeekDays(calendarTable);
  addMonthDays(calendarTable, date);

  calendarContainer.appendChild(calendarTable);
}

/**
 * Displays the events happening on the given date in the 'events'
 * container.
 * @param {Date} date The date for which the events will be displayed.
 */
function displayEvents(date) {
  const events = eventsDictionary[date];
  if (events === undefined) {
    return;
  }

  const eventsContainer = document.getElementById('events');
  eventsContainer.innerHTML = '';

  for(let i = 0; i < events.length; i++) {
    eventsContainer.appendChild(createEventElement(events[i]));
  }
}

/**
 * Gets the next month date of the date received.
 * @param {Date} date The date for which the new date will be created.
 * @return {Date} The date of the next month.
 */
function getNextMonthDate(date) {
  const currentMonth = date.getMonth();
  const currentYear = date.getFullYear();

  if (currentMonth === 11) {
    return new Date(currentYear + 1, 0);
  }

  return new Date(currentYear, currentMonth + 1);
}

/**
 * Gets the number of days present in the month of the date received.
 * @param {Date} date The date for which we will compute the number of days.
 * @return {Integer} The number of days.
 */
function getNumberOfDaysInMonth(date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const nextMonthDate = getNextMonthDate(new Date(year, month));
  const nextMonthYear = nextMonthDate.getFullYear();
  const nextMonth = nextMonthDate.getMonth();

  return new Date(nextMonthYear, nextMonth, 0).getDate();
}

/**
 * Gets the previous month date of the date received.
 * @param {Date} date The date for which the new date will be created.
 * @return {Date} The date of the previous month.
 */
function getPreviousMonthDate(date) {
  const currentMonth = date.getMonth();
  const currentYear = date.getFullYear();

  if (currentMonth === 0) {
    return new Date(currentYear - 1, 11);
  }

  return new Date(currentYear, currentMonth - 1);
}

/**
 * Loads the calendar associated with the current date and displays the events
 * of the current day.
 */
function loadCalendar() {
  const currentDate = new Date();
  const today = new Date(currentDate.getFullYear(), currentDate.getMonth(),
      currentDate.getDate());

  createMonthCalendar(currentDate);
  displayEvents(today);
}

/**
 * Fetches events from the server and stores them in the dictionary.
 */
async function loadEvents() {
  const response = await fetch('/events');
  const events = await response.json();

  events.forEach((event) => {
    const eventStartDate = createDate(event.start);
    const eventEndDate = createDate(event.end);
    const eventStartDay = new Date(eventStartDate.getFullYear(),
        eventStartDate.getMonth(), eventStartDate.getDate());
    const eventObject = new Event(event.name, eventStartDate,
        eventEndDate);

    if (eventStartDay in eventsDictionary) {
      eventsDictionary[eventStartDay].push(eventObject);
    } else {
      eventsDictionary[eventStartDay] = [eventObject];
    }
  });
}

/**
 * Fetches groups from the server and adds them to the groups section.
 */
function loadGroups() {
  const groupsList = document.getElementById('groups');

  fetch('/groups')
      .then((response) => response.json())
      .then((groups) => {
        groups.forEach((group) => {
          const groupObject = new Group(group.university, group.degree,
              group.year);
          groupsList.appendChild(createGroupElement(groupObject));
        });
      });
}

/**
 * Loads the new month determined by the function provided.
 * @param {Date} date The date used to compute the new month.
 * @param {Function} functionToCreateNewMonth The function used to create
 * the new month.
 */
function loadNewMonth(date, functionToCreateNewMonth) {
  const newMonthDate = functionToCreateNewMonth(date);
  document.getElementById('calendar').innerHTML = '';
  createMonthCalendar(newMonthDate);
}

/**
 * Loads the calendar and events associated with the user profile.
 */
async function loadProfile() {
  loadGroups();
  await loadEvents();
  loadCalendar();
}

/**
 * Opens the new group form and hides the "Create new group" button.
 */
function openGroupForm() {
  changeContainerDisplay('group-form', 'block');
  changeContainerDisplay('open-group-form', 'none');
}
<|endoftext|>


<commit_before>import CALL_API from './CALL_API';
import { normalize, Schema } from 'normalizr';
import isPlainObject from 'lodash.isplainobject';

/**
 * Checks an action against the RSAA definition, returning a (possibly empty)
 * array of validation errors.
 *
 * @function validateRSAA
 * @access public
 * @param {Object} action - The action to check against the RSAA definition.
 * @returns {Array}
 */
function validateRSAA(action) {
  var validationErrors = [];
  const validRootKeys = [
    [CALL_API],
    'payload',
    'meta'
  ];
  const validCallAPIKeys = [
    'endpoint',
    'method',
    'types',
    'body',
    'headers',
    'transform',
    'bailout'
  ];
  const validMethods = [
    'GET',
    'HEAD',
    'POST',
    'PUT',
    'PATCH',
    'DELETE',
    'OPTIONS'
  ]

  if (!isPlainObject(action)) {
    validationErrors.push('RSAA must be a plain JavaScript object');
    return validationErrors;
  }

  const callAPI = action[CALL_API];
  if (typeof callAPI === 'undefined') {
    validationErrors.push('Missing [CALL_API] key');
    return validationErrors;
  }
  for (let key in action) {
    if (!validRootKeys.includes(key)) {
      validationErrors.push(`Invalid root key: ${key}`);
    }
    if (validationErrors.length) {
      return validationErrors;
    }
  }
  if (!isPlainObject(callAPI)) {
    validationErrors.push('[CALL_API] property must be a plain JavaScript object');
    return validationErrors;
  }
  for (let key in callAPI) {
    if (!validCallAPIKeys.includes(key)) {
      validationErrors.push(`Invalid [CALL_API] key: ${key}`);
    }
    if (validationErrors.length) {
      return validationErrors;
    }
  }

  const { endpoint, method, body, headers, transform, types, bailout } = callAPI;
  if (typeof endpoint !== 'string' && typeof endpoint !== 'function') {
    validationErrors.push('[CALL_API].endpoint property must be a string or a function');
  }
  if (typeof method !== 'string') {
    validationErrors.push('[CALL_API].method property must be a string');
  } else if (!validMethods.includes(method.toUpperCase())) {
    validationErrors.push(`Invalid [CALL_API].method: ${method.toUpperCase()}`);
  }
  if (!Array.isArray(types) || types.length !== 3) {
    validationErrors.push('[CALL_API].types property must be an array of length 3');
  }
  if (typeof headers !== 'undefined' && !isPlainObject(headers)) {
    validationErrors.push('[CALL_API].headers property must be undefined, or a plain JavaScript object');
  }
  if (typeof transform !== 'undefined' && typeof transform !== 'function') {
    validationErrors.push('[CALL_API].transform property must be undefined, or a function');
  }
  if (typeof bailout !== 'undefined' && typeof bailout !== 'boolean' && typeof bailout !== 'function') {
    validationErrors.push('[CALL_API].bailout property must be undefined, a boolean, or a function');
  };
  return validationErrors;
}

export default validateRSAA;
<commit_msg>Closes #11<commit_after>import CALL_API from './CALL_API';
import isPlainObject from 'lodash.isplainobject';

/**
 * Checks an action against the RSAA definition, returning a (possibly empty)
 * array of validation errors.
 *
 * @function validateRSAA
 * @access public
 * @param {Object} action - The action to check against the RSAA definition.
 * @returns {Array}
 */
function validateRSAA(action) {
  var validationErrors = [];
  const validRootKeys = [
    [CALL_API],
    'payload',
    'meta'
  ];
  const validCallAPIKeys = [
    'endpoint',
    'method',
    'types',
    'body',
    'headers',
    'transform',
    'bailout'
  ];
  const validMethods = [
    'GET',
    'HEAD',
    'POST',
    'PUT',
    'PATCH',
    'DELETE',
    'OPTIONS'
  ]

  if (!isPlainObject(action)) {
    validationErrors.push('RSAA must be a plain JavaScript object');
    return validationErrors;
  }

  const callAPI = action[CALL_API];
  if (typeof callAPI === 'undefined') {
    validationErrors.push('Missing [CALL_API] key');
    return validationErrors;
  }
  for (let key in action) {
    if (!validRootKeys.includes(key)) {
      validationErrors.push(`Invalid root key: ${key}`);
    }
    if (validationErrors.length) {
      return validationErrors;
    }
  }
  if (!isPlainObject(callAPI)) {
    validationErrors.push('[CALL_API] property must be a plain JavaScript object');
    return validationErrors;
  }
  for (let key in callAPI) {
    if (!validCallAPIKeys.includes(key)) {
      validationErrors.push(`Invalid [CALL_API] key: ${key}`);
    }
    if (validationErrors.length) {
      return validationErrors;
    }
  }

  const { endpoint, method, body, headers, transform, types, bailout } = callAPI;
  if (typeof endpoint !== 'string' && typeof endpoint !== 'function') {
    validationErrors.push('[CALL_API].endpoint property must be a string or a function');
  }
  if (typeof method !== 'string') {
    validationErrors.push('[CALL_API].method property must be a string');
  } else if (!validMethods.includes(method.toUpperCase())) {
    validationErrors.push(`Invalid [CALL_API].method: ${method.toUpperCase()}`);
  }
  if (!Array.isArray(types) || types.length !== 3) {
    validationErrors.push('[CALL_API].types property must be an array of length 3');
  }
  if (typeof headers !== 'undefined' && !isPlainObject(headers)) {
    validationErrors.push('[CALL_API].headers property must be undefined, or a plain JavaScript object');
  }
  if (typeof transform !== 'undefined' && typeof transform !== 'function') {
    validationErrors.push('[CALL_API].transform property must be undefined, or a function');
  }
  if (typeof bailout !== 'undefined' && typeof bailout !== 'boolean' && typeof bailout !== 'function') {
    validationErrors.push('[CALL_API].bailout property must be undefined, a boolean, or a function');
  };
  return validationErrors;
}

export default validateRSAA;
<|endoftext|>


<commit_before>var NewHostWizard = function($scope, $uibModalInstance, $http, $timeout, appsession, uuid4) {
    $scope.pubkeyUptoDate = false;
    $scope.pubkeyUpdating = false;
    $scope.inprg = false;

    $scope.errors = [];

    $scope.host = {
        "@type" : 'host',
        id : uuid4.generate(),
        address : '',
        publicKey : '',
        dedicated : true
    };
    $scope.password = {
    	password: ''
    };

	$scope.usepubkey = true;

	$scope.controllerKey = '';

    $scope.updateTimeout = null;
    $scope.clearPublicKey = function (event) {
		if(event.keyCode == 13) {
			$scope.addHost();
		}
        $scope.host.publicKey = '';
    }
    $scope.errorHandler = function(error, responseCode) {
		$scope.errors.push(error);
    };
    $scope.updatePubkey = function () {
    	if($scope.host.address == '') {
    		return;
    	}
        $scope.host.publicKey = '';
        $scope.pubkeyUpdating = true;
        if($scope.updateTimeout != null) {
            $timeout.cancel($scope.updateTimeout);
            $scope.pubkeyUptoDate = false;
        }
		$scope.errors = [];
        appsession.get('s/r/host/helpers/pubkey?address='+$scope.host.address)
            .success(function(pubkey) {
                $scope.pubkeyUptoDate = true;
                $scope.pubkey = pubkey;
                $scope.host.publicKey = pubkey.fingerprint;
		        $scope.pubkeyUpdating = false;
            })
            .error(function(error) {
            	$scope.errorHandler(error);
            	$scope.pubkeyUpdating = false;
            });
    };
    $scope.checkHostAddress = function() {
		appsession.get('s/r/host/byaddress/'+$scope.host.address)
			.success(function(data) {
				if(data.length > 0) {
					$scope.errorHandler({'code':'UNIQ',message : 'Host with this address already registered'});
				}
			});
    };
    $scope.close = function() {
        $uibModalInstance.dismiss('cancel');
    };
	$scope.onKeyPress = function(event) {
		if(event.keyCode == 13) {
			$scope.addHost();
		}
	};
	$scope.toggleDedicated = function() {
		$scope.host.dedicated = !$scope.host.dedicated;
	};
    $scope.addHost = function () {
    	var onHostAdded = function() {
			$uibModalInstance.close();
		};
		var hostAddError = function(error) {
			$scope.errorHandler(error);
			$scope.inprg = false;
		};

		$scope.inprg = true;
    	if($scope.usepubkey) {
    	    appsession.put('s/r/host/join-pubkey',$scope.host)
				.success(onHostAdded)
				.error(hostAddError);
    	} else {
			appsession.put('s/r/host/join',
				{
					host : $scope.host,
					password : $scope.password.password
				})
				.success(onHostAdded)
				.error(hostAddError);
    	}
    };

    appsession.get('s/r/host/helpers/controller-pubkey').success(function(result) {
    	$scope.controllerKey = result;
    });
};<commit_msg>fix: do not check host address if it is empty<commit_after>var NewHostWizard = function($scope, $uibModalInstance, $http, $timeout, appsession, uuid4) {
    $scope.pubkeyUptoDate = false;
    $scope.pubkeyUpdating = false;
    $scope.inprg = false;

    $scope.errors = [];

    $scope.host = {
        "@type" : 'host',
        id : uuid4.generate(),
        address : '',
        publicKey : '',
        dedicated : true
    };
    $scope.password = {
    	password: ''
    };

	$scope.usepubkey = true;

	$scope.controllerKey = '';

    $scope.updateTimeout = null;
    $scope.clearPublicKey = function (event) {
		if(event.keyCode == 13) {
			$scope.addHost();
		}
        $scope.host.publicKey = '';
    }
    $scope.errorHandler = function(error, responseCode) {
		$scope.errors.push(error);
    };
    $scope.updatePubkey = function () {
    	if($scope.host.address == '') {
    		return;
    	}
        $scope.host.publicKey = '';
        $scope.pubkeyUpdating = true;
        if($scope.updateTimeout != null) {
            $timeout.cancel($scope.updateTimeout);
            $scope.pubkeyUptoDate = false;
        }
		$scope.errors = [];
        appsession.get('s/r/host/helpers/pubkey?address='+$scope.host.address)
            .success(function(pubkey) {
                $scope.pubkeyUptoDate = true;
                $scope.pubkey = pubkey;
                $scope.host.publicKey = pubkey.fingerprint;
		        $scope.pubkeyUpdating = false;
            })
            .error(function(error) {
            	$scope.errorHandler(error);
            	$scope.pubkeyUpdating = false;
            });
    };
    $scope.checkHostAddress = function() {
    	if($scope.host.address.length == 0) {
    		return;
    	}
		appsession.get('s/r/host/byaddress/'+$scope.host.address)
			.success(function(data) {
				if(data.length > 0) {
					$scope.errorHandler({'code':'UNIQ',message : 'Host with this address already registered'});
				}
			});
    };
    $scope.close = function() {
        $uibModalInstance.dismiss('cancel');
    };
	$scope.onKeyPress = function(event) {
		if(event.keyCode == 13) {
			$scope.addHost();
		}
	};
	$scope.toggleDedicated = function() {
		$scope.host.dedicated = !$scope.host.dedicated;
	};
    $scope.addHost = function () {
    	var onHostAdded = function() {
			$uibModalInstance.close();
		};
		var hostAddError = function(error) {
			$scope.errorHandler(error);
			$scope.inprg = false;
		};

		$scope.inprg = true;
    	if($scope.usepubkey) {
    	    appsession.put('s/r/host/join-pubkey',$scope.host)
				.success(onHostAdded)
				.error(hostAddError);
    	} else {
			appsession.put('s/r/host/join',
				{
					host : $scope.host,
					password : $scope.password.password
				})
				.success(onHostAdded)
				.error(hostAddError);
    	}
    };

    appsession.get('s/r/host/helpers/controller-pubkey').success(function(result) {
    	$scope.controllerKey = result;
    });
};<|endoftext|>


<commit_before>(function(angular, $, _, TL) {
  'use strict';
  var deps = _.union(window.dependencies || [], [
    'ngAria',
    'ngAnimate',
    'ui.bootstrap',
    'irida.session',
    'irida.notifications',
    'irida.cart'
  ]);

  angular.module('irida', deps)
    .config(function($httpProvider) {
      $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

      // Make sure that all ajax form data is sent in the correct format.
      $httpProvider.defaults.transformRequest = function(data) {
        if (data === undefined) {
          return data;
        }
        return $.param(data);
      };
    })
    .run(function(uibPaginationConfig) {
      uibPaginationConfig.firstText = TL.lang.page.first;
      uibPaginationConfig.previousText = TL.lang.page.prev;
      uibPaginationConfig.nextText = TL.lang.page.next;
      uibPaginationConfig.lastText = TL.lang.page.last;
      uibPaginationConfig.boundaryLinks = true;
      uibPaginationConfig.directionLinks = true;
      uibPaginationConfig.maxSize = 8;
      uibPaginationConfig.rotate = false;
    });
})(window.angular, window.$, window._, window.TL);<commit_msg>Not using uib-paginate anymore!<commit_after>(function(angular, $, _, TL) {
  'use strict';
  var deps = _.union(window.dependencies || [], [
    'ngAria',
    'ngAnimate',
    'ui.bootstrap',
    'irida.session',
    'irida.notifications',
    'irida.cart'
  ]);

  angular.module('irida', deps)
    .config(function($httpProvider) {
      $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

      // Make sure that all ajax form data is sent in the correct format.
      $httpProvider.defaults.transformRequest = function(data) {
        if (data === undefined) {
          return data;
        }
        return $.param(data);
      };
    });
})(window.angular, window.$, window._, window.TL);<|endoftext|>


<commit_before><commit_msg>schema defined<commit_after>'use strict'

const Sequelize = require('sequelize')
const db = require('APP/db')

const Order = db.define({
    products: {
        type: Sequelize.ARRAY(Sequelize.INTEGER),
        allowNull: false,
        isArray: true,
    },
    prices: {
        type: Sequelize.ARRAY(Sequelize.INTEGER),
        allowNull: false,
        isArray: true,
    },
    quantities: {
        type: Sequelize.ARRAY(Sequelize.INTEGER),
        allowNull: false,
        isArray: true,
    },
    status: { // “Created”/ “Processing”/ “Cancelled”/ “Completed”
        type: Sequelize.ENUM,
        values: ['created', 'processing', 'cancelled', 'completed'],
        defaultValue: 'created',
        allowNull: false,
    }
}, {
    validate: {
        // Check that there are the same number of items in prices, quantities, and products so indices match
        // i.e. at index: price[index], quantity[index] and product[index] are for the same order item
        arraysAreSameLength: function() {

        }
    }
});<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Csernik Flaviu Andrei
 *
 * See the file LICENSE.txt for copying permission.
 *
 */

window.requestAnimationFrame =
  window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

var defaultPeek = {
  note : {
    name : "I",
    frequency : 0.00
  },
  cents : 0,
  frequency : 0.00
};

function fontStringPX(size, name) {
  return size + "px " + name;
}
<commit_msg>Use strict mode in view code.<commit_after>/*
 * Copyright (c) 2013 Csernik Flaviu Andrei
 *
 * See the file LICENSE.txt for copying permission.
 *
 */

"use strict";

window.requestAnimationFrame =
  window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

var defaultPeek = {
  note : {
    name : "I",
    frequency : 0.00
  },
  cents : 0,
  frequency : 0.00
};

function fontStringPX(size, name) {
  return size + "px " + name;
}
<|endoftext|>


<commit_before>'use strict';

const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
  printSchema: graphQLPrintSchema,
} = require('graphql');
const titleize = require('underscore.string/titleize');
const camelize = require('underscore.string/camelize');
const merge = require('lodash/merge');
const { plural, singular } = require('pluralize');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../error');

// Validate IDL definition
// Also performs some transformation, e.g. adding default values
// TODO: use JSON schema validation|transformation instead
// TODO: move all validation into this method
const validateIdlDefinition = function (obj) {
  const modelTypes = Object.keys(obj.models).map(key => obj.models[key].type);
  validateModelsDefinition(obj.models, { modelTypes });
  return obj;
};

const validateModelsDefinition = function (obj, { modelTypes }) {
  if (typeof obj !== 'object') { return obj; }

  Object.keys(obj).forEach(attrName => {
    const child = obj[attrName];
    if (typeof child === 'object') {
      // TODO: should detect whether child _could_ have `type` instead (i.e. is a JSON schema), as we want `type` to be optional
      // Adds def.title default value, by using parent property name
      if (child.type && !child.title) {
        child.title = attrName;
      }
      // Definitions of type `object` must have valid `properties`
      if (child.type === 'object') {
        if (!child.properties || typeof child.properties !== 'object' || Object.keys(child.properties).length === 0) {
          throw new EngineError(`The following definition of type 'object' is missing 'properties': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
      }
      // Replace { type: "Model" } by { type: "object", modelName: "Model" }
      const isModel = modelTypes.includes(child.type);
      if (isModel) {
        // Make sure we are not overriding user definitions
        if (child.modelName) {
          throw new EngineError(`The following model cannot have a property named 'modelName': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
        Object.assign(child, {
          type: 'object',
          modelName: child.type,
        });
      }
    }
    // Recurse over children
    validateModelsDefinition(child, { modelTypes });
  }, {});

  return obj;
};

// Transforms an IDL definition into an object easy to parse by GraphQL
const getRootDefinition = function ({ definitions, bulkOptions: { write: allowBulkWrite, delete: allowBulkDelete } }) {
  const models = validateIdlDefinition(definitions).models;

  const safeOperations = operations.filter(operation => operation.safe);
  const unsafeOperations = operations.filter(operation => {
    return !operation.safe
      && !(!allowBulkWrite && operation.isBulkWrite)
      && !(!allowBulkDelete && operation.isBulkDelete);
  });
  const safeProperties = getOperationDefinitions({ models, operations: safeOperations });
  const unsafeProperties = getOperationDefinitions({ models, operations: unsafeOperations });

  const rootDef = {
    query: {
      title: 'Query',
      type: 'object',
      description: 'Fetches information about different entities',
      properties: safeProperties,
    },
    mutation: {
      title: 'Mutation',
      type: 'object',
      description: 'Modifies information about different entities',
      properties: unsafeProperties,
    },
  };

  return rootDef;
};

const getOperationDefinitions = function({ models, operations }) {
  return operations.reduce((memo, operation) => {
    const props = getOperationDefinition({ models, operation });
    // Make a deep copy for each definition object
    // Otherwise, each definition would refer each other, which would create some problems
    const copiedProps = merge({}, props);
    Object.assign(memo, copiedProps);
    return memo;
  }, {});
};

const getOperationDefinition = function ({ models, operation }) {
  return Object.keys(models).reduce((properties, modelName) => {
    const model = models[modelName];
    const name = model.title || modelName;
    const def = Object.assign({}, model, { name });

    // `find*` operations are aliased for convenience
    // E.g. `findPet` and `findPets` -> `pet` and `pets`
    const isFind = operation.prefix === 'find';

    // E.g. `updatePets` operation
    let operationName;
    if (operation.multiple) {
      operationName = isFind ? getPluralName(def) : getPluralOperationName(def, operation.prefix);
      properties[operationName] = { type: 'array', items: def };
    // E.g. `updatePet` operation
    } else {
      operationName = isFind ? getSingularName(def) : getSingularOperationName(def, operation.prefix);
      properties[operationName] = def;
    }

    properties[operationName].operation = operation.prefix;

    return properties;
  }, {});
};

/* eslint-disable no-multi-spaces */
const operations = [
  { name: 'findOne',      prefix: 'find',     safe: true,   multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'findMany',     prefix: 'find',     safe: true,   multiple: true,   isBulkWrite: false, isBulkDelete: false },
  { name: 'createOne',    prefix: 'create',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'createMany',   prefix: 'create',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'replaceOne',   prefix: 'replace',  safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'replaceMany',  prefix: 'replace',  safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'updateOne',    prefix: 'update',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'updateMany',   prefix: 'update',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'upsertOne',    prefix: 'upsert',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'upsertMany',   prefix: 'upsert',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'deleteOne',    prefix: 'delete',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'deleteMany',   prefix: 'delete',   safe: false,  multiple: true,   isBulkWrite: false, isBulkDelete: true  },
];
/* eslint-enable no-multi-spaces */

// Retrieves all root model definitions, so that recursive sub-models can point to them
const getModels = function (rootDef) {
  return Object.keys(rootDef).reduce((memo, name) => {
    const props = rootDef[name].properties;
    const subProps = Object.keys(props).reduce((memo,val) => {
      if (typeof props[val] !== 'object') { return memo; }
      return memo.concat(props[val]);
    }, []);
    return memo.concat(subProps);
  }, []);
};

// Returns GraphQL schema
const getSchema = function ({ definitions, bulkOptions }) {
  const key = `schema/${JSON.stringify({ definitions, bulkOptions })}/top`;
  if (cache.exists(key)) {
    return cache.get(key);
  }
  // Deep copy, so we do not modify input
  definitions = merge({}, definitions);

  const rootDef = getRootDefinition({ definitions, bulkOptions });
  const schemaId = uuidv4();
  const models = getModels(rootDef);

  // Apply `getType` to each top-level operation, i.e. Query and Mutation
  const topLevelSchema = Object.keys(rootDef).reduce((memo, name) => {
    const def = rootDef[name];
    memo[name] = getType(def, { schemaId, models });
    return memo;
  }, {});

  const rootSchema = new GraphQLSchema(topLevelSchema);
  cache.set(key, rootSchema);
  return rootSchema;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // When top-level model enters this function, `def.operation` will be defined.
  // This passed the operation to all sub-schemas, so that resolvers know the current operation
  const operation = def.operation || opts.operation;
  opts = Object.assign({}, opts, { operation });

  // Done so that children can get a cached reference of parent type, while avoiding infinite recursion
  // Only cache schemas that have a model name, because they are the only one that can recurse
  // Namespace by operation, because operations can have slightly different types
  const modelName = def.modelName;
  const key = modelName && `field/${opts.schemaId}/${modelName}/${operation}`;
  if (key && cache.exists(key)) {
    return cache.get(key);
  }

  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  if (key) {
    cache.set(key, field);
  }
  return field;
};

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

  {
    condition: def => def.required,
    value(def, opts) {
      // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
      const modifiedDef = Object.assign({}, def, { required: false });
      const subType = getType(modifiedDef, opts);
      const type = new GraphQLNonNull(subType);
      const description = def.description || (def.items && def.items.description);
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'array' && typeof def.items === 'object',
    value(def, opts) {
      const subDef = def.items;
      const subType = getType(subDef, opts);
      const type = new GraphQLList(subType);
      const description = subDef.description;
      const fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (subDef.modelName) {
        Object.assign(fieldInfo, {
          args: {
            id: {
              type: GraphQLInt,
              description: 'id to look at',
              defaultValue: 10
            },
          },
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'object',
    value(initialDef, opts) {
      // If this definition points to a top-level model, use that model instead
      const topDef = opts.models.find(model => initialDef.modelName
        && model.modelName === initialDef.modelName
        && opts.operation === model.operation);
      const def = topDef || initialDef;

      let name = getTypeName(def, opts.operation);
      // Cannot create two GraphQL object types with the same name
      // Fix it by appending underscores to the name
      // TODO: either prefix with top-level model name, or throw exception, or some other better solution
      let key = `typename/${opts.schemaId}/${name}`;
      while (cache.exists(key)) {
        name += '_';
        key += '_';
      }
      cache.set(key, true);

      const description = def.description;

      const type = new GraphQLObjectType({
        name,
        description,

        // This needs to be function, otherwise we run in an infinite recursion,
        // if the children try to reference a parent type
        fields() {
          return Object.keys(def.properties).reduce((fields, attrName) => {
            const childDef = def.properties[attrName];
            // Recurse over children
            fields[attrName] = getField(childDef, opts);
            return fields;
          }, {});
        },
      });

      let fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (initialDef.modelName) {
        Object.assign(fieldInfo, {
          //description: `Fetches information about a ${getSingularName(def)}`,
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && !op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'integer' && def.format === 'id',
    value(def) {
      const type = GraphQLID;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'integer',
    value(def) {
      const type = GraphQLInt;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'number',
    value(def) {
      const type = GraphQLFloat;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'string',
    value(def) {
      const type = GraphQLString;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'boolean',
    value(def) {
      const type = GraphQLBoolean;
      const description = def.description;
      return { type, description };
    },
  },

];

const executeOperation = async function ({ operation, args = {}, callback }) {
  const response = await callback({ operation, args });
  return response;
};

// Returns def.title
const getDefinitionName = function (def) {
  const name = def.title;
  if (!name) {
    throw new EngineError(`Missing "title" key in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  if (typeof def.title !== 'string') {
    throw new EngineError(`"title" must be a string in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  return name;
};

// Returns def.title, in plural form, lowercased, e.g. `pets`, for findMany queries
const getPluralName = function (def) {
  const name = getDefinitionName(def);
  return plural(name).toLowerCase();
};

// Returns def.title, in plural form, lowercased, e.g. `pet`, for findOne queries
const getSingularName = function (def) {
  const name = getDefinitionName(def);
  return singular(name).toLowerCase();
};

// Returns operation name, camelized, in plural form, e.g. `findPets` or `deletePets`
const getPluralOperationName = function (def, operation) {
  const name = getPluralName(def);
  return camelize(`${operation} ${name}`);
};

// Returns operation name, camelized, in singular form, e.g. `findPet` or `deletePet`
const getSingularOperationName = function (def, operation) {
  const name = getSingularName(def);
  return camelize(`${operation} ${name}`);
};

// Returns def.title, titleized, e.g. `Pet`, for schema type name
const getTypeName = function (def, operation) {
  const name = getDefinitionName(def);
  return titleize(operation) + titleize(singular(name));
};

const printSchema = function (schema) {
  return graphQLPrintSchema(schema);
};

// General key-value cache, specific for each root IDL definition
const cache = {

  _data: {},

  get(key) {
    return this._data[key];
  },

  exists(key) {
    return this.get(key) != null;
  },

  set(key, value) {
    this._data[key] = value;
  },

};

module.exports = {
  graphqlGetSchema: getSchema,
  graphqlPrintSchema: printSchema,
};<commit_msg>Remove unused hack<commit_after>'use strict';

const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
  printSchema: graphQLPrintSchema,
} = require('graphql');
const titleize = require('underscore.string/titleize');
const camelize = require('underscore.string/camelize');
const merge = require('lodash/merge');
const { plural, singular } = require('pluralize');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../error');

// Validate IDL definition
// Also performs some transformation, e.g. adding default values
// TODO: use JSON schema validation|transformation instead
// TODO: move all validation into this method
const validateIdlDefinition = function (obj) {
  const modelTypes = Object.keys(obj.models).map(key => obj.models[key].type);
  validateModelsDefinition(obj.models, { modelTypes });
  return obj;
};

const validateModelsDefinition = function (obj, { modelTypes }) {
  if (typeof obj !== 'object') { return obj; }

  Object.keys(obj).forEach(attrName => {
    const child = obj[attrName];
    if (typeof child === 'object') {
      // TODO: should detect whether child _could_ have `type` instead (i.e. is a JSON schema), as we want `type` to be optional
      // Adds def.title default value, by using parent property name
      if (child.type && !child.title) {
        child.title = attrName;
      }
      // Definitions of type `object` must have valid `properties`
      if (child.type === 'object') {
        if (!child.properties || typeof child.properties !== 'object' || Object.keys(child.properties).length === 0) {
          throw new EngineError(`The following definition of type 'object' is missing 'properties': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
      }
      // Replace { type: "Model" } by { type: "object", modelName: "Model" }
      const isModel = modelTypes.includes(child.type);
      if (isModel) {
        // Make sure we are not overriding user definitions
        if (child.modelName) {
          throw new EngineError(`The following model cannot have a property named 'modelName': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
        Object.assign(child, {
          type: 'object',
          modelName: child.type,
        });
      }
    }
    // Recurse over children
    validateModelsDefinition(child, { modelTypes });
  }, {});

  return obj;
};

// Transforms an IDL definition into an object easy to parse by GraphQL
const getRootDefinition = function ({ definitions, bulkOptions: { write: allowBulkWrite, delete: allowBulkDelete } }) {
  const models = validateIdlDefinition(definitions).models;

  const safeOperations = operations.filter(operation => operation.safe);
  const unsafeOperations = operations.filter(operation => {
    return !operation.safe
      && !(!allowBulkWrite && operation.isBulkWrite)
      && !(!allowBulkDelete && operation.isBulkDelete);
  });
  const safeProperties = getOperationDefinitions({ models, operations: safeOperations });
  const unsafeProperties = getOperationDefinitions({ models, operations: unsafeOperations });

  const rootDef = {
    query: {
      title: 'Query',
      type: 'object',
      description: 'Fetches information about different entities',
      properties: safeProperties,
    },
    mutation: {
      title: 'Mutation',
      type: 'object',
      description: 'Modifies information about different entities',
      properties: unsafeProperties,
    },
  };

  return rootDef;
};

const getOperationDefinitions = function({ models, operations }) {
  return operations.reduce((memo, operation) => {
    const props = getOperationDefinition({ models, operation });
    // Make a deep copy for each definition object
    // Otherwise, each definition would refer each other, which would create some problems
    const copiedProps = merge({}, props);
    Object.assign(memo, copiedProps);
    return memo;
  }, {});
};

const getOperationDefinition = function ({ models, operation }) {
  return Object.keys(models).reduce((properties, modelName) => {
    const model = models[modelName];
    const name = model.title || modelName;
    const def = Object.assign({}, model, { name });

    // `find*` operations are aliased for convenience
    // E.g. `findPet` and `findPets` -> `pet` and `pets`
    const isFind = operation.prefix === 'find';

    // E.g. `updatePets` operation
    let operationName;
    if (operation.multiple) {
      operationName = isFind ? getPluralName(def) : getPluralOperationName(def, operation.prefix);
      properties[operationName] = { type: 'array', items: def };
    // E.g. `updatePet` operation
    } else {
      operationName = isFind ? getSingularName(def) : getSingularOperationName(def, operation.prefix);
      properties[operationName] = def;
    }

    properties[operationName].operation = operation.prefix;

    return properties;
  }, {});
};

/* eslint-disable no-multi-spaces */
const operations = [
  { name: 'findOne',      prefix: 'find',     safe: true,   multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'findMany',     prefix: 'find',     safe: true,   multiple: true,   isBulkWrite: false, isBulkDelete: false },
  { name: 'createOne',    prefix: 'create',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'createMany',   prefix: 'create',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'replaceOne',   prefix: 'replace',  safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'replaceMany',  prefix: 'replace',  safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'updateOne',    prefix: 'update',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'updateMany',   prefix: 'update',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'upsertOne',    prefix: 'upsert',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'upsertMany',   prefix: 'upsert',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'deleteOne',    prefix: 'delete',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'deleteMany',   prefix: 'delete',   safe: false,  multiple: true,   isBulkWrite: false, isBulkDelete: true  },
];
/* eslint-enable no-multi-spaces */

// Retrieves all root model definitions, so that recursive sub-models can point to them
const getModels = function (rootDef) {
  return Object.keys(rootDef).reduce((memo, name) => {
    const props = rootDef[name].properties;
    const subProps = Object.keys(props).reduce((memo,val) => {
      if (typeof props[val] !== 'object') { return memo; }
      return memo.concat(props[val]);
    }, []);
    return memo.concat(subProps);
  }, []);
};

// Returns GraphQL schema
const getSchema = function ({ definitions, bulkOptions }) {
  const key = `schema/${JSON.stringify({ definitions, bulkOptions })}/top`;
  if (cache.exists(key)) {
    return cache.get(key);
  }
  // Deep copy, so we do not modify input
  definitions = merge({}, definitions);

  const rootDef = getRootDefinition({ definitions, bulkOptions });
  const schemaId = uuidv4();
  const models = getModels(rootDef);

  // Apply `getType` to each top-level operation, i.e. Query and Mutation
  const topLevelSchema = Object.keys(rootDef).reduce((memo, name) => {
    const def = rootDef[name];
    memo[name] = getType(def, { schemaId, models });
    return memo;
  }, {});

  const rootSchema = new GraphQLSchema(topLevelSchema);
  cache.set(key, rootSchema);
  return rootSchema;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // When top-level model enters this function, `def.operation` will be defined.
  // This passed the operation to all sub-schemas, so that resolvers know the current operation
  const operation = def.operation || opts.operation;
  opts = Object.assign({}, opts, { operation });

  // Done so that children can get a cached reference of parent type, while avoiding infinite recursion
  // Only cache schemas that have a model name, because they are the only one that can recurse
  // Namespace by operation, because operations can have slightly different types
  const modelName = def.modelName;
  const key = modelName && `field/${opts.schemaId}/${modelName}/${operation}`;
  if (key && cache.exists(key)) {
    return cache.get(key);
  }

  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  if (key) {
    cache.set(key, field);
  }
  return field;
};

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

  {
    condition: def => def.required,
    value(def, opts) {
      // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
      const modifiedDef = Object.assign({}, def, { required: false });
      const subType = getType(modifiedDef, opts);
      const type = new GraphQLNonNull(subType);
      const description = def.description || (def.items && def.items.description);
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'array' && typeof def.items === 'object',
    value(def, opts) {
      const subDef = def.items;
      const subType = getType(subDef, opts);
      const type = new GraphQLList(subType);
      const description = subDef.description;
      const fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (subDef.modelName) {
        Object.assign(fieldInfo, {
          args: {
            id: {
              type: GraphQLInt,
              description: 'id to look at',
              defaultValue: 10
            },
          },
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'object',
    value(initialDef, opts) {
      // If this definition points to a top-level model, use that model instead
      const topDef = opts.models.find(model => initialDef.modelName
        && model.modelName === initialDef.modelName
        && opts.operation === model.operation);
      const def = topDef || initialDef;

      let name = getTypeName(def, opts.operation);
      const description = def.description;

      const type = new GraphQLObjectType({
        name,
        description,

        // This needs to be function, otherwise we run in an infinite recursion,
        // if the children try to reference a parent type
        fields() {
          return Object.keys(def.properties).reduce((fields, attrName) => {
            const childDef = def.properties[attrName];
            // Recurse over children
            fields[attrName] = getField(childDef, opts);
            return fields;
          }, {});
        },
      });

      let fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (initialDef.modelName) {
        Object.assign(fieldInfo, {
          //description: `Fetches information about a ${getSingularName(def)}`,
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && !op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'integer' && def.format === 'id',
    value(def) {
      const type = GraphQLID;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'integer',
    value(def) {
      const type = GraphQLInt;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'number',
    value(def) {
      const type = GraphQLFloat;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'string',
    value(def) {
      const type = GraphQLString;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'boolean',
    value(def) {
      const type = GraphQLBoolean;
      const description = def.description;
      return { type, description };
    },
  },

];

const executeOperation = async function ({ operation, args = {}, callback }) {
  const response = await callback({ operation, args });
  return response;
};

// Returns def.title
const getDefinitionName = function (def) {
  const name = def.title;
  if (!name) {
    throw new EngineError(`Missing "title" key in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  if (typeof def.title !== 'string') {
    throw new EngineError(`"title" must be a string in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  return name;
};

// Returns def.title, in plural form, lowercased, e.g. `pets`, for findMany queries
const getPluralName = function (def) {
  const name = getDefinitionName(def);
  return plural(name).toLowerCase();
};

// Returns def.title, in plural form, lowercased, e.g. `pet`, for findOne queries
const getSingularName = function (def) {
  const name = getDefinitionName(def);
  return singular(name).toLowerCase();
};

// Returns operation name, camelized, in plural form, e.g. `findPets` or `deletePets`
const getPluralOperationName = function (def, operation) {
  const name = getPluralName(def);
  return camelize(`${operation} ${name}`);
};

// Returns operation name, camelized, in singular form, e.g. `findPet` or `deletePet`
const getSingularOperationName = function (def, operation) {
  const name = getSingularName(def);
  return camelize(`${operation} ${name}`);
};

// Returns def.title, titleized, e.g. `Pet`, for schema type name
const getTypeName = function (def, operation) {
  const name = getDefinitionName(def);
  return titleize(operation) + titleize(singular(name));
};

const printSchema = function (schema) {
  return graphQLPrintSchema(schema);
};

// General key-value cache, specific for each root IDL definition
const cache = {

  _data: {},

  get(key) {
    return this._data[key];
  },

  exists(key) {
    return this.get(key) != null;
  },

  set(key, value) {
    this._data[key] = value;
  },

};

module.exports = {
  graphqlGetSchema: getSchema,
  graphqlPrintSchema: printSchema,
};<|endoftext|>


<commit_before>/*******************************************************************************
DigiBird meta information component
*******************************************************************************/
var platforms = require('../helpers/platforms');
var request = require('request-promise-native');

module.exports = {
    statistics: function(platformId) {
        var platform = platforms.platform(platformId);

        if (platform.endpoint_type === "json-api") {
            return this.statisticsJson(platform);
        } else if (platform.endpoint_type === "sparql") {
            // return this.statisticsSparql(platform);
            console.log("Get information using SPARQL query");
            this.statisticsSparql(platform);
        } else {
            var value = Math.floor((Math.random() * 10) + 1);
            var statistics = [{type:"users", value:value}];

            return new Promise(function(resolve, reject) {
                setTimeout(
                    function() {
                        // We fulfill the promise !
                        resolve(statistics);
                    }, 1000);
            });
        }

        return statistics;
    },
    statisticsJson: function(platform) {
        // query the platforms endpoint for contributions from the Netherlands
        var options = {
            url: platform.endpoint_location,
            qs: { query: "cnt:netherlands" }
        };

        return request(options)
        .then(function(json) {
            var results = JSON.parse(json);
            var statistics = [{type:"Dutch contributions", value:results.numRecordings}];
            return statistics;
        });
    },
    statisticsSparql: function(platform) {
        var query =
            "SELECT ?s ?p ?o " +
            "WHERE " +
            "{ ?s ?p ?o . } " +
            "LIMIT 10";

        console.log("SPARQL query:", query);

        // query the platforms endpoint for contributions
        var options = {
            url: platform.endpoint_location,
            method: 'POST', // using post with query as body, get might be more generic?
            headers: {
                'Accept': 'application/sparql-results+json',
                'Content-Type': 'application/sparql-query'
            },
            body: query
        };

        return request(options)
        .then(function(response) {
            console.log("response:", response);
        }, function (error) {
            console.log("error response:", error);
        });
    }
}
<commit_msg>Added list of queries for obtaining statistics, resolves #46 #47.<commit_after>/*******************************************************************************
DigiBird meta information component
*******************************************************************************/
var platforms = require('../helpers/platforms');
var request = require('request-promise-native');

module.exports = {
    statistics: function(platformId) {
        var platform = platforms.platform(platformId);

        if (platform.endpoint_type === "json-api") {
            return this.statisticsJson(platform);
        } else if (platform.endpoint_type === "sparql") {
            // return this.statisticsSparql(platform);
            console.log("Get information using SPARQL query");
            this.statisticsSparql(platform);
        } else {
            var value = Math.floor((Math.random() * 10) + 1);
            var statistics = [{type:"users", value:value}];

            return new Promise(function(resolve, reject) {
                setTimeout(
                    function() {
                        // We fulfill the promise !
                        resolve(statistics);
                    }, 1000);
            });
        }

        return statistics;
    },
    statisticsJson: function(platform) {
        // query the platforms endpoint for contributions from the Netherlands
        var options = {
            url: platform.endpoint_location,
            qs: { query: "cnt:netherlands" }
        };

        return request(options)
        .then(function(json) {
            var results = JSON.parse(json);
            var statistics = [{type:"Dutch contributions", value:results.numRecordings}];
            return statistics;
        });
    },
    statisticsSparql: function(platform) {
        var queries = this.sparqlStatisticsQueries();
        console.log("SPARQL query:", queries.test);

        // query the platforms endpoint for contributions
        var options = {
            url: platform.endpoint_location,
            method: 'POST', // using post with query as body, get might be more generic?
            headers: {
                'Accept': 'application/sparql-results+json',
                'Content-Type': 'application/sparql-query'
            },
            body: queries.annotated_objects_birds
        };

        return request(options)
        .then(function(response) {
            console.log("response:", response);
        }, function (error) {
            console.log("error response:", error);
        });
    },
    sparqlStatisticsQueries: function() {
        // create an object with queries that can be used for monitor purposes
        var queries =
        {
            test:
                "SELECT ?s ?p ?o " +
                "WHERE " +
                "{ ?s ?p ?o . } " +
                "LIMIT 10",
            users:
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?user) as ?numberUsers) " +
                "WHERE { " +
                    "?annotation oa:hasTarget ?work . " +
                    "?annotation oa:annotatedBy ?user . " +
                "}",
            users_birds:
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?user) as ?numberUsers) " +
                "WHERE { " +
                    "?annotation oa:hasTarget ?object . " +
                    "?object rdf:type <http://accurator.nl/bird#Target> . " +
                    "?annotation oa:annotatedBy ?user . " +
                "}",
            annotations:
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?annotation) as ?numberAnnotations) " +
                "WHERE { " +
                    "?annotation rdf:type oa:Annotation . " +
                "}",
            annotations_birds:
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?annotation) as ?numberAnnotations) " +
                "WHERE { " +
                    "?annotation oa:hasTarget ?object . " +
                    "?object rdf:type <http://accurator.nl/bird#Target> . " +
                    "?annotation rdf:type oa:Annotation . " +
                "}",
            objects:
                "PREFIX edm: <http://www.europeana.eu/schemas/edm/> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?object) as ?numberObjects) " +
                "WHERE { ?object rdf:type edm:ProvidedCHO . }",
            objects_birds:
                "PREFIX edm: <http://www.europeana.eu/schemas/edm/> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?object) as ?numberObjects) " +
                "WHERE { " +
                    "?object rdf:type edm:ProvidedCHO . " +
                    "?object rdf:type <http://accurator.nl/bird#Target> . " +
                "}",
            annotated_objects:
                "PREFIX edm: <http://www.europeana.eu/schemas/edm/> " +
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?object) as ?numberObjects) " +
                "WHERE { " +
                    "?annotation oa:hasTarget ?object . " +
                    "?object rdf:type edm:ProvidedCHO . " +
                    "?annotation rdf:type oa:Annotation . " +
                "}",
            annotated_objects_birds:
                "PREFIX oa: <http://www.w3.org/ns/oa#> " +
                "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
                "SELECT (COUNT(DISTINCT ?object) as ?numberObjects) " +
                "WHERE { " +
                    "?annotation oa:hasTarget ?object . " +
                    "?object rdf:type <http://accurator.nl/bird#Target> . " +
                    "?annotation rdf:type oa:Annotation . " +
                "}"

        };

        return queries;
    }
}
<|endoftext|>