<commit_before>const knex = require("../../knex")
const knexCleaner = require("knex-cleaner")
const {fakeUsers, fakeMedias, fakeReactions} = require("../../__fixtures__")

const [users, medias, reactions] = [fakeUsers(), fakeMedias(), fakeReactions()]

beforeAll(async () => {
  await knexCleaner.clean(knex)
  await knex("users").insert(users)
  await knex("medias").insert(medias)
  await knex("reactions").insert(reactions)
})

describe("media resolver", () => {
  const mediaResolver = require("../resolvers/media")
  test("posted_by", async () => {
    const posted_by = await mediaResolver.posted_by(medias[0])
    expect(posted_by).toEqual(users[0])
  })
  test("myReaction", async () => {
    const myReaction = await mediaResolver.myReaction(medias[0], null, {
      me: users[0],
    })
    expect(myReaction).toEqual(reactions[0])
    const myNullReaction = await mediaResolver.myReaction(medias[0], null, {})
    expect(myNullReaction).toBeNull()
  })
})

describe("query resolver", () => {
  const queryResolver = require("../resolvers/query")
  test("user", async () => {
    const user = await queryResolver.user(null, {id: "2"})
    expect(user).toEqual(users[1])
  })
  test("me", async () => {
    const me = await queryResolver.me(null, null, {me: users[1]})
    expect(me).toEqual(users[1])
  })
  test("media", async () => {
    const media = await queryResolver.media(null, {
      id: "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99",
    })
    expect(media).toEqual(medias[1])
  })
  test("medias", async () => {
    const mediasFirst = await queryResolver.medias(null, {first: 1})
    expect(mediasFirst).toMatchSnapshot()
    const mediasSecond = await queryResolver.medias(null, {
      first: 1,
      after: mediasFirst.pageInfo.endCursor,
    })
    expect(mediasSecond).toMatchSnapshot()
    const allMedias = await queryResolver.medias(null, {first: 10})
    expect(allMedias).toMatchSnapshot()
  })
})

describe("reaction resolver", () => {
  const reactionResolver = require("../resolvers/reaction")
  test("media", async () => {
    const media = await reactionResolver.media(reactions[1])
    expect(media).toEqual(medias[1])
  })
  test("user", async () => {
    const user = await reactionResolver.user(reactions[1])
    expect(user).toEqual(users[1])
  })
  test("type", async () => {
    const like = await reactionResolver.type(reactions[1])
    expect(like).toBe("LIKE")
    const dislike = await reactionResolver.type(reactions[0])
    expect(dislike).toBe("DISLIKE")
  })
})

describe("user resolver", () => {
  const userResolver = require("../resolvers/user")
  test("medias", async () => {
    const mediasUser0 = await userResolver.medias(users[0], {first: 10})
    const mediasUser1 = await userResolver.medias(users[1], {first: 10})
    expect(mediasUser0).toMatchSnapshot()
    expect(mediasUser1).toMatchSnapshot()
  })
  test("reactions", async () => {
    const likes = await userResolver.medias(users[0], {first: 10, type: "LIKE"})
    expect(likes).toMatchSnapshot()
    const dislikes = await userResolver.medias(users[0], {
      first: 10,
      type: "DISLIKE",
    })
    expect(dislikes).toMatchSnapshot()
  })
})

describe("mutation resolver", () => {
  const mutationResolver = require("../resolvers/mutation")
  test("addMedia minimum", async () => {
    const minimumMedia = {
      title: "Daughter - Candles",
      url: "https://www.youtube.com/watch?v=BucfErwPTWs",
      duration: "296.00",
    }
    const media = await mutationResolver.addMedia(
      null,
      {media: minimumMedia},
      {me: users[0]},
    )
    for (const key of ["title", "url", "duration"])
      expect(media[key]).toEqual(minimumMedia[key])
    expect(media.user_id).toBe(users[0].id)
    const [{count}] = await knex("medias").count()
    expect(+count).toBe(medias.length + 1)
    const [mediaFromDb] = await knex("medias").where({id: media.id})
    expect(mediaFromDb).toEqual(media)
  })
  test("addMedia with all", async () => {
    const mediaWithAll = {
      title: "XO Tour Llif3",
      artist: "Lil Uzi Vert",
      url: "https://www.youtube.com/watch?v=WrsFXgQk5UI",
      duration: "257.00",
      thumbnail: "https://i.ytimg.com/vi/WrsFXgQk5UI/hqdefault.jpg",
      description: "I like this music so much",
    }
    const media = await mutationResolver.addMedia(
      null,
      {media: mediaWithAll},
      {me: users[0]},
    )
    for (const key of [
      "title",
      "url",
      "duration",
      "artist",
      "thumbnail",
      "description",
    ])
      expect(media[key]).toEqual(mediaWithAll[key])
    expect(media.user_id).toBe(users[0].id)
    const [{count}] = await knex("medias").count()
    expect(+count).toBe(medias.length + 2)
    const [mediaFromDb] = await knex("medias").where({id: media.id})
    expect(mediaFromDb).toEqual(media)
  })
  describe("editMedia", () => {
    const testId = "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99"
    test("doesn't edit if user is not the same", async () => {
      const media = await mutationResolver.editMedia(
        null,
        {
          media_id: testId,
          media: {
            artist: "Major Lazer",
          },
        },
        {me: users[0]},
      )
      expect(media).toEqual(medias[1])
      const [mediaFromDb] = await knex("medias").where({id: testId})
      expect(mediaFromDb).toEqual(medias[1])
    })
    test("edit if user is the same", async () => {
      const media = await mutationResolver.editMedia(
        null,
        {
          media_id: testId,
          media: {
            artist: "Major Lazer",
          },
        },
        {me: users[1]},
      )
      expect(media.artist).toBe("Major Lazer")
      const [mediaFromDb] = await knex("medias").where({id: testId})
      expect(mediaFromDb).toEqual(media)
    })
  })
  describe("deleteMedia", () => {
    test("do not delete when user id is wrong", async () => {
      const inputMedia = {
        title: "test",
        url: "test",
        duration: "1.6",
      }
      const media = await mutationResolver.addMedia(
        null,
        {media: inputMedia},
        {me: users[0]},
      )
      const count = await knex("medias").count().then(([{count}]) => +count)
      const deletedMedia = await mutationResolver.deleteMedia(
        null,
        {media_id: media.id},
        {me: users[1]},
      )
      expect(deletedMedia).toBeUndefined()
      const newCount = await knex("medias").count().then(([{count}]) => +count)
      expect(newCount).toBe(count)
      const [mediaFromDb] = await knex("medias").where({id: media.id})
      expect(mediaFromDb).toBeDefined()
    })
    test("delete when user id is right", async () => {
      const inputMedia = {
        title: "test",
        url: "test",
        duration: "1.6",
      }
      const media = await mutationResolver.addMedia(
        null,
        {media: inputMedia},
        {me: users[0]},
      )
      const count = await knex("medias").count().then(([{count}]) => +count)
      const deletedMedia = await mutationResolver.deleteMedia(
        null,
        {media_id: media.id},
        {me: users[0]},
      )
      expect(deletedMedia.title).toBe("test")
      expect(deletedMedia.url).toBe("test")
      expect(deletedMedia.duration).toBe("1.60")
      const newCount = await knex("medias").count().then(([{count}]) => +count)
      expect(newCount).toBe(count - 1)
      const [mediaFromDb] = await knex("medias").where({id: media.id})
      expect(mediaFromDb).toBeUndefined()
    })
  })
  test("addMutation", async () => {
    const inputMutation = {
      media_id: medias[2].id,
      type: "LIKE",
    }
    const reaction = await mutationResolver.addReaction(null, inputMutation, {
      me: users[0],
    })
    expect(reaction.user_id).toBe(users[0].id)
    const [{count}] = await knex("reactions").count()
    expect(+count).toBe(reactions.length + 1)
    const [reactionFromDb] = await knex("reactions").where({id: reaction.id})
    expect(reactionFromDb).toEqual(reaction)
  })
  test("deleteMutation", async () => {
    const inputMutation = {
      media_id: medias[1].id,
      type: "LIKE",
    }
    const reaction = await mutationResolver.addReaction(null, inputMutation, {
      me: users[0],
    })
    const count = await knex("reactions").count().then(([{count}]) => +count)
    const deletedReaction = await mutationResolver.deleteReaction(
      null,
      inputMutation,
      {
        me: users[0],
      },
    )
    expect(deletedReaction.id).toBe(reaction.id)
    expect(deletedReaction.media_id).toBe(medias[1].id)
    expect(deletedReaction.type).toBe("like")
    const newCount = await knex("reactions").count().then(([{count}]) => +count)
    expect(newCount).toBe(count - 1)
    const [reactionFromDb] = await knex("reactions").where({id: reaction.id})
    expect(reactionFromDb).toBeUndefined()
  })
})
<commit_msg>Fix test for resolvers<commit_after>const knex = require("../../knex")
const knexCleaner = require("knex-cleaner")

const users = [{id: "1", name: "John Doe"}, {id: "2", name: "Jean-Luc Borgnol"}]
const medias = [
  {
    id: "6697c3b3-7934-45d5-bb0c-a985a262204f",
    user_id: "1",
    title: "Skrillex & Poo Bear - Would You Ever",
    url: "https://soundcloud.com/skrillex/wouldyouever",
    duration: "234.00",
    thumbnail:
      "https://i1.sndcdn.com/artworks-000235280255-5fyldq-t500x500.jpg",
    artist: "Skrillex",
    description: 'test3 "https://soundcloud.com/skrillex/wouldyouever"',
    created_at: new Date("2017-09-20T16:41:54.000Z"),
  },
  {
    id: "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99",
    user_id: "2",
    title:
      "Major Lazer - Sua Cara (feat. Anitta & Pabllo Vittar) (Official Audio)",
    url: "https://www.youtube.com/watch?v=2TzEALB4qP0",
    duration: "169.00",
    thumbnail: "https://i.ytimg.com/vi/2TzEALB4qP0/maxresdefault.jpg",
    artist: null,
    description: "test2",
    created_at: new Date("2017-09-20T15:13:44.000Z"),
  },
  {
    id: "520051cd-f2e6-436f-9400-0b069de3d118",
    user_id: "2",
    title: "The Chemical Brothers - Asleep From Day",
    url: "https://www.youtube.com/watch?v=Cj7F0KU367s",
    duration: "298.00",
    thumbnail: "https://i.ytimg.com/vi/Cj7F0KU367s/hqdefault.jpg",
    artist: null,
    description: null,
    created_at: new Date("2017-09-16T23:25:51.000Z"),
  },
  {
    id: "ebe7610c-8d49-48a0-8ba0-22cf54d880ac",
    user_id: "1",
    title: 'Diplo ft. Jahan Lennon - "About That Life" (Official Music Video)',
    url: "https://www.youtube.com/watch?v=ylwggwp_YsY",
    duration: "187.00",
    thumbnail: "https://i.ytimg.com/vi/ylwggwp_YsY/maxresdefault.jpg",
    artist: null,
    description: null,
    created_at: new Date("2017-09-16T23:24:11.000Z"),
  },
  {
    id: "ca96d4e0-b35d-4016-ac6e-b756e75c6492",
    user_id: "2",
    title:
      "ビッチと会う feat. Weny Dacillo, Pablo Blasta & JP THE WAVY / DJ CHARI & DJ TATSUKI",
    url: "https://www.youtube.com/watch?v=qkg2st1CWKg",
    duration: "316.00",
    thumbnail: "https://i.ytimg.com/vi/qkg2st1CWKg/maxresdefault.jpg",
    artist: null,
    description: null,
    created_at: new Date("2017-09-11T15:47:38.000Z"),
  },
  {
    id: "8727546e-0a28-4a86-9721-a454ad4fc544",
    user_id: "1",
    title:
      "Yellow Claw - Good Day ft. DJ Snake & Elliphant [OFFICIAL MUSIC VIDEO]",
    url: "https://www.youtube.com/watch?v=RyMqplmQ_fE",
    duration: "252.00",
    thumbnail: "https://i.ytimg.com/vi/RyMqplmQ_fE/maxresdefault.jpg",
    artist: null,
    description: "Moi j'aime bien le dimanche",
    created_at: new Date("2017-09-10T15:38:43.000Z"),
  },
]
const reactions = [
  {
    id: "e37a3977-323f-4aaf-b53b-6036c3e89f32",
    user_id: "1",
    media_id: "6697c3b3-7934-45d5-bb0c-a985a262204f",
    type: "dislike",
    created_at: new Date("2017-09-23T16:22:46.769Z"),
  },
  {
    id: "a439c88e-88de-45f8-b140-f783e86fba94",
    user_id: "2",
    media_id: "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99",
    type: "like",
    created_at: new Date("2017-09-23T16:29:11.802Z"),
  },
  {
    id: "73c8fd17-2ec6-4819-9434-878e153f3104",
    user_id: "1",
    media_id: "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99",
    type: "dislike",
    created_at: new Date("2017-09-24T09:35:21.060Z"),
  },
  {
    id: "ea39f370-4295-4198-8e07-484b8a8bcfbe",
    user_id: "1",
    media_id: "8727546e-0a28-4a86-9721-a454ad4fc544",
    type: "like",
    created_at: new Date("2017-09-24T09:44:23.444Z"),
  },
]

beforeAll(async () => {
  await knexCleaner.clean(knex)
  await knex("users").insert(users)
  await knex("medias").insert(medias)
  await knex("reactions").insert(reactions)
})

describe("media resolver", () => {
  const mediaResolver = require("../resolvers/media")
  test("posted_by", async () => {
    const posted_by = await mediaResolver.posted_by(medias[0])
    expect(posted_by).toEqual(users[0])
  })
  test("myReaction", async () => {
    const myReaction = await mediaResolver.myReaction(medias[0], null, {
      me: users[0],
    })
    expect(myReaction).toEqual(reactions[0])
    const myNullReaction = await mediaResolver.myReaction(medias[0], null, {})
    expect(myNullReaction).toBeNull()
  })
})

describe("query resolver", () => {
  const queryResolver = require("../resolvers/query")
  test("user", async () => {
    const user = await queryResolver.user(null, {id: "2"})
    expect(user).toEqual(users[1])
  })
  test("me", async () => {
    const me = await queryResolver.me(null, null, {me: users[1]})
    expect(me).toEqual(users[1])
  })
  test("media", async () => {
    const media = await queryResolver.media(null, {
      id: "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99",
    })
    expect(media).toEqual(medias[1])
  })
  test("medias", async () => {
    const mediasFirst = await queryResolver.medias(null, {first: 1})
    expect(mediasFirst).toMatchSnapshot()
    const mediasSecond = await queryResolver.medias(null, {
      first: 1,
      after: mediasFirst.pageInfo.endCursor,
    })
    expect(mediasSecond).toMatchSnapshot()
    const allMedias = await queryResolver.medias(null, {first: 10})
    expect(allMedias).toMatchSnapshot()
  })
})

describe("reaction resolver", () => {
  const reactionResolver = require("../resolvers/reaction")
  test("media", async () => {
    const media = await reactionResolver.media(reactions[1])
    expect(media).toEqual(medias[1])
  })
  test("user", async () => {
    const user = await reactionResolver.user(reactions[1])
    expect(user).toEqual(users[1])
  })
  test("type", async () => {
    const like = await reactionResolver.type(reactions[1])
    expect(like).toBe("LIKE")
    const dislike = await reactionResolver.type(reactions[0])
    expect(dislike).toBe("DISLIKE")
  })
})

describe("user resolver", () => {
  const userResolver = require("../resolvers/user")
  test("medias", async () => {
    const mediasUser0 = await userResolver.medias(users[0], {first: 10})
    const mediasUser1 = await userResolver.medias(users[1], {first: 10})
    expect(mediasUser0).toMatchSnapshot()
    expect(mediasUser1).toMatchSnapshot()
  })
  test("reactions", async () => {
    const likes = await userResolver.medias(users[0], {first: 10, type: "LIKE"})
    expect(likes).toMatchSnapshot()
    const dislikes = await userResolver.medias(users[0], {
      first: 10,
      type: "DISLIKE",
    })
    expect(dislikes).toMatchSnapshot()
  })
})

describe("mutation resolver", () => {
  const mutationResolver = require("../resolvers/mutation")
  test("addMedia minimum", async () => {
    const minimumMedia = {
      title: "Daughter - Candles",
      url: "https://www.youtube.com/watch?v=BucfErwPTWs",
      duration: "296.00",
    }
    const media = await mutationResolver.addMedia(
      null,
      {media: minimumMedia},
      {me: users[0]},
    )
    for (const key of ["title", "url", "duration"])
      expect(media[key]).toEqual(minimumMedia[key])
    expect(media.user_id).toBe(users[0].id)
    const [{count}] = await knex("medias").count()
    expect(+count).toBe(medias.length + 1)
    const [mediaFromDb] = await knex("medias").where({id: media.id})
    expect(mediaFromDb).toEqual(media)
  })
  test("addMedia with all", async () => {
    const mediaWithAll = {
      title: "XO Tour Llif3",
      artist: "Lil Uzi Vert",
      url: "https://www.youtube.com/watch?v=WrsFXgQk5UI",
      duration: "257.00",
      thumbnail: "https://i.ytimg.com/vi/WrsFXgQk5UI/hqdefault.jpg",
      description: "I like this music so much",
    }
    const media = await mutationResolver.addMedia(
      null,
      {media: mediaWithAll},
      {me: users[0]},
    )
    for (const key of [
      "title",
      "url",
      "duration",
      "artist",
      "thumbnail",
      "description",
    ])
      expect(media[key]).toEqual(mediaWithAll[key])
    expect(media.user_id).toBe(users[0].id)
    const [{count}] = await knex("medias").count()
    expect(+count).toBe(medias.length + 2)
    const [mediaFromDb] = await knex("medias").where({id: media.id})
    expect(mediaFromDb).toEqual(media)
  })
  describe("editMedia", () => {
    const testId = "13c309d6-c4e6-4f8b-b52a-c05ae0c03e99"
    test("doesn't edit if user is not the same", async () => {
      const media = await mutationResolver.editMedia(
        null,
        {
          media_id: testId,
          media: {
            artist: "Major Lazer",
          },
        },
        {me: users[0]},
      )
      expect(media).toEqual(medias[1])
      const [mediaFromDb] = await knex("medias").where({id: testId})
      expect(mediaFromDb).toEqual(medias[1])
    })
    test("edit if user is the same", async () => {
      const media = await mutationResolver.editMedia(
        null,
        {
          media_id: testId,
          media: {
            artist: "Major Lazer",
          },
        },
        {me: users[1]},
      )
      expect(media.artist).toBe("Major Lazer")
      const [mediaFromDb] = await knex("medias").where({id: testId})
      expect(mediaFromDb).toEqual(media)
    })
  })
  describe("deleteMedia", () => {
    test("do not delete when user id is wrong", async () => {
      const inputMedia = {
        title: "test",
        url: "test",
        duration: "1.6",
      }
      const media = await mutationResolver.addMedia(
        null,
        {media: inputMedia},
        {me: users[0]},
      )
      const count = await knex("medias").count().then(([{count}]) => +count)
      const deletedMedia = await mutationResolver.deleteMedia(
        null,
        {media_id: media.id},
        {me: users[1]},
      )
      expect(deletedMedia).toBeUndefined()
      const newCount = await knex("medias").count().then(([{count}]) => +count)
      expect(newCount).toBe(count)
      const [mediaFromDb] = await knex("medias").where({id: media.id})
      expect(mediaFromDb).toBeDefined()
    })
    test("delete when user id is right", async () => {
      const inputMedia = {
        title: "test",
        url: "test",
        duration: "1.6",
      }
      const media = await mutationResolver.addMedia(
        null,
        {media: inputMedia},
        {me: users[0]},
      )
      const count = await knex("medias").count().then(([{count}]) => +count)
      const deletedMedia = await mutationResolver.deleteMedia(
        null,
        {media_id: media.id},
        {me: users[0]},
      )
      expect(deletedMedia.title).toBe("test")
      expect(deletedMedia.url).toBe("test")
      expect(deletedMedia.duration).toBe("1.60")
      const newCount = await knex("medias").count().then(([{count}]) => +count)
      expect(newCount).toBe(count - 1)
      const [mediaFromDb] = await knex("medias").where({id: media.id})
      expect(mediaFromDb).toBeUndefined()
    })
  })
  test("addMutation", async () => {
    const inputMutation = {
      media_id: medias[2].id,
      type: "LIKE",
    }
    const reaction = await mutationResolver.addReaction(null, inputMutation, {
      me: users[0],
    })
    expect(reaction.user_id).toBe(users[0].id)
    const [{count}] = await knex("reactions").count()
    expect(+count).toBe(reactions.length + 1)
    const [reactionFromDb] = await knex("reactions").where({id: reaction.id})
    expect(reactionFromDb).toEqual(reaction)
  })
  test("deleteMutation", async () => {
    const inputMutation = {
      media_id: medias[1].id,
      type: "LIKE",
    }
    const reaction = await mutationResolver.addReaction(null, inputMutation, {
      me: users[0],
    })
    const count = await knex("reactions").count().then(([{count}]) => +count)
    const deletedReaction = await mutationResolver.deleteReaction(
      null,
      inputMutation,
      {
        me: users[0],
      },
    )
    expect(deletedReaction.id).toBe(reaction.id)
    expect(deletedReaction.media_id).toBe(medias[1].id)
    expect(deletedReaction.type).toBe("like")
    const newCount = await knex("reactions").count().then(([{count}]) => +count)
    expect(newCount).toBe(count - 1)
    const [reactionFromDb] = await knex("reactions").where({id: reaction.id})
    expect(reactionFromDb).toBeUndefined()
  })
})
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * imports. we use the testing tdd testing framework mocha and the assertion
 * library chai.
 */

const _ = require('lodash');
const chai = require('chai');
const mocha = require('mocha');
const expect = chai.expect;
const fs = require('fs').promises;
const moment = require('moment');
const promisify = require('es6-promisify');
const csv = require('csv');
const parseCsv = promisify(csv.parse);

const generateData = require('../data/generateTestingData'); // To get this file compile ../data/generateTestingData.ts

mocha.describe('The generateDates function', () => {
	const generateDates = generateData.generateDates;
	mocha.it('should exist.', () => {
		expect(typeof (generateDates)).to.equal('function');
	});
	mocha.it('should be able to generate dates at different time steps.', () => {
		const startTimeStamp = '2020-10-01 21:00:00';
		const endTimeStamp = '2020-10-02 21:00:00';
		const timeStep = { days: 1 };
		expect(generateDates(startTimeStamp, endTimeStamp, timeStep)).to.deep.equal(
			[startTimeStamp, endTimeStamp]
		);
	});
	mocha.it('should be able to generate dates at a time step 15 seconds', () => {
		const startTimeStamp = '2020-10-01 21:00:00';
		const endTimeStamp = '2020-10-02 21:00:00';
		const timeStep = { second: 15 };
		const startMoment = moment(startTimeStamp);
		const endMoment = moment(endTimeStamp);
		const result = [];
		while (!startMoment.isAfter(endMoment)) {
			result.push(startMoment.format('YYYY-MM-DD HH:mm:ss'));
			startMoment.add(timeStep);
		}
		expect(generateDates(startTimeStamp, endTimeStamp, timeStep)).to.deep.equal(
			result
		);
	});
});

mocha.describe('momenting', () => {
	const momenting = generateData.momenting;
	mocha.it('should cover the simple singleton case', () => {
		const date = '2019-09-10 00:00:15';
		const periodOfMoments = [moment(date)];
		expect([1]).to.deep.equals(momenting(periodOfMoments));
	});
	mocha.it('should be able to partition an array with just two elements', () => {
		const startMoment = moment('2019-09-10 00:00:15');
		const timeStepMs = 15000;
		expect([0, 1]).to.deep.equals(momenting([startMoment, startMoment.clone().add(timeStepMs)], timeStepMs));
	});
	mocha.it('should cover the a skipped datetime', () => {
		const test = ['2019-09-10T00:00:15',
			'2019-09-10T00:00:30', '2019-09-10T00:01:00'];
		const timeStepMs = 15000;
		expect(momenting(test.map(timeStamp => moment(timeStamp)), timeStepMs)).to.deep.equals([0, 1, 1]);
	});
	mocha.it('should work on irregular timesteps', () => {
		const test = ['2019-09-10T00:00:15',
			'2019-09-10T00:00:30', '2019-09-10T00:00:50'];
		const timeStepMs = 15000;
		expect(momenting(test.map(timeStamp => moment(timeStamp)), timeStepMs)).to.deep.equals([0, 1, 5 / 15]);
	});
});

mocha.describe('Generate Sine wave helper', () => {
	const generateSineData = generateData.generateSineData;
	mocha.it('should exist', () => {
		expect(typeof (generateSineData)).to.equal('function');
	});
	mocha.it('should be able to generate data for simply one day', () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		expect(generateSineData(startTimeStamp, endTimeStamp, { timeStep: { day: 1 } })).to.deep.equals([[startTimeStamp, '1'], [endTimeStamp, '1']]);
	});
	mocha.it('should be able to generate data for half a day', () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		expect(generateSineData(startTimeStamp, endTimeStamp, { timeStep: { hour: 12 } }))
			.to.deep.equals([[startTimeStamp, '1'], ['2019-09-10 12:00:00', '1'], [endTimeStamp, '1']]);
	});
});

mocha.describe('Generate Sine wave', () => {
	const generateSineData = generateData.generateSineData;
	const generateSine = generateData.generateSine;
	mocha.it('should properly write to file', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test1.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 } };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude });
		await generateSine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const records = await parseCsv(dataFromFile);

		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
	mocha.it('should be able to normalize values for OED', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test2.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 }, normalizeByHour: true };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude })
			.map(row => {
				let scaledValue = row[1] * 1 / 3;
				scaledValue = scaledValue.toFixed(8); // we reduce numbers down to 8 decimals places because the rest are insignificant
				return [row[0], scaledValue];
			});
		await generateSine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const preprocessedRecords = await parseCsv(dataFromFile);
		// we reduce numbers down to 8 decimals places because there will be differences at very
		// low significant places.
		const records = preprocessedRecords.map(row => [row[0], parseFloat(row[1]).toFixed(8)]);
		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
});

mocha.describe('Generate Cosine wave', () => {
	const generateSineData = generateData.generateSineData;
	const generateCosine = generateData.generateCosine;
	mocha.it('should properly write to file', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test1.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 } };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude, phaseShift: (Math.PI / 2) });
		await generateCosine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const records = await parseCsv(dataFromFile);

		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
});
<commit_msg>Flip expectations and actual for consistency<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * imports. we use the testing tdd testing framework mocha and the assertion
 * library chai.
 */

const _ = require('lodash');
const chai = require('chai');
const mocha = require('mocha');
const expect = chai.expect;
const fs = require('fs').promises;
const moment = require('moment');
const promisify = require('es6-promisify');
const csv = require('csv');
const parseCsv = promisify(csv.parse);

const generateData = require('../data/generateTestingData'); // To get this file compile ../data/generateTestingData.ts

mocha.describe('The generateDates function', () => {
	const generateDates = generateData.generateDates;
	mocha.it('should exist.', () => {
		expect(typeof (generateDates)).to.equal('function');
	});
	mocha.it('should be able to generate dates at different time steps.', () => {
		const startTimeStamp = '2020-10-01 21:00:00';
		const endTimeStamp = '2020-10-02 21:00:00';
		const timeStep = { days: 1 };
		expect(generateDates(startTimeStamp, endTimeStamp, timeStep)).to.deep.equal(
			[startTimeStamp, endTimeStamp]
		);
	});
	mocha.it('should be able to generate dates at a time step 15 seconds', () => {
		const startTimeStamp = '2020-10-01 21:00:00';
		const endTimeStamp = '2020-10-02 21:00:00';
		const timeStep = { second: 15 };
		const startMoment = moment(startTimeStamp);
		const endMoment = moment(endTimeStamp);
		const result = [];
		while (!startMoment.isAfter(endMoment)) {
			result.push(startMoment.format('YYYY-MM-DD HH:mm:ss'));
			startMoment.add(timeStep);
		}
		expect(generateDates(startTimeStamp, endTimeStamp, timeStep)).to.deep.equal(
			result
		);
	});
});

mocha.describe('momenting', () => {
	const momenting = generateData.momenting;
	mocha.it('should cover the simple singleton case', () => {
		const date = '2019-09-10 00:00:15';
		const periodOfMoments = [moment(date)];
		expect(momenting(periodOfMoments)).to.deep.equals([1]);
	});
	mocha.it('should be able to partition an array with just two elements', () => {
		const startMoment = moment('2019-09-10 00:00:15');
		const timeStepMs = 15000;
		expect(momenting([startMoment, startMoment.clone().add(timeStepMs)], timeStepMs)).to.deep.equals([0, 1]);
	});
	mocha.it('should cover the a skipped datetime', () => {
		const test = ['2019-09-10T00:00:15',
			'2019-09-10T00:00:30', '2019-09-10T00:01:00'];
		const timeStepMs = 15000;
		expect(momenting(test.map(timeStamp => moment(timeStamp)), timeStepMs)).to.deep.equals([0, 1, 1]);
	});
	mocha.it('should work on irregular timesteps', () => {
		const test = ['2019-09-10T00:00:15',
			'2019-09-10T00:00:30', '2019-09-10T00:00:50'];
		const timeStepMs = 15000;
		expect(momenting(test.map(timeStamp => moment(timeStamp)), timeStepMs)).to.deep.equals([0, 1, 5 / 15]);
	});
});

mocha.describe('Generate Sine wave helper', () => {
	const generateSineData = generateData.generateSineData;
	mocha.it('should exist', () => {
		expect(typeof (generateSineData)).to.equal('function');
	});
	mocha.it('should be able to generate data for simply one day', () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		expect(generateSineData(startTimeStamp, endTimeStamp, { timeStep: { day: 1 } })).to.deep.equals([[startTimeStamp, '1'], [endTimeStamp, '1']]);
	});
	mocha.it('should be able to generate data for half a day', () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		expect(generateSineData(startTimeStamp, endTimeStamp, { timeStep: { hour: 12 } }))
			.to.deep.equals([[startTimeStamp, '1'], ['2019-09-10 12:00:00', '1'], [endTimeStamp, '1']]);
	});
});

mocha.describe('Generate Sine wave', () => {
	const generateSineData = generateData.generateSineData;
	const generateSine = generateData.generateSine;
	mocha.it('should properly write to file', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test1.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 } };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude });
		await generateSine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const records = await parseCsv(dataFromFile);

		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
	mocha.it('should be able to normalize values for OED', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test2.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 }, normalizeByHour: true };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude })
			.map(row => {
				let scaledValue = row[1] * 1 / 3;
				scaledValue = scaledValue.toFixed(8); // we reduce numbers down to 8 decimals places because the rest are insignificant
				return [row[0], scaledValue];
			});
		await generateSine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const preprocessedRecords = await parseCsv(dataFromFile);
		// we reduce numbers down to 8 decimals places because there will be differences at very
		// low significant places.
		const records = preprocessedRecords.map(row => [row[0], parseFloat(row[1]).toFixed(8)]);
		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
});

mocha.describe('Generate Cosine wave', () => {
	const generateSineData = generateData.generateSineData;
	const generateCosine = generateData.generateCosine;
	mocha.it('should properly write to file', async () => {
		const startTimeStamp = '2019-09-10 00:00:00';
		const endTimeStamp = '2019-09-11 00:00:00';
		const filename = 'test1.csv';
		const timeOptions = { timeStep: { minute: 20 }, periodLength: { day: 1 } };
		const maxAmplitude = 2;
		const data = generateSineData(startTimeStamp, endTimeStamp, { ...timeOptions, maxAmplitude: maxAmplitude, phaseShift: (Math.PI / 2) });
		await generateCosine(startTimeStamp, endTimeStamp, { ...timeOptions, filename: `${__dirname}/${filename}`, maxAmplitude: maxAmplitude });
		// https://stackabuse.com/reading-and-writing-csv-files-in-nodejs-with-node-csv/
		const dataFromFile = await fs.readFile(`${__dirname}/${filename}`);
		const records = await parseCsv(dataFromFile);

		expect(records).to.deep.equal(data);
		await fs.unlink(`${__dirname}/${filename}`); // delete test file created
	});
});
<|endoftext|>


<commit_before>/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
/* eslint-disable no-underscore-dangle */
const { authenticate } = require('@feathersjs/authentication').hooks;
const moment = require('moment');
const { v4: uuidv4 } = require('uuid');
const { Configuration } = require('@schul-cloud/commons');
const reqlib = require('app-root-path').require;

const { Forbidden, BadRequest, GeneralError } = reqlib('src/errors');
const logger = require('../../../logger');
const { createMultiDocumentAggregation } = require('../utils/aggregations');
const { splitForSearchIndexes } = require('../../../utils/database');
const { hasSchoolPermission, blockDisposableEmail } = require('../../../hooks');
const { equal: equalIds } = require('../../../helper/compare').ObjectId;
const { validateParams, parseRequestQuery } = require('../hooks/adminUsers.hooks');
const { sendRegistrationLink } = require('../hooks/userService');

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id).select('schoolId').lean().exec();

const getCurrentYear = (ref, schoolId) =>
	ref.app
		.service('schools')
		.get(schoolId, {
			query: { $select: ['currentYear'] },
		})
		.then(({ currentYear }) => currentYear.toString());

class AdminUsers {
	constructor(roleName) {
		this.roleName = roleName;
		this.docs = {};
	}

	async find(params) {
		return this.getUsers(undefined, params);
	}

	async get(id, params) {
		return this.getUsers(id, params);
	}

	async getUsers(_id, params) {
		// integration test did not get the role in the setup
		// so here is a workaround set it at first call
		if (!this.role) {
			this.role = (
				await this.app.service('roles').find({
					query: { name: this.roleName },
				})
			).data[0];
		}

		try {
			const { query: clientQuery = {}, account } = params;
			const currentUserId = account.userId.toString();

			// fetch base data
			const { schoolId } = await getCurrentUserInfo(currentUserId);
			const schoolYearId = await getCurrentYear(this, schoolId);

			const query = {
				schoolId,
				roles: this.role._id,
				schoolYearId,
				sort: clientQuery.$sort || clientQuery.sort,
				select: [
					'consentStatus',
					'consent',
					'classes',
					'firstName',
					'lastName',
					'email',
					'createdAt',
					'importHash',
					'birthday',
					'preferences.registrationMailSend',
				],
				skip: clientQuery.$skip || clientQuery.skip,
				limit: clientQuery.$limit || clientQuery.limit,
			};
			if (_id) {
				query._id = _id;
			} else if (clientQuery.users) {
				query._id = clientQuery.users;
				// If the number of users exceeds 20, the underlying parsing library
				// will convert the array to an object with the index as the key.
				// To continue working with it, we convert it here back to the array form.
				// See the documentation for further infos: https://github.com/ljharb/qs#parsing-arrays
				if (typeof query._id === 'object') query._id = Object.values(query._id);
			}
			if (clientQuery.consentStatus) query.consentStatus = clientQuery.consentStatus;
			if (clientQuery.classes) query.classes = clientQuery.classes;
			if (clientQuery.firstName) query.firstName = clientQuery.firstName;
			if (clientQuery.lastName) query.lastName = clientQuery.lastName;
			if (clientQuery.searchQuery) {
				const seachQueryElements = splitForSearchIndexes(clientQuery.searchQuery);
				query.searchQuery = seachQueryElements.join(' ');
				// recreating sort here, to set searchQuery as first (main) parameter of sorting
				query.searchFilterGate = seachQueryElements.length * 0.8;
				query.sort = {
					searchQuery: 1,
					...query.sort,
				};
			}

			const dateQueries = ['createdAt'];
			for (const dateQuery of dateQueries) {
				if (clientQuery[dateQuery]) {
					if (typeof clientQuery[dateQuery] === 'object') {
						for (const [key, value] of Object.entries(clientQuery[dateQuery])) {
							if (['$gt', '$gte', '$lt', '$lte'].includes(key)) {
								clientQuery[dateQuery][key] = new Date(value);
							}
						}
						query[dateQuery] = clientQuery[dateQuery];
					} else {
						query[dateQuery] = new Date(clientQuery[dateQuery]);
					}
				}
			}

			return new Promise((resolve, reject) =>
				userModel
					.aggregate(createMultiDocumentAggregation(query))
					.option({
						collation: { locale: 'de', caseLevel: true },
					})
					.exec((err, res) => {
						if (err) reject(err);
						else resolve(res[0] || {});
					})
			);
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this.', err);
			}
			if (err && err.code >= 500) {
				const uuid = uuidv4();
				logger.error(uuid, err);
				if (Configuration.get('NODE_ENV') !== 'production') {
					throw err;
				}
				throw new GeneralError(uuid);
			}
			throw err;
		}
	}

	async create(_data, _params) {
		const currentUserId = _params.account.userId.toString();
		const { schoolId } = await getCurrentUserInfo(currentUserId);
		await this.checkExternal(schoolId);
		const { email } = _data;
		await this.checkMail(email);
		return this.app.service('usersModel').create({
			..._data,
			schoolId,
			roles: [this.role._id],
		});
	}

	async update(_id, _data, _params) {
		// route should be blocked
		return this.patch(_id, _data, _params);
	}

	async patch(_id, _data, _params) {
		if (!_id) throw new BadRequest('id is required');
		const params = await this.prepareParams(_id, _params);
		const { email } = _data;
		await this.checkMail(email, _id);
		await this.updateAccount(email, _id);
		// _id is part of params and will be combined with the schoolId
		const users = await this.prepareRoleback(email, _id, () =>
			this.app.service('usersModel').patch(null, _data, params)
		);

		if (users.length === 0) throw new BadRequest('user could not be edit');

		return users[0];
	}

	/**
	 * Should be used only in create in future, t
	 * he user itself should get a flag to define if it is from a external system
	 * @param {*} schoolId
	 */
	async checkExternal(schoolId) {
		const { isExternal } = await this.app.service('schools').get(schoolId);
		if (isExternal) {
			throw new Forbidden('Creating new students or teachers is only possible in the source system.');
		}
	}

	/**
	 * Create a params value for following request to filter and secure request
	 * - admins only allowed to change user on own school
	 * @param {Object} params - Feathersjs params
	 */
	async prepareParams(_id, params) {
		const currentUserId = params.account.userId.toString();
		const { schoolId } = await getCurrentUserInfo(currentUserId);

		return {
			query: {
				_id,
				roles: this.role._id,
				schoolId: schoolId.toString(),
			},
		};
	}

	/**
	 * request user and compare the email address.
	 * if possible it should be solved via unique index on database
	 * @param {string} email
	 * @param {string} userId
	 */
	async checkMail(email, userId) {
		if (email) {
			const user = await this.app.service('usersModel').find({ query: { email: email.toLowerCase() } });
			if (userId && user.total === 1 && equalIds(user.data[0]._id, userId)) return;
			if (user.total !== 0) {
				throw new BadRequest('Email already exists.');
			}
		}
	}

	/**
	 * Update the account email if eamil will be changed on user.
	 * IMPORTANT: Keep in mind to do a roleback if saving the user failed
	 * @param {*} email
	 * @param {*} userId
	 */
	async updateAccount(email, userId) {
		if (email) {
			email = email.toLowerCase();
			await this.app.service('accountModel').patch(
				null,
				{ username: email },
				{
					query: {
						userId,
						username: { $ne: email },
						systemId: { $exists: false },
					},
				}
			);
		}
	}

	async prepareRoleback(email, userId, fu) {
		try {
			return await fu();
		} catch (err) {
			if (email) {
				const { email: oldMail } = await this.app.service('usersModel').get(userId);
				await this.app.service('accountModel').patch(
					null,
					{ username: oldMail },
					{
						query: {
							userId,
							systemId: { $exists: false },
						},
					}
				);
			}
			throw err;
		}
	}

	async remove(id, params) {
		const { _ids } = params.query;
		const currentUser = await getCurrentUserInfo(params.account.userId);

		if (id) {
			const userToRemove = await getCurrentUserInfo(id);
			if (!equalIds(currentUser.schoolId, userToRemove.schoolId)) {
				throw new Forbidden('You cannot remove users from other schools.');
			}
			await this.app.service('accountModel').remove(null, { query: { userId: id } });
			return this.app.service('usersModel').remove(id);
		}

		const usersIds = await Promise.all(_ids.map((userId) => getCurrentUserInfo(userId)));
		if (usersIds.some((user) => !equalIds(currentUser.schoolId, user.schoolId))) {
			throw new Forbidden('You cannot remove users from other schools.');
		}

		await this.app.service('accountModel').remove(null, { query: { userId: { $in: _ids } } });
		return this.app.service('usersModel').remove(null, { query: { _id: { $in: _ids } } });
	}

	async setup(app) {
		this.app = app;
		this.role = (
			await this.app.service('roles').find({
				query: { name: this.roleName },
			})
		).data[0];
	}
}

const formatBirthdayOfUsers = ({ result: { data: users } }) => {
	users.forEach((user) => {
		if (user.birthday) user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		if (user.birthday) {
			user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		}
	});
};

const adminHookGenerator = (kind) => ({
	before: {
		all: [authenticate('jwt')],
		find: [hasSchoolPermission(`${kind}_LIST`)],
		get: [hasSchoolPermission(`${kind}_LIST`)],
		create: [hasSchoolPermission(`${kind}_CREATE`), blockDisposableEmail('email')],
		update: [hasSchoolPermission(`${kind}_EDIT`), blockDisposableEmail('email')],
		patch: [hasSchoolPermission(`${kind}_EDIT`), blockDisposableEmail('email')],
		remove: [hasSchoolPermission(`${kind}_DELETE`), parseRequestQuery, validateParams],
	},
	after: {
		find: [formatBirthdayOfUsers],
		create: [sendRegistrationLink],
	},
});

module.exports = {
	AdminUsers,
	adminHookGenerator,
};
<commit_msg>change search credentials <commit_after>/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
/* eslint-disable no-underscore-dangle */
const { authenticate } = require('@feathersjs/authentication').hooks;
const moment = require('moment');
const { v4: uuidv4 } = require('uuid');
const { Configuration } = require('@schul-cloud/commons');
const reqlib = require('app-root-path').require;

const { Forbidden, BadRequest, GeneralError } = reqlib('src/errors');
const logger = require('../../../logger');
const { createMultiDocumentAggregation } = require('../utils/aggregations');
const { splitForSearchIndexes } = require('../../../utils/database');
const { hasSchoolPermission, blockDisposableEmail } = require('../../../hooks');
const { equal: equalIds } = require('../../../helper/compare').ObjectId;
const { validateParams, parseRequestQuery } = require('../hooks/adminUsers.hooks');
const { sendRegistrationLink } = require('../hooks/userService');

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id).select('schoolId').lean().exec();

const getCurrentYear = (ref, schoolId) =>
	ref.app
		.service('schools')
		.get(schoolId, {
			query: { $select: ['currentYear'] },
		})
		.then(({ currentYear }) => currentYear.toString());

class AdminUsers {
	constructor(roleName) {
		this.roleName = roleName;
		this.docs = {};
	}

	async find(params) {
		return this.getUsers(undefined, params);
	}

	async get(id, params) {
		return this.getUsers(id, params);
	}

	async getUsers(_id, params) {
		// integration test did not get the role in the setup
		// so here is a workaround set it at first call
		if (!this.role) {
			this.role = (
				await this.app.service('roles').find({
					query: { name: this.roleName },
				})
			).data[0];
		}

		try {
			const { query: clientQuery = {}, account } = params;
			const currentUserId = account.userId.toString();

			// fetch base data
			const { schoolId } = await getCurrentUserInfo(currentUserId);
			const schoolYearId = await getCurrentYear(this, schoolId);

			const query = {
				schoolId,
				roles: this.role._id,
				schoolYearId,
				sort: clientQuery.$sort || clientQuery.sort,
				select: [
					'consentStatus',
					'consent',
					'classes',
					'firstName',
					'lastName',
					'email',
					'createdAt',
					'importHash',
					'birthday',
					'preferences.registrationMailSend',
				],
				skip: clientQuery.$skip || clientQuery.skip,
				limit: clientQuery.$limit || clientQuery.limit,
			};
			if (_id) {
				query._id = _id;
			} else if (clientQuery.users) {
				query._id = clientQuery.users;
				// If the number of users exceeds 20, the underlying parsing library
				// will convert the array to an object with the index as the key.
				// To continue working with it, we convert it here back to the array form.
				// See the documentation for further infos: https://github.com/ljharb/qs#parsing-arrays
				if (typeof query._id === 'object') query._id = Object.values(query._id);
			}
			if (clientQuery.consentStatus) query.consentStatus = clientQuery.consentStatus;
			if (clientQuery.classes) query.classes = clientQuery.classes;
			if (clientQuery.firstName) query.firstName = clientQuery.firstName;
			if (clientQuery.lastName) query.lastName = clientQuery.lastName;
			if (clientQuery.searchQuery && clientQuery.searchQuery.trim().length !== 0) {
				const seachQueryElements = splitForSearchIndexes(clientQuery.searchQuery.trim());
				query.searchQuery = seachQueryElements.join(' ');
				// reduce gate, because 'splitForSearchIndexes' adds value for 1 and 2 first letters and
				query.searchFilterGate = (seachQueryElements.length - 1) * 0.85;
				// recreating sort here, to set searchQuery as first (main) parameter of sorting
				query.sort = {
					searchQuery: 1,
					...query.sort,
				};
			}

			const dateQueries = ['createdAt'];
			for (const dateQuery of dateQueries) {
				if (clientQuery[dateQuery]) {
					if (typeof clientQuery[dateQuery] === 'object') {
						for (const [key, value] of Object.entries(clientQuery[dateQuery])) {
							if (['$gt', '$gte', '$lt', '$lte'].includes(key)) {
								clientQuery[dateQuery][key] = new Date(value);
							}
						}
						query[dateQuery] = clientQuery[dateQuery];
					} else {
						query[dateQuery] = new Date(clientQuery[dateQuery]);
					}
				}
			}

			return new Promise((resolve, reject) =>
				userModel
					.aggregate(createMultiDocumentAggregation(query))
					.option({
						collation: { locale: 'de', caseLevel: true },
					})
					.exec((err, res) => {
						if (err) reject(err);
						else resolve(res[0] || {});
					})
			);
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this.', err);
			}
			if (err && err.code >= 500) {
				const uuid = uuidv4();
				logger.error(uuid, err);
				if (Configuration.get('NODE_ENV') !== 'production') {
					throw err;
				}
				throw new GeneralError(uuid);
			}
			throw err;
		}
	}

	async create(_data, _params) {
		const currentUserId = _params.account.userId.toString();
		const { schoolId } = await getCurrentUserInfo(currentUserId);
		await this.checkExternal(schoolId);
		const { email } = _data;
		await this.checkMail(email);
		return this.app.service('usersModel').create({
			..._data,
			schoolId,
			roles: [this.role._id],
		});
	}

	async update(_id, _data, _params) {
		// route should be blocked
		return this.patch(_id, _data, _params);
	}

	async patch(_id, _data, _params) {
		if (!_id) throw new BadRequest('id is required');
		const params = await this.prepareParams(_id, _params);
		const { email } = _data;
		await this.checkMail(email, _id);
		await this.updateAccount(email, _id);
		// _id is part of params and will be combined with the schoolId
		const users = await this.prepareRoleback(email, _id, () =>
			this.app.service('usersModel').patch(null, _data, params)
		);

		if (users.length === 0) throw new BadRequest('user could not be edit');

		return users[0];
	}

	/**
	 * Should be used only in create in future, t
	 * he user itself should get a flag to define if it is from a external system
	 * @param {*} schoolId
	 */
	async checkExternal(schoolId) {
		const { isExternal } = await this.app.service('schools').get(schoolId);
		if (isExternal) {
			throw new Forbidden('Creating new students or teachers is only possible in the source system.');
		}
	}

	/**
	 * Create a params value for following request to filter and secure request
	 * - admins only allowed to change user on own school
	 * @param {Object} params - Feathersjs params
	 */
	async prepareParams(_id, params) {
		const currentUserId = params.account.userId.toString();
		const { schoolId } = await getCurrentUserInfo(currentUserId);

		return {
			query: {
				_id,
				roles: this.role._id,
				schoolId: schoolId.toString(),
			},
		};
	}

	/**
	 * request user and compare the email address.
	 * if possible it should be solved via unique index on database
	 * @param {string} email
	 * @param {string} userId
	 */
	async checkMail(email, userId) {
		if (email) {
			const user = await this.app.service('usersModel').find({ query: { email: email.toLowerCase() } });
			if (userId && user.total === 1 && equalIds(user.data[0]._id, userId)) return;
			if (user.total !== 0) {
				throw new BadRequest('Email already exists.');
			}
		}
	}

	/**
	 * Update the account email if eamil will be changed on user.
	 * IMPORTANT: Keep in mind to do a roleback if saving the user failed
	 * @param {*} email
	 * @param {*} userId
	 */
	async updateAccount(email, userId) {
		if (email) {
			email = email.toLowerCase();
			await this.app.service('accountModel').patch(
				null,
				{ username: email },
				{
					query: {
						userId,
						username: { $ne: email },
						systemId: { $exists: false },
					},
				}
			);
		}
	}

	async prepareRoleback(email, userId, fu) {
		try {
			return await fu();
		} catch (err) {
			if (email) {
				const { email: oldMail } = await this.app.service('usersModel').get(userId);
				await this.app.service('accountModel').patch(
					null,
					{ username: oldMail },
					{
						query: {
							userId,
							systemId: { $exists: false },
						},
					}
				);
			}
			throw err;
		}
	}

	async remove(id, params) {
		const { _ids } = params.query;
		const currentUser = await getCurrentUserInfo(params.account.userId);

		if (id) {
			const userToRemove = await getCurrentUserInfo(id);
			if (!equalIds(currentUser.schoolId, userToRemove.schoolId)) {
				throw new Forbidden('You cannot remove users from other schools.');
			}
			await this.app.service('accountModel').remove(null, { query: { userId: id } });
			return this.app.service('usersModel').remove(id);
		}

		const usersIds = await Promise.all(_ids.map((userId) => getCurrentUserInfo(userId)));
		if (usersIds.some((user) => !equalIds(currentUser.schoolId, user.schoolId))) {
			throw new Forbidden('You cannot remove users from other schools.');
		}

		await this.app.service('accountModel').remove(null, { query: { userId: { $in: _ids } } });
		return this.app.service('usersModel').remove(null, { query: { _id: { $in: _ids } } });
	}

	async setup(app) {
		this.app = app;
		this.role = (
			await this.app.service('roles').find({
				query: { name: this.roleName },
			})
		).data[0];
	}
}

const formatBirthdayOfUsers = ({ result: { data: users } }) => {
	users.forEach((user) => {
		if (user.birthday) user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		if (user.birthday) {
			user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		}
	});
};

const adminHookGenerator = (kind) => ({
	before: {
		all: [authenticate('jwt')],
		find: [hasSchoolPermission(`${kind}_LIST`)],
		get: [hasSchoolPermission(`${kind}_LIST`)],
		create: [hasSchoolPermission(`${kind}_CREATE`), blockDisposableEmail('email')],
		update: [hasSchoolPermission(`${kind}_EDIT`), blockDisposableEmail('email')],
		patch: [hasSchoolPermission(`${kind}_EDIT`), blockDisposableEmail('email')],
		remove: [hasSchoolPermission(`${kind}_DELETE`), parseRequestQuery, validateParams],
	},
	after: {
		find: [formatBirthdayOfUsers],
		create: [sendRegistrationLink],
	},
});

module.exports = {
	AdminUsers,
	adminHookGenerator,
};
<|endoftext|>


<commit_before>var INSTALL_SCOPE = 'https://www.googleapis.com/auth/drive.install',
    FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file',
    OPENID_SCOPE = 'openid';

import Ember from 'ember';
import loader from 'ember-gdrive/lib/loader';

var merge = function(a, b) {
  return Ember.merge(a || {}, b || {});
};

var Auth = Ember.Object.extend();

Auth.reopenClass({
  isAuthenticated: false,
  isUnauthenticated: Ember.computed.not('isAuthenticated'),

  user: null,
  userID: Ember.computed.alias('user.id'),

  clientID: ENV.GOOGLE_CLIENT_ID,
  permissions: [INSTALL_SCOPE, FILE_SCOPE, OPENID_SCOPE],

  authorize: function(options) {
    var finalOptions = merge({
      client_id: this.clientID,
      scope: this.permissions,
      authuser: -1,
      immediate: false
    }, options || {});

    Ember.assert('GOOGLE_CLIENT_ID was not set', finalOptions.client_id);

    return new Ember.RSVP.Promise(function(resolve, reject) {
      console.log('authorize', finalOptions);
      loader.load().then(function () {
        gapi.auth.authorize(finalOptions, function (result) {
          if (result && !result.error) {
            Ember.run(null, resolve, result);
          } else {
            Ember.run(null, reject, result && result.error ? result.error : 'unauthenticated');
          }
        });
      });
    }, 'ember-gdrive: Auth#authorize');
  },

  authorizeImmediate: function(options) {
    return this.authorize(merge({
      immediate: true
    }, options));
  },

  fetchCurrentUser: function() {
    return new Ember.RSVP.Promise(function(resolve, reject) {
      gapi.client.oauth2.userinfo.get().execute(function(user) {
        if (user.id) {
          Ember.run(null, resolve, user);
        }
        else {
          Ember.run(null, reject);
        }
      });
    }, 'GoogleDriveAuth _fetchUserObject');
  },

  close: function(){
    return new Ember.RSVP.Promise(function(resolve){
      gapi.auth.signOut();
      resolve();
    });
  }

});

export default Auth;
<commit_msg>Eliminated reliance on global ENV object<commit_after>var INSTALL_SCOPE = 'https://www.googleapis.com/auth/drive.install',
    FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file',
    OPENID_SCOPE = 'openid';

import Ember from 'ember';
import loader from 'ember-gdrive/lib/loader';

var merge = function(a, b) {
  return Ember.merge(a || {}, b || {});
};

var Auth = Ember.Object.extend({
  isAuthenticated: false,
  isUnauthenticated: Ember.computed.not('isAuthenticated'),

  user: null,
  userID: Ember.computed.alias('user.id'),
  permissions: [INSTALL_SCOPE, FILE_SCOPE, OPENID_SCOPE],

  authorize: function(options) {
    var finalOptions = merge({
      scope: this.permissions,
      authuser: -1,
      immediate: false
    }, options || {});

    Ember.assert('GOOGLE_CLIENT_ID was not set', finalOptions.client_id);

    return new Ember.RSVP.Promise(function(resolve, reject) {
      console.log('authorize', finalOptions);
      loader.load().then(function () {
        gapi.auth.authorize(finalOptions, function (result) {
          if (result && !result.error) {
            Ember.run(null, resolve, result);
          } else {
            Ember.run(null, reject, result && result.error ? result.error : 'unauthenticated');
          }
        });
      });
    }, 'ember-gdrive: Auth#authorize');
  },

  authorizeImmediate: function(options) {
    return this.authorize(merge({
      immediate: true
    }, options));
  },

  fetchCurrentUser: function() {
    return new Ember.RSVP.Promise(function(resolve, reject) {
      gapi.client.oauth2.userinfo.get().execute(function(user) {
        if (user.id) {
          Ember.run(null, resolve, user);
        }
        else {
          Ember.run(null, reject);
        }
      });
    }, 'GoogleDriveAuth _fetchUserObject');
  },

  close: function(){
    return new Ember.RSVP.Promise(function(resolve){
      gapi.auth.signOut();
      resolve();
    });
  }

});

export default Auth;
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

// ************************************************************************************************
// Constants

const CLASS_ID = Components.ID("{9589DC0D-9709-4578-883E-D393452B3611}");
const CLASS_NAME = "Firebug Annotation Service";
const CONTRACT_ID = "@joehewitt.com/firebug-annotation-service;1";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

const dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);

// ************************************************************************************************
// Annotaion service implementation

var FBTrace = null;

/**
 * @class Represents an internal Firebug annotation service. This service is used to
 * annotate sites with an info whether Firebug should be activated for them or not.
 */
function AnnotationService()
{
    this.wrappedJSObject = this;

    FBTrace = Cc["@joehewitt.com/firebug-trace-service;1"]
       .getService(Ci.nsISupports).wrappedJSObject.getTracer("extensions.firebug");

    // Get annotation file stored within the profile directory.
    this.file = dirService.get("ProfD", Ci.nsIFile);
    this.file.append("firebug");
    this.file.append("annotations.json");

    // Load annotaions.
    this.initialize();
}

AnnotationService.prototype =
{
    annotations: [],

    setPageAnnotation: function(uri, value)
    {
        this.annotations[uri.spec] = value;
    },

    getPageAnnotation: function(uri)
    {
        return this.annotations[uri.spec];
    },

    pageHasAnnotation: function(uri)
    {
        return this.annotations[uri.spec] ? true : false;
    },

    removePageAnnotation: function(uri)
    {
        delete this.annotations[uri.spec];
    },

    getAnnotations: function()
    {
        return this.annotations;
    },

    clear: function()
    {
        this.annotations = [];
    },

    // Persistence
    initialize: function()
    {
        try
        {
            this.clear();

            if (!this.file.exists())
            {
                this.file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
                if (FBTrace.DBG_ANNOTATIONS)
                    FBTrace.sysout("AnnotationService.initialize; Annotaions file created " +
                        this.file.path);
                return;
            }

            var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                .createInstance(Ci.nsIFileInputStream);
            var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
                .createInstance(Ci.nsIConverterInputStream);

            // Initialize input stream.
            inputStream.init(this.file, 0x01 | 0x08, 0666, 0); // read, create
            cstream.init(inputStream, "UTF-8", 0, 0);

            // Load annotations.
            var data = {};
            cstream.readString(-1, data);
            if (!data.value.length)
                return;

            // Convert to map for faster lookup.
            var arr = eval(data.value);
            if (!arr)
                return;

            for (var i=0; i<arr.length; i++)
                this.annotations[arr[i].uri] = arr[i].value;

            if (FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; Annotations loaded from " +
                    this.file.path);
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; EXCEPTION", err);
        }
    },

    flush: function()
    {
        try
        {
            // Initialize output stream.
            var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
                .createInstance(Ci.nsIFileOutputStream);
            outputStream.init(this.file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate

            // Convert data to JSON.
            var arr = [];
            for (var uri in this.annotations)
                arr.push("{" + "'uri': '" + uri + "', 'value': '" + this.annotations[uri]+ "'}");
            var jsonString = "[\n" + arr.join(",\n") + "\n]\n";

            // Store annotations.
            outputStream.write(jsonString, jsonString.length);
            outputStream.close();

            if (FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; Annotations stored to " +
                    this.file.path, jsonString);
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.flush; EXCEPTION", err);
        }
    },

    /* nsISupports */
    QueryInterface: function(iid)
    {
        if (iid.equals(Ci.nsISupports))
            return this;

        throw Cr.NS_ERROR_NO_INTERFACE;
    }
};

// ************************************************************************************************
// Service factory

var gServiceSingleton = null;
var AnnotationsFactory =
{
    createInstance: function (outer, iid)
    {
        if (outer != null)
            throw Cr.NS_ERROR_NO_AGGREGATION;

        if (iid.equals(Ci.nsISupports))
        {
            if (!gServiceSingleton)
                gServiceSingleton = new AnnotationService();
            return gServiceSingleton.QueryInterface(iid);
        }

        throw Cr.NS_ERROR_NO_INTERFACE;
    },

    QueryInterface: function(iid)
    {
        if (iid.equals(Ci.nsISupports) ||
            iid.equals(Ci.nsISupportsWeakReference) ||
            iid.equals(Ci.nsIFactory))
            return this;

        throw Cr.NS_ERROR_NO_INTERFACE;
    }
};

// ************************************************************************************************
// Module implementation

var AnnotationsModule =
{
    registerSelf: function (compMgr, fileSpec, location, type)
    {
        compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
        compMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME,
            CONTRACT_ID, fileSpec, location, type);
    },

    unregisterSelf: function(compMgr, fileSpec, location)
    {
        compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
        compMgr.unregisterFactoryLocation(CLASS_ID, location);
    },

    getClassObject: function (compMgr, cid, iid)
    {
        if (!iid.equals(Ci.nsIFactory))
            throw Cr.NS_ERROR_NOT_IMPLEMENTED;

        if (cid.equals(CLASS_ID))
            return AnnotationsFactory;

        throw Cr.NS_ERROR_NO_INTERFACE;
    },

    canUnload: function(compMgr)
    {
        return true;
    }
};

// ************************************************************************************************

function NSGetModule(compMgr, fileSpec)
{
    return AnnotationsModule;
}
<commit_msg>Use native JSON for annotations.<commit_after>/* See license.txt for terms of usage */

// ************************************************************************************************
// Constants

const CLASS_ID = Components.ID("{9589DC0D-9709-4578-883E-D393452B3611}");
const CLASS_NAME = "Firebug Annotation Service";
const CONTRACT_ID = "@joehewitt.com/firebug-annotation-service;1";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

const dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);

// ************************************************************************************************
// Annotaion service implementation

var FBTrace = null;

/**
 * @class Represents an internal Firebug annotation service. This service is used to
 * annotate sites with an info whether Firebug should be activated for them or not.
 */
function AnnotationService()
{
    this.wrappedJSObject = this;

    FBTrace = Cc["@joehewitt.com/firebug-trace-service;1"]
       .getService(Ci.nsISupports).wrappedJSObject.getTracer("extensions.firebug");

    // Get annotation file stored within the profile directory.
    this.file = dirService.get("ProfD", Ci.nsIFile);
    this.file.append("firebug");
    this.file.append("annotations.json");

    // Load annotaions.
    this.initialize();
}

AnnotationService.prototype =
{
    annotations: [],

    setPageAnnotation: function(uri, value)
    {
        this.annotations[uri.spec] = value;
    },

    getPageAnnotation: function(uri)
    {
        return this.annotations[uri.spec];
    },

    pageHasAnnotation: function(uri)
    {
        return this.annotations[uri.spec] ? true : false;
    },

    removePageAnnotation: function(uri)
    {
        delete this.annotations[uri.spec];
    },

    getAnnotations: function()
    {
        return this.annotations;
    },

    clear: function()
    {
        this.annotations = [];
    },

    // Persistence
    initialize: function()
    {
        try
        {
            this.clear();

            if (!this.file.exists())
            {
                this.file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
                if (FBTrace.DBG_ANNOTATIONS)
                    FBTrace.sysout("AnnotationService.initialize; Annotaions file created " +
                        this.file.path);
                return;
            }

            var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                .createInstance(Ci.nsIFileInputStream);
            var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
                .createInstance(Ci.nsIConverterInputStream);

            // Initialize input stream.
            inputStream.init(this.file, 0x01 | 0x08, 0666, 0); // read, create
            cstream.init(inputStream, "UTF-8", 0, 0);

            // Load annotations.
            var data = {};
            cstream.readString(-1, data);
            if (!data.value.length)
                return;

            var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
            var arr = nativeJSON.decode(data.value);
            if (!arr)
                return;

            // Convert to map for faster lookup.
            for (var i=0; i<arr.length; i++)
                this.annotations[arr[i].uri] = arr[i].value;

            if (FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; Annotations loaded from " +
                    this.file.path, arr);
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; EXCEPTION", err);
        }
    },

    flush: function()
    {
        try
        {
            // Initialize output stream.
            var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
                .createInstance(Ci.nsIFileOutputStream);
            outputStream.init(this.file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate

            // Convert data to JSON.
            var arr = [];
            for (var uri in this.annotations)
                arr.push({
                    uri: uri,
                    value: this.annotations[uri]
                });

            var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
            var jsonString = nativeJSON.encode(arr);

            // Store annotations.
            outputStream.write(jsonString, jsonString.length);
            outputStream.close();

            if (FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.initialize; Annotations stored to " +
                    this.file.path, jsonString);
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS || FBTrace.DBG_ANNOTATIONS)
                FBTrace.sysout("AnnotationService.flush; EXCEPTION", err);
        }
    },

    /* nsISupports */
    QueryInterface: function(iid)
    {
        if (iid.equals(Ci.nsISupports))
            return this;

        throw Cr.NS_ERROR_NO_INTERFACE;
    }
};

// ************************************************************************************************
// Service factory

var gServiceSingleton = null;
var AnnotationsFactory =
{
    createInstance: function (outer, iid)
    {
        if (outer != null)
            throw Cr.NS_ERROR_NO_AGGREGATION;

        if (iid.equals(Ci.nsISupports))
        {
            if (!gServiceSingleton)
                gServiceSingleton = new AnnotationService();
            return gServiceSingleton.QueryInterface(iid);
        }

        throw Cr.NS_ERROR_NO_INTERFACE;
    },

    QueryInterface: function(iid)
    {
        if (iid.equals(Ci.nsISupports) ||
            iid.equals(Ci.nsISupportsWeakReference) ||
            iid.equals(Ci.nsIFactory))
            return this;

        throw Cr.NS_ERROR_NO_INTERFACE;
    }
};

// ************************************************************************************************
// Module implementation

var AnnotationsModule =
{
    registerSelf: function (compMgr, fileSpec, location, type)
    {
        compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
        compMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME,
            CONTRACT_ID, fileSpec, location, type);
    },

    unregisterSelf: function(compMgr, fileSpec, location)
    {
        compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
        compMgr.unregisterFactoryLocation(CLASS_ID, location);
    },

    getClassObject: function (compMgr, cid, iid)
    {
        if (!iid.equals(Ci.nsIFactory))
            throw Cr.NS_ERROR_NOT_IMPLEMENTED;

        if (cid.equals(CLASS_ID))
            return AnnotationsFactory;

        throw Cr.NS_ERROR_NO_INTERFACE;
    },

    canUnload: function(compMgr)
    {
        return true;
    }
};

// ************************************************************************************************

function NSGetModule(compMgr, fileSpec)
{
    return AnnotationsModule;
}
<|endoftext|>


<commit_before>var charting = {
  loadGoals: function() {
    var url = "http://localhost:3000/goals.json";
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        if (request.status == 200) {
          charting.removeGoalMenuItems();

          var goalsString = request.responseText;
          var goalsJSON = JSON.parse(goalsString);
          var goalsOut = [];
          for (var i in goalsJSON) {
            goalsOut[i] = goalsJSON[i].goal.name;
          }
          charting.goals = goalsOut;

          charting.addGoalMenuItems();
        }
        else {
          alert("Couldn't load notes: " + request.status);
        }
      }
    }

    request.send(null);
  },

  addGoalMenuItems: function() {
    var context_menu = document.getElementById("contentAreaContextMenu");
    var charting_end = document.getElementById("charting-end");
    for (goal in charting.goals) {
      var menu_item = createTagMenuItem(goal, charting.goals[goal]);
      context_menu.insertBefore(menu_item, charting_end);
    }
  },

  removeGoalMenuItems: function() {
    for (i in this.goals) {
      var menuItem = document.getElementById('goal-menu-item' + i);
      menuItem.parentNode.removeChild(menuItem);
    }
  },

  onLoad: function() {
    // initialization code
    this.initialized = true;
    this.strings = document.getElementById("charting-strings");
  },

  note: function(goalIndex) {
    var selection = escape(content.window.getSelection());
    var source_url = escape(content.window.location.toString());
    var source_title = escape(content.document.title);

    alert(selection + "\n" + source_url + "\n" + source_title);

    var params= "note[body]=" + selection +
                "&note[goal]=" + charting.goals[goalIndex] +
                "&source[location]=" + source_url +
                "&source[title]=" + source_title +
                "&source[doctype]=webpage";

    var request = new XMLHttpRequest();
    request.open("POST", "http://localhost:3000/notes.json", true);
    request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    request.setRequestHeader("Content-length", params.length);

    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        if (request.status == 201) {
          alert("Created note for " + charting.goals[goalIndex]);
        }
        else {
          alert("Failed to create note.");
        }
      }
    }

    request.send(params);
  },

  onToolbarOptionsButtonCommand: function(e) {
    alert("Options!");
  },

  onToolbarNoteButtonCommand: function(e) {
    alert("Note!");
  }
};

window.addEventListener("load", charting.onLoad, false);
<commit_msg>Removed alert showing selection and url and stuff.<commit_after>var charting = {
  loadGoals: function() {
    var url = "http://localhost:3000/goals.json";
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        if (request.status == 200) {
          charting.removeGoalMenuItems();

          var goalsString = request.responseText;
          var goalsJSON = JSON.parse(goalsString);
          var goalsOut = [];
          for (var i in goalsJSON) {
            goalsOut[i] = goalsJSON[i].goal.name;
          }
          charting.goals = goalsOut;

          charting.addGoalMenuItems();
        }
        else {
          alert("Couldn't load notes: " + request.status);
        }
      }
    }

    request.send(null);
  },

  addGoalMenuItems: function() {
    var context_menu = document.getElementById("contentAreaContextMenu");
    var charting_end = document.getElementById("charting-end");
    for (goal in charting.goals) {
      var menu_item = createTagMenuItem(goal, charting.goals[goal]);
      context_menu.insertBefore(menu_item, charting_end);
    }
  },

  removeGoalMenuItems: function() {
    for (i in this.goals) {
      var menuItem = document.getElementById('goal-menu-item' + i);
      menuItem.parentNode.removeChild(menuItem);
    }
  },

  onLoad: function() {
    // initialization code
    this.initialized = true;
    this.strings = document.getElementById("charting-strings");
  },

  note: function(goalIndex) {
    var selection = escape(content.window.getSelection());
    var source_url = escape(content.window.location.toString());
    var source_title = escape(content.document.title);

    var params= "note[body]=" + selection +
                "&note[goal]=" + charting.goals[goalIndex] +
                "&source[location]=" + source_url +
                "&source[title]=" + source_title +
                "&source[doctype]=webpage";

    var request = new XMLHttpRequest();
    request.open("POST", "http://localhost:3000/notes.json", true);
    request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    request.setRequestHeader("Content-length", params.length);

    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        if (request.status == 201) {
          alert("Created note for " + charting.goals[goalIndex]);
        }
        else {
          alert("Failed to create note.");
        }
      }
    }

    request.send(params);
  },

  onToolbarOptionsButtonCommand: function(e) {
    alert("Options!");
  },

  onToolbarNoteButtonCommand: function(e) {
    alert("Note!");
  }
};

window.addEventListener("load", charting.onLoad, false);
<|endoftext|>


<commit_before>
$(document).ready(function () {
    $(".auto").click(function () {
        var msg = "";
        msg+= "자동로그인을 사용하시면 다음부터 회원아이디와 패스워드를 입력하실 필요가 없습니다.\n\n";
        msg+= "공공장소에서는 개인정보가 유출될 수 있으니 사용을 자제하여 주십시오.\n\n";
        msg+= "자동로그인을 사용하시겠습니까?";

        if (!$("#auto_login").val() && confirm(msg)) {
            $("#auto_login").val("1");
            $(this).addClass("checked");
        }
        else {
            $("#auto_login").val("");
            $(this).removeClass("checked");
        }
    });

    $("form[name=foutlogin]").submit(function () {
        if (!$("input[name=mb_id]").val()) {
            alert("회원아이디를 입력하십시오.");
            $("input[name=mb_id]").focus();
            return false;
        }
        if (!$("input[name=mb_password]").val()) {
            alert("패스워드를 입력하십시오.");
            $("input[name=mb_password]").focus();
            return false;
        }
        return true;
    });
});

<commit_msg>새글 페이지에서 아웃로그인 오작동 문제 해결 (물깊은님 제안)<commit_after>
$(document).ready(function () {
    $(".auto").click(function () {
        var msg = "";
        msg+= "자동로그인을 사용하시면 다음부터 회원아이디와 패스워드를 입력하실 필요가 없습니다.\n\n";
        msg+= "공공장소에서는 개인정보가 유출될 수 있으니 사용을 자제하여 주십시오.\n\n";
        msg+= "자동로그인을 사용하시겠습니까?";

        if (!$("#auto_login").val() && confirm(msg)) {
            $("#auto_login").val("1");
            $(this).addClass("checked");
        }
        else {
            $("#auto_login").val("");
            $(this).removeClass("checked");
        }
    });

    $("form[name=foutlogin]").submit(function () {
        if (!$(this).find("input[name=mb_id]").val()) {
            alert("회원아이디를 입력하십시오.");
            $(this).find("input[name=mb_id]").focus();
            return false;
        }
        if (!$(this).find("input[name=mb_password]").val()) {
            alert("패스워드를 입력하십시오.");
            $(this).find("input[name=mb_password]").focus();
            return false;
        }
        return true;
    });
});

<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */
var BaseSoundManager = require('../BaseSoundManager');
var Class = require('../../utils/Class');
var EventEmitter = require('eventemitter3');
var NoAudioSound = require('./NoAudioSound');
var NOOP = require('../../utils/NOOP');

/**
 * @classdesc
 * No audio implementation of the sound manager. It is used if audio has been
 * disabled in the game config or the device doesn't support any audio.
 *
 * It represents a graceful degradation of sound manager logic that provides
 * minimal functionality and prevents Phaser projects that use audio from
 * breaking on devices that don't support any audio playback technologies.
 *
 * @class NoAudioSoundManager
 * @extends Phaser.Sound.BaseSoundManager
 * @memberOf Phaser.Sound
 * @constructor
 * @author Pavle Goloskokovic <pgoloskokovic@gmail.com> (http://prunegames.com)
 * @since 3.0.0
 *
 * @param {Phaser.Game} game - Reference to the current game instance.
 */
var NoAudioSoundManager = new Class({
    Extends: EventEmitter,
    initialize: function NoAudioSoundManager (game)
    {
        EventEmitter.call(this);
        this.game = game;
        this.sounds = [];
        this.mute = false;
        this.volume = 1;
        this.rate = 1;
        this.detune = 0;
        this.pauseOnBlur = true;
        this.locked = false;
    },
    add: function (key, config)
    {
        var sound = new NoAudioSound(this, key, config);
        this.sounds.push(sound);
        return sound;
    },
    addAudioSprite: function (key, config)
    {
        var sound = this.add(key, config);
        sound.spritemap = {};
        return sound;
    },
    // eslint-disable-next-line no-unused-vars
    play: function (key, extra)
    {
        return false;
    },
    // eslint-disable-next-line no-unused-vars
    playAudioSprite: function (key, spriteName, config)
    {
        return false;
    },
    remove: function (sound)
    {
        return BaseSoundManager.prototype.remove.call(this, sound);
    },
    removeByKey: function (key)
    {
        return BaseSoundManager.prototype.removeByKey.call(this, key);
    },
    pauseAll: NOOP,
    resumeAll: NOOP,
    stopAll: NOOP,
    update: NOOP,
    destroy: function ()
    {
        BaseSoundManager.prototype.destroy.call(this);
    },
    forEachActiveSound: function (callbackfn, scope)
    {
        BaseSoundManager.prototype.forEachActiveSound.call(this, callbackfn, scope);
    }
});
module.exports = NoAudioSoundManager;
<commit_msg>Added new methods to NoAudio<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Pavle Goloskokovic <pgoloskokovic@gmail.com> (http://prunegames.com)
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

var BaseSoundManager = require('../BaseSoundManager');
var Class = require('../../utils/Class');
var EventEmitter = require('eventemitter3');
var NoAudioSound = require('./NoAudioSound');
var NOOP = require('../../utils/NOOP');

/**
 * @classdesc
 * No audio implementation of the sound manager. It is used if audio has been
 * disabled in the game config or the device doesn't support any audio.
 *
 * It represents a graceful degradation of sound manager logic that provides
 * minimal functionality and prevents Phaser projects that use audio from
 * breaking on devices that don't support any audio playback technologies.
 *
 * @class NoAudioSoundManager
 * @extends Phaser.Sound.BaseSoundManager
 * @memberOf Phaser.Sound
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Game} game - Reference to the current game instance.
 */
var NoAudioSoundManager = new Class({

    Extends: EventEmitter,

    initialize:

    function NoAudioSoundManager (game)
    {
        EventEmitter.call(this);

        this.game = game;
        this.sounds = [];
        this.mute = false;
        this.volume = 1;
        this.rate = 1;
        this.detune = 0;
        this.pauseOnBlur = true;
        this.locked = false;
    },

    add: function (key, config)
    {
        var sound = new NoAudioSound(this, key, config);

        this.sounds.push(sound);

        return sound;
    },

    addAudioSprite: function (key, config)
    {
        var sound = this.add(key, config);

        sound.spritemap = {};

        return sound;
    },

    // eslint-disable-next-line no-unused-vars
    play: function (key, extra)
    {
        return false;
    },

    // eslint-disable-next-line no-unused-vars
    playAudioSprite: function (key, spriteName, config)
    {
        return false;
    },

    remove: function (sound)
    {
        return BaseSoundManager.prototype.remove.call(this, sound);
    },

    removeByKey: function (key)
    {
        return BaseSoundManager.prototype.removeByKey.call(this, key);
    },

    pauseAll: NOOP,
    resumeAll: NOOP,
    stopAll: NOOP,
    update: NOOP,
    setRate: NOOP,
    setDetune: NOOP,
    setMute: NOOP,
    setVolume: NOOP,

    forEachActiveSound: function (callbackfn, scope)
    {
        BaseSoundManager.prototype.forEachActiveSound.call(this, callbackfn, scope);
    },

    destroy: function ()
    {
        BaseSoundManager.prototype.destroy.call(this);
    }

});

module.exports = NoAudioSoundManager;
<|endoftext|>