<commit_before>/* @flow */
import React, { Component } from "react"
import { Element as ReactElement } from "react"

import {
  ContextTypes,
  ProxyContextTypes,
} from "./ReactHigherEventTypes"
import type {
  Context,
  ProxyContext,
  EventProps,
} from "./ReactHigherEventTypes"

class ReactHigherEventContainer extends Component<void, Props, State> {
  handleEvent: (eventType: string, event: SyntheticEvent) => void;
  subscribe: (eventType: string, handler: Handler) => Function;
  proxySubscribe: (eventType: string, handler: ProxyHandler) => Function;
  events: Map<string, Set<Function>> = new Map();
  proxySubscribers: Set<(eventProps: EventProps) => void> = new Set();
  state: State = {};
  constructor(props: Props) {
    super(props)
    this.subscribe = this.subscribe.bind(this)
    this.handleEvent = this.handleEvent.bind(this)
    this.proxySubscribe = this.proxySubscribe.bind(this)
  }
  componentWillUpdate(nextProps: Props, nextState: State) {
    if(this.state !== nextState) {
      this.proxySubscribers.forEach(
        (subscriber) => subscriber(nextState)
      )
    }
  }
  subscribe(eventType: string, handler: Handler): Function {
    let eventSubscribers = this.events.get(eventType)
    if(!eventSubscribers) {
      eventSubscribers = new Set()
      this.events.set(eventType, eventSubscribers)
      this.updateEventProp({ eventType, create: true })
    }
    eventSubscribers.add(handler)
    return () => {
      const eventSubscribers = this.events.get(eventType)
      if(eventSubscribers) {
        eventSubscribers.delete(handler)
        if(eventSubscribers.size === 0) {
          this.events.delete(eventType)
          this.updateEventProp({ eventType, create: false })
        }
      }
    }
  }
  proxySubscribe(handler: ProxyHandler): Function {
    this.proxySubscribers.add(handler)
    handler(this.state)
    return () => {
      this.proxySubscribers.delete(handler)
    }
  }
  updateEventProp({ eventType, create }: { eventType: string, create: boolean }): void {
    this.setState((state: State) => {
      if(!!state[eventType] === create) {
        return state;
      }
      return {
        ...state,
        [eventType]: create ? this.handleEvent.bind(null, eventType) : null,
      }
    })
  }
  handleEvent(eventType: string, event: SyntheticEvent) {
    if(!this.events.has(eventType)) {
      return
    }
    const subscribers = this.events.get(eventType)
    if(subscribers) {
      subscribers.forEach((func) => func(event))
    }
  }
  getChildContext(): Context & ProxyContext {
    return {
      higherEvent: {
        subscribe: this.subscribe,
      },
      higherEventProxy: {
        subscribe: this.proxySubscribe,
      }
    }
  }
  render(): ReactElement {
    const { children, component, ...props } = this.props
    const Component = component || "div"
    return (
      <Component {...props} {...this.state}>
        {children}
      </Component>
    )
  }
}

ReactHigherEventContainer.childContextTypes = {
  ...ContextTypes,
  ...ProxyContextTypes,
}

type Props = {
  children?: any,
  component?: Function,
}
type State = EventProps
type Handler = (event: SyntheticEvent) => void
type ProxyHandler = (eventProps: EventProps) => void

export default ReactHigherEventContainer
<commit_msg>Avoid triggering multiple times for same event<commit_after>/* @flow */
import React, { Component } from "react"
import { Element as ReactElement } from "react"

import {
  ContextTypes,
  ProxyContextTypes,
} from "./ReactHigherEventTypes"
import type {
  Context,
  ProxyContext,
  EventProps,
} from "./ReactHigherEventTypes"

class ReactHigherEventContainer extends Component<void, Props, State> {
  handleEvent: (eventType: string, event: SyntheticEvent) => void;
  subscribe: (eventType: string, handler: Handler) => Function;
  proxySubscribe: (eventType: string, handler: ProxyHandler) => Function;
  events: Map<string, Set<Function>> = new Map();
  proxySubscribers: Set<(eventProps: EventProps) => void> = new Set();
  state: State = {};
  constructor(props: Props) {
    super(props)
    this.subscribe = this.subscribe.bind(this)
    this.handleEvent = this.handleEvent.bind(this)
    this.proxySubscribe = this.proxySubscribe.bind(this)
  }
  componentWillUpdate(nextProps: Props, nextState: State) {
    if(this.state !== nextState) {
      this.proxySubscribers.forEach(
        (subscriber) => subscriber(nextState)
      )
    }
  }
  subscribe(eventType: string, handler: Handler): Function {
    let eventSubscribers = this.events.get(eventType)
    if(!eventSubscribers) {
      eventSubscribers = new Set()
      this.events.set(eventType, eventSubscribers)
      this.updateEventProp({ eventType, create: true })
    }
    eventSubscribers.add(handler)
    return () => {
      const eventSubscribers = this.events.get(eventType)
      if(eventSubscribers) {
        eventSubscribers.delete(handler)
        if(eventSubscribers.size === 0) {
          this.events.delete(eventType)
          this.updateEventProp({ eventType, create: false })
        }
      }
    }
  }
  proxySubscribe(handler: ProxyHandler): Function {
    this.proxySubscribers.add(handler)
    handler(this.state)
    return () => {
      this.proxySubscribers.delete(handler)
    }
  }
  updateEventProp({ eventType, create }: { eventType: string, create: boolean }): void {
    this.setState((state: State) => {
      if(!!state[eventType] === create) {
        return state;
      }
      return {
        ...state,
        [eventType]: create ? this.handleEvent.bind(null, eventType) : null,
      }
    })
  }
  lastNativeEvent: ?Event = null
  handleEvent(eventType: string, event: SyntheticEvent) {
    if(!this.events.has(eventType) || event.nativeEvent === this.lastNativeEvent) {
      return
    }
    this.lastNativeEvent = event.nativeEvent
    const subscribers = this.events.get(eventType)
    if(subscribers) {
      subscribers.forEach((func) => func(event))
    }
  }
  getChildContext(): Context & ProxyContext {
    return {
      higherEvent: {
        subscribe: this.subscribe,
      },
      higherEventProxy: {
        subscribe: this.proxySubscribe,
      }
    }
  }
  render(): ReactElement {
    const { children, component, ...props } = this.props
    const Component = component || "div"
    return (
      <Component {...props} {...this.state}>
        {children}
      </Component>
    )
  }
}

ReactHigherEventContainer.childContextTypes = {
  ...ContextTypes,
  ...ProxyContextTypes,
}

type Props = {
  children?: any,
  component?: Function,
}
type State = EventProps
type Handler = (event: SyntheticEvent) => void
type ProxyHandler = (eventProps: EventProps) => void

export default ReactHigherEventContainer
<|endoftext|>


<commit_before>import React from "react";
import styled, { keyframes } from "styled-components";
import PropTypes from "prop-types";

import Page from "../components/page";

const GitHubIcon = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="20"
      height="20"
      fill="currentColor"
      viewBox="0 0 16 16"
    >
      <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
    </svg>
  );
};

const Code = ({ commits }) => {
  return (
    <Page title="Code" description="Some of my most recent coding projects.">
      <Background />
      <Heading>Code</Heading>
      <Paragraph>
        A probably not entirely up-to-date list of my current side projects...
        There is plenty more to see on{" "}
        <a
          href="https://github.com/overshard"
          rel="noopener noreferrer"
          target="_blank"
        >
          my GitHub account
        </a>{" "}
        and generally around the internet if you are interested.
      </Paragraph>
      <Projects>
        <Project>
          <ProjectHeading>AI-Art</ProjectHeading>
          <ProjectParagraph>
            Art generation using VQGAN + CLIP in docker containers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.aiart.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/ai-art"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Analytics</ProjectHeading>
          <ProjectParagraph>
            A self-hostable analytics service with a straightforward API to
            track events from any source.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.analytics.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/analytics"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Status</ProjectHeading>
          <ProjectParagraph>
            A self-hosted status monitoring service.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.status.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/status"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Blog</ProjectHeading>
          <ProjectParagraph>
            A self-hostable blog built on Wagtail targeted towards developers
            with code blocks, syntax highlighting, live search, great SEO, and a
            clean customizable UI.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.blog.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/blog"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timelite</ProjectHeading>
          <ProjectParagraph>
            A simple time tracking progressive web app. Uses local storage and
            service workers to remain accessible offline. Sometimes you just
            need the essentials when you are busy.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timelite.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timelite"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timestrap</ProjectHeading>
          <ProjectParagraph>
            A full feature time tracking web app. Supports multiple users and
            exporting reports in multiple formats. Makes use of websockets to
            maintain state across clients.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timestrap.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timestrap"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>isaacbythewood.com</ProjectHeading>
          <ProjectParagraph>
            The personal website of Isaac Bythewood. So this site...
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.isaacbythewood.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/isaacbythewood.com"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dockerfiles</ProjectHeading>
          <ProjectParagraph>
            All the Dockerfiles I use for various purposes. More detailed usage
            instructions are at the top of each Dockerfile.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dockerfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dockerfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>alpinefiles</ProjectHeading>
          <ProjectParagraph>
            Some of the files that I use on my Alpine Linux servers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.alpinefiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/alpinefiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dotfiles</ProjectHeading>
          <ProjectParagraph>
            A variety of config files for setting up new systems.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dotfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dotfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
      </Projects>
    </Page>
  );
};

export async function getServerSideProps() {
  // NOTE: We eval the require on purpose so that webpack doesn't bundle it
  const { Sequelize } = eval("require('sequelize')");

  const sequelize = new Sequelize("sqlite://db.sqlite3");

  const Commit = sequelize.define("commit", {
    repo: {
      type: Sequelize.STRING,
      allowNull: false,
    },
    createdAt: {
      type: Sequelize.DATE,
      allowNull: false,
    },
    data: {
      type: Sequelize.JSON,
      allowNull: false,
    },
  });

  const getCommit = async (repo) => {
    const storedCommit = await Commit.findOne({
      where: {
        repo,
        createdAt: {
          [Sequelize.Op.gte]: new Date(Date.now() - 24 * 60 * 60 * 1000),
        },
      },
    });
    if (storedCommit) {
      return storedCommit;
    }
    const commitsFetch = await fetch(
      `https://api.github.com/repos/overshard/${repo}/commits`
    );
    const commits = await commitsFetch.json();
    const commit = commits[0];
    const commitData = {
      repo: repo,
      createdAt: new Date(),
      data: {
        sha: commit.sha,
        commit: {
          message: commit.commit.message,
          date: commit.commit.author.date,
        },
        author: {
          name: commit.commit.author.name,
          email: commit.commit.author.email,
        },
      },
    };

    await Commit.create(commitData);
    return commitData;
  };

  const getCommits = async () => {
    const aiart = await getCommit("ai-art");
    const alpinefiles = await getCommit("alpinefiles");
    const analytics = await getCommit("analytics");
    const blog = await getCommit("blog");
    const dockerfiles = await getCommit("dockerfiles");
    const dotfiles = await getCommit("dotfiles");
    const isaacbythewood = await getCommit("isaacbythewood.com");
    const status = await getCommit("status");
    const timelite = await getCommit("timelite");
    const timestrap = await getCommit("timestrap");

    return {
      aiart: aiart,
      alpinefiles: alpinefiles,
      analytics: analytics,
      blog: blog,
      dockerfiles: dockerfiles,
      dotfiles: dotfiles,
      isaacbythewood: isaacbythewood,
      status: status,
      timelite: timelite,
      timestrap: timestrap,
    };
  };

  await Commit.sync();

  return getCommits().then((commits) => {
    return {
      props: {
        commits: JSON.parse(JSON.stringify(commits)),
      },
    };
  });
}

Code.propTypes = {
  commits: PropTypes.object,
};

export default Code;

const TransformRight = keyframes`
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
`;

const TransformLeft = keyframes`
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
`;

const Background = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: white;
  z-index: -2;
`;

const Heading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2.5em;
  color: black;

  &::before {
    content: "";
    display: block;
    width: 50px;
    height: 5px;
    margin-bottom: 20px;
    background-color: ${(props) => props.theme.colors.blue};
  }
`;

const Paragraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;

  a {
    color: black;
    text-decoration: none;
    position: relative;
    white-space: nowrap;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 1);
      transform-origin: left;
      animation: ${TransformLeft} 300ms normal forwards;
    }

    &::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 0.2);
    }

    &:hover {
      &::before {
        animation: ${TransformRight} 300ms normal forwards;
      }
    }
  }
`;

const Projects = styled.div`
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  gap: 20px;
`;

const Project = styled.div`
  width: calc(50% - 20px);
  display: flex;
  flex-direction: column;

  @media (${(props) => props.theme.breakpoints.mobile}) {
    width: 100%;
  }
`;

const ProjectHeading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2em;
  color: black;
`;

const ProjectParagraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;
  flex-grow: 1;
`;

const ProjectButton = styled.a`
  padding: 10px 15px;
  font-size: 1.2em;
  text-decoration: none;
  background-color: black;
  margin-bottom: 20px;
  font-weight: 700;
  text-transform: uppercase;
  display: inline-block;
  letter-spacing: 2px;
  color: white;
  background-image: linear-gradient(
    to right,
    ${(props) => props.theme.colors.blue} 0,
    ${(props) => props.theme.colors.purple} 100%
  );
  transform: scale(1);
  transition-duration: 250ms;
  transition-property: transform;
  width: 125px;
  text-align: center;
  display: flex;
  align-items: center;

  & svg {
    margin-right: 10px;
  }

  &:hover {
    transform: scale(1.2);
  }
`;

const ProjectCommit = styled.pre`
  font-family: monospace;
  background: black;
  color: #00ff00;
  padding: 20px;
  overflow-x: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
`;
<commit_msg>Add newtab project<commit_after>import React from "react";
import styled, { keyframes } from "styled-components";
import PropTypes from "prop-types";

import Page from "../components/page";

const GitHubIcon = () => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="20"
      height="20"
      fill="currentColor"
      viewBox="0 0 16 16"
    >
      <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
    </svg>
  );
};

const Code = ({ commits }) => {
  return (
    <Page title="Code" description="Some of my most recent coding projects.">
      <Background />
      <Heading>Code</Heading>
      <Paragraph>
        A probably not entirely up-to-date list of my current side projects...
        There is plenty more to see on{" "}
        <a
          href="https://github.com/overshard"
          rel="noopener noreferrer"
          target="_blank"
        >
          my GitHub account
        </a>{" "}
        and generally around the internet if you are interested.
      </Paragraph>
      <Projects>
        <Project>
          <ProjectHeading>AI-Art</ProjectHeading>
          <ProjectParagraph>
            Art generation using VQGAN + CLIP in docker containers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.aiart.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/ai-art"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Analytics</ProjectHeading>
          <ProjectParagraph>
            A self-hostable analytics service with a straightforward API to
            track events from any source.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.analytics.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/analytics"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Status</ProjectHeading>
          <ProjectParagraph>
            A self-hosted status monitoring service.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.status.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/status"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Blog</ProjectHeading>
          <ProjectParagraph>
            A self-hostable blog built on Wagtail targeted towards developers
            with code blocks, syntax highlighting, live search, great SEO, and a
            clean customizable UI.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.blog.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/blog"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timelite</ProjectHeading>
          <ProjectParagraph>
            A simple time tracking progressive web app. Uses local storage and
            service workers to remain accessible offline. Sometimes you just
            need the essentials when you are busy.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timelite.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timelite"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>Timestrap</ProjectHeading>
          <ProjectParagraph>
            A full feature time tracking web app. Supports multiple users and
            exporting reports in multiple formats. Makes use of websockets to
            maintain state across clients.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.timestrap.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/timestrap"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>isaacbythewood.com</ProjectHeading>
          <ProjectParagraph>
            The personal website of Isaac Bythewood. So this site...
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.isaacbythewood.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/isaacbythewood.com"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>New Tab</ProjectHeading>
          <ProjectParagraph>
            A clean new tab page extension for Chrome.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.newtab.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/newtab"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dockerfiles</ProjectHeading>
          <ProjectParagraph>
            All the Dockerfiles I use for various purposes. More detailed usage
            instructions are at the top of each Dockerfile.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dockerfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dockerfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>alpinefiles</ProjectHeading>
          <ProjectParagraph>
            Some of the files that I use on my Alpine Linux servers.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.alpinefiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/alpinefiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
        <Project>
          <ProjectHeading>dotfiles</ProjectHeading>
          <ProjectParagraph>
            A variety of config files for setting up new systems.
          </ProjectParagraph>
          <ProjectCommit>
            {JSON.stringify(commits.dotfiles.data, null, 2)}
          </ProjectCommit>
          <ProjectButton
            href="https://www.github.com/overshard/dotfiles"
            rel="noopener noreferrer"
            target="_blank"
          >
            <GitHubIcon />
            GitHub
          </ProjectButton>
        </Project>
      </Projects>
    </Page>
  );
};

export async function getServerSideProps() {
  // NOTE: We eval the require on purpose so that webpack doesn't bundle it
  const { Sequelize } = eval("require('sequelize')");

  const sequelize = new Sequelize("sqlite://db.sqlite3");

  const Commit = sequelize.define("commit", {
    repo: {
      type: Sequelize.STRING,
      allowNull: false,
    },
    createdAt: {
      type: Sequelize.DATE,
      allowNull: false,
    },
    data: {
      type: Sequelize.JSON,
      allowNull: false,
    },
  });

  const getCommit = async (repo) => {
    const storedCommit = await Commit.findOne({
      where: {
        repo,
        createdAt: {
          [Sequelize.Op.gte]: new Date(Date.now() - 24 * 60 * 60 * 1000),
        },
      },
    });
    if (storedCommit) {
      return storedCommit;
    }
    const commitsFetch = await fetch(
      `https://api.github.com/repos/overshard/${repo}/commits`
    );
    const commits = await commitsFetch.json();
    const commit = commits[0];
    const commitData = {
      repo: repo,
      createdAt: new Date(),
      data: {
        sha: commit.sha,
        commit: {
          message: commit.commit.message,
          date: commit.commit.author.date,
        },
        author: {
          name: commit.commit.author.name,
          email: commit.commit.author.email,
        },
      },
    };

    await Commit.create(commitData);
    return commitData;
  };

  const getCommits = async () => {
    const aiart = await getCommit("ai-art");
    const alpinefiles = await getCommit("alpinefiles");
    const analytics = await getCommit("analytics");
    const blog = await getCommit("blog");
    const dockerfiles = await getCommit("dockerfiles");
    const dotfiles = await getCommit("dotfiles");
    const isaacbythewood = await getCommit("isaacbythewood.com");
    const status = await getCommit("status");
    const timelite = await getCommit("timelite");
    const timestrap = await getCommit("timestrap");
    const newtab = await getCommit("newtab");

    return {
      aiart: aiart,
      alpinefiles: alpinefiles,
      analytics: analytics,
      blog: blog,
      dockerfiles: dockerfiles,
      dotfiles: dotfiles,
      isaacbythewood: isaacbythewood,
      status: status,
      timelite: timelite,
      timestrap: timestrap,
      newtab: newtab,
    };
  };

  await Commit.sync();

  return getCommits().then((commits) => {
    return {
      props: {
        commits: JSON.parse(JSON.stringify(commits)),
      },
    };
  });
}

Code.propTypes = {
  commits: PropTypes.object,
};

export default Code;

const TransformRight = keyframes`
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
`;

const TransformLeft = keyframes`
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
`;

const Background = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: white;
  z-index: -2;
`;

const Heading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2.5em;
  color: black;

  &::before {
    content: "";
    display: block;
    width: 50px;
    height: 5px;
    margin-bottom: 20px;
    background-color: ${(props) => props.theme.colors.blue};
  }
`;

const Paragraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;

  a {
    color: black;
    text-decoration: none;
    position: relative;
    white-space: nowrap;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 1);
      transform-origin: left;
      animation: ${TransformLeft} 300ms normal forwards;
    }

    &::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 2px;
      background: rgba(0, 0, 0, 0.2);
    }

    &:hover {
      &::before {
        animation: ${TransformRight} 300ms normal forwards;
      }
    }
  }
`;

const Projects = styled.div`
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  gap: 20px;
`;

const Project = styled.div`
  width: calc(50% - 20px);
  display: flex;
  flex-direction: column;

  @media (${(props) => props.theme.breakpoints.mobile}) {
    width: 100%;
  }
`;

const ProjectHeading = styled.h1`
  font-weight: 700;
  margin-top: 60px;
  margin-bottom: 20px;
  font-size: 2em;
  color: black;
`;

const ProjectParagraph = styled.p`
  font-size: 1.5em;
  margin-top: 0;
  margin-bottom: 20px;
  font-weight: 300;
  color: black;
  flex-grow: 1;
`;

const ProjectButton = styled.a`
  padding: 10px 15px;
  font-size: 1.2em;
  text-decoration: none;
  background-color: black;
  margin-bottom: 20px;
  font-weight: 700;
  text-transform: uppercase;
  display: inline-block;
  letter-spacing: 2px;
  color: white;
  background-image: linear-gradient(
    to right,
    ${(props) => props.theme.colors.blue} 0,
    ${(props) => props.theme.colors.purple} 100%
  );
  transform: scale(1);
  transition-duration: 250ms;
  transition-property: transform;
  width: 125px;
  text-align: center;
  display: flex;
  align-items: center;

  & svg {
    margin-right: 10px;
  }

  &:hover {
    transform: scale(1.2);
  }
`;

const ProjectCommit = styled.pre`
  font-family: monospace;
  background: black;
  color: #00ff00;
  padding: 20px;
  overflow-x: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
`;
<|endoftext|>


<commit_before>//general app config
import {shortTitle, fullTitle} from '../configs/general';
//list of actions
import loadContent from '../actions/loadContent';
import loadContributors from '../actions/loadContributors';
import loadSearchResults from '../actions/search/loadSearchResults';
import loadAdvancedSearchResults from '../actions/search/updateUserResultsVisibility';
import loadDeck from '../actions/loadDeck';
import loadSlideView from '../actions/slide/loadSlideView';
import loadSlideEdit from '../actions/slide/loadSlideEdit';
import loadDeckView from '../actions/loadDeckView';
import loadDeckEdit from '../actions/loadDeckEdit';
import loadDataSources from '../actions/datasource/loadDataSources';
import loadActivities from '../actions/activityfeed/loadActivities';
import loadUserNotifications from '../actions/user/loadUserNotifications';
import loadDeckTree from '../actions/decktree/loadDeckTree';
import loadTranslations from '../actions/loadTranslations';
import loadContentHistory from '../actions/loadContentHistory';
import loadContentUsage from '../actions/loadContentUsage';
import loadContentQuestions from '../actions/loadContentQuestions';
import loadContentDiscussion from '../actions/activityfeed/contentdiscussion/loadContentDiscussion';
import loadSimilarContents from '../actions/loadSimilarContents';
import loadImportFile from '../actions/loadImportFile';
import loadPresentation from '../actions/loadPresentation';
import loadRouteNotFound from '../actions/loadRouteNotFound';

export default {
    //-----------------------------------HomePage routes------------------------------
    home: {
        path: '/',
        method: 'get',
        page: 'home',
        title: 'SlideWiki -- Home',
        handler: require('../components/Home/Home'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: fullTitle
            });
            done();
        }
    },
    about: {
        path: '/about',
        method: 'get',
        page: 'about',
        title: 'SlideWiki -- About',
        handler: require('../components/Home/About'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | About'
            });
            done();
        }
    },
    import: {
        path: '/import',
        method: 'get',
        page: 'import',
        title: 'SlideWiki -- Import presentation',
        handler: require('../components/Import/Import'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | Import presentation'
            });
            done();
        }
    },
    addDeck: {
        path: '/addDeck',
        method: 'get',
        page: 'addDeck',
        title: 'SlideWiki -- Add Deck',
        handler: require('../components/AddDeck/AddDeck'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | Add Deck'
            });
            done();
        }
    },
    notifications: {
        path: '/notifications',
        method: 'get',
        page: 'notifications',
        title: 'SlideWiki -- User notifications',
        handler: require('../components/User/UserNotificationsPanel/UserNotificationsPanel'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | User notifications'
            });
            done();
        }
    },

//-----------------------------------Search routes------------------------------
    searchresults: {
        path: '/search/:searchstatus/:searchstring?/:entity?/:searchlang?',
        method: 'get',
        page: 'search',
        title: 'SlideWiki -- Search',
        handler: require('../components/Search/SearchResultsPanel/SearchPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSearchResults, payload, done);
        }
    },

    //-----------------------------------DeckPage routes------------------------------
    // selector {id: 'id of parent deck', stype: 'type of selected content e.g. slide, deck or question', sid: 'id of selected content', spath: 'path of the content in deck tree, separated by semi-colon and colon for its position e.g. 67:3;45:1;45:4', mode: 'interaction mode e.g. view or edit'}
    deck: {
        path: '/deck/:id/:stype?/:sid?/:spath?/:mode?',
        method: 'get',
        page: 'deck',
        handler: require('../components/Deck/Deck'),
        action: (context, payload, done) => {
            context.executeAction(loadDeck, payload, done);
        }
    },
    contributors: {
        path: '/contributors/:stype/:sid',
        method: 'get',
        page: 'contributors',
        handler: require('../components/Deck/ContentModulesPanel/ContributorsPanel/ContributorsPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContributors, payload, done);
        }
    },
    similarcontent: {
        path: '/similarcontent/:stype/:sid',
        method: 'get',
        page: 'similarcontent',
        handler: require('../components/Deck/SimilarContentPanel/SimilarContentPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSimilarContents, payload, done);
        }
    },
    content: {
        path: '/content/:stype/:sid/:mode?',
        method: 'get',
        page: 'content',
        handler: require('../components/Deck/ContentPanel/ContentPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContent, payload, done);
        }
    },
    slideview: {
        path: '/slideview/:sid',
        method: 'get',
        page: 'slideview',
        handler: require('../components/Deck/ContentPanel/SlideModes/SlideViewPanel/SlideViewPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSlideView, payload, done);
        }
    },
    slideedit: {
        path: '/slideedit/:sid',
        method: 'get',
        page: 'slideedit',
        handler: require('../components/Deck/ContentPanel/SlideModes/SlideEditPanel/SlideEditPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSlideEdit, payload, done);
        }
    },
    deckview: {
        path: '/deckview/:sid',
        method: 'get',
        page: 'deckview',
        handler: require('../components/Deck/ContentPanel/DeckModes/DeckViewPanel/DeckViewPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckView, payload, done);
        }
    },
    deckedit: {
        path: '/deckedit/:sid',
        method: 'get',
        page: 'deckedit',
        handler: require('../components/Deck/ContentPanel/DeckModes/DeckEditPanel/DeckEditPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckEdit, payload, done);
        }
    },
    datasource: {
        path: '/datasource/:stype/:sid',
        method: 'get',
        page: 'datasources',
        handler: require('../components/Deck/ContentModulesPanel/DataSourcePanel/DataSourcePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDataSources, payload, done);
        }
    },
    activities: {
        path: '/activities/:stype/:sid',
        method: 'get',
        page: 'activities',
        handler: require('../components/Deck/ActivityFeedPanel/ActivityFeedPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadActivities, payload, done);
        }
    },
    translations: {
        path: '/translations/:stype/:sid',
        method: 'get',
        page: 'translations',
        handler: require('../components/Deck/TranslationPanel/TranslationPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadTranslations, payload, done);
        }
    },
    history: {
        path: '/history/:stype/:sid',
        method: 'get',
        page: 'history',
        handler: require('../components/Deck/ContentModulesPanel/ContentHistoryPanel/ContentHistoryPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentHistory, payload, done);
        }
    },
    usage: {
        path: '/usage/:stype/:sid',
        method: 'get',
        page: 'usage',
        handler: require('../components/Deck/ContentModulesPanel/ContentUsagePanel/ContentUsagePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentUsage, payload, done);
        }
    },
    questions: {
        path: '/questions/:stype/:sid',
        method: 'get',
        page: 'questions',
        handler: require('../components/Deck/ContentModulesPanel/ContentQuestionsPanel/ContentQuestionsPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentQuestions, payload, done);
        }
    },
    discussion: {
        path: '/discussion/:stype/:sid',
        method: 'get',
        page: 'discussion',
        handler: require('../components/Deck/ContentModulesPanel/ContentDiscussionPanel/ContentDiscussionPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentDiscussion, payload, done);
        }
    },
    decktree: {
        path: '/decktree/:id/:spath?',
        method: 'get',
        page: 'decktree',
        handler: require('../components/Deck/TreePanel/TreePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckTree, payload, done);
        }
    },
    presentation: {
        path: '/presentation/:id/',
        method: 'get',
        page: 'presentation',
        handler: require('../components/Deck/Presentation/Presentation'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckTree, payload, done);
            //context.executeAction(loadPresentation, payload, done);
            context.executeAction(loadDeck, payload, done);
        }
    },
    importfile: {
        path: '/importfile',
        method: 'post',
        page: 'importfile',
        handler: require('../actions/loadImportFile'),
        action: (context, payload, done) => {
            context.executeAction(loadImportFile, payload, done);
            //context.executeAction(loadPresentation, payload, done);
            //context.executeAction(loadDeck, payload, done);
        }
    },
    notfound: {
        path: '*',
        method: 'get',
        handler: require('../components/RouteNotFound/RouteNotFound'),
        action: (context, payload, done) => {
            context.executeAction(loadRouteNotFound, payload, done);
        }
    }
};
<commit_msg>added a TODO comment<commit_after>//general app config
import {shortTitle, fullTitle} from '../configs/general';
//list of actions
import loadContent from '../actions/loadContent';
import loadContributors from '../actions/loadContributors';
import loadSearchResults from '../actions/search/loadSearchResults';
import loadAdvancedSearchResults from '../actions/search/updateUserResultsVisibility';
import loadDeck from '../actions/loadDeck';
import loadSlideView from '../actions/slide/loadSlideView';
import loadSlideEdit from '../actions/slide/loadSlideEdit';
import loadDeckView from '../actions/loadDeckView';
import loadDeckEdit from '../actions/loadDeckEdit';
import loadDataSources from '../actions/datasource/loadDataSources';
import loadActivities from '../actions/activityfeed/loadActivities';
import loadUserNotifications from '../actions/user/loadUserNotifications';
import loadDeckTree from '../actions/decktree/loadDeckTree';
import loadTranslations from '../actions/loadTranslations';
import loadContentHistory from '../actions/loadContentHistory';
import loadContentUsage from '../actions/loadContentUsage';
import loadContentQuestions from '../actions/loadContentQuestions';
import loadContentDiscussion from '../actions/activityfeed/contentdiscussion/loadContentDiscussion';
import loadSimilarContents from '../actions/loadSimilarContents';
import loadImportFile from '../actions/loadImportFile';
import loadPresentation from '../actions/loadPresentation';
import loadRouteNotFound from '../actions/loadRouteNotFound';

export default {
    //-----------------------------------HomePage routes------------------------------
    home: {
        path: '/',
        method: 'get',
        page: 'home',
        title: 'SlideWiki -- Home',
        handler: require('../components/Home/Home'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: fullTitle
            });
            done();
        }
    },
    about: {
        path: '/about',
        method: 'get',
        page: 'about',
        title: 'SlideWiki -- About',
        handler: require('../components/Home/About'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | About'
            });
            done();
        }
    },
    import: {
        path: '/import',
        method: 'get',
        page: 'import',
        title: 'SlideWiki -- Import presentation',
        handler: require('../components/Import/Import'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | Import presentation'
            });
            done();
        }
    },
    //TODO: add an initial loader for this page
    addDeck: {
        path: '/addDeck',
        method: 'get',
        page: 'addDeck',
        title: 'SlideWiki -- Add Deck',
        handler: require('../components/AddDeck/AddDeck'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | Add Deck'
            });
            done();
        }
    },
    notifications: {
        path: '/notifications',
        method: 'get',
        page: 'notifications',
        title: 'SlideWiki -- User notifications',
        handler: require('../components/User/UserNotificationsPanel/UserNotificationsPanel'),
        action: (context, payload, done) => {
            context.dispatch('UPDATE_PAGE_TITLE', {
                pageTitle: shortTitle + ' | User notifications'
            });
            done();
        }
    },

//-----------------------------------Search routes------------------------------
    searchresults: {
        path: '/search/:searchstatus/:searchstring?/:entity?/:searchlang?',
        method: 'get',
        page: 'search',
        title: 'SlideWiki -- Search',
        handler: require('../components/Search/SearchResultsPanel/SearchPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSearchResults, payload, done);
        }
    },

    //-----------------------------------DeckPage routes------------------------------
    // selector {id: 'id of parent deck', stype: 'type of selected content e.g. slide, deck or question', sid: 'id of selected content', spath: 'path of the content in deck tree, separated by semi-colon and colon for its position e.g. 67:3;45:1;45:4', mode: 'interaction mode e.g. view or edit'}
    deck: {
        path: '/deck/:id/:stype?/:sid?/:spath?/:mode?',
        method: 'get',
        page: 'deck',
        handler: require('../components/Deck/Deck'),
        action: (context, payload, done) => {
            context.executeAction(loadDeck, payload, done);
        }
    },
    contributors: {
        path: '/contributors/:stype/:sid',
        method: 'get',
        page: 'contributors',
        handler: require('../components/Deck/ContentModulesPanel/ContributorsPanel/ContributorsPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContributors, payload, done);
        }
    },
    similarcontent: {
        path: '/similarcontent/:stype/:sid',
        method: 'get',
        page: 'similarcontent',
        handler: require('../components/Deck/SimilarContentPanel/SimilarContentPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSimilarContents, payload, done);
        }
    },
    content: {
        path: '/content/:stype/:sid/:mode?',
        method: 'get',
        page: 'content',
        handler: require('../components/Deck/ContentPanel/ContentPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContent, payload, done);
        }
    },
    slideview: {
        path: '/slideview/:sid',
        method: 'get',
        page: 'slideview',
        handler: require('../components/Deck/ContentPanel/SlideModes/SlideViewPanel/SlideViewPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSlideView, payload, done);
        }
    },
    slideedit: {
        path: '/slideedit/:sid',
        method: 'get',
        page: 'slideedit',
        handler: require('../components/Deck/ContentPanel/SlideModes/SlideEditPanel/SlideEditPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadSlideEdit, payload, done);
        }
    },
    deckview: {
        path: '/deckview/:sid',
        method: 'get',
        page: 'deckview',
        handler: require('../components/Deck/ContentPanel/DeckModes/DeckViewPanel/DeckViewPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckView, payload, done);
        }
    },
    deckedit: {
        path: '/deckedit/:sid',
        method: 'get',
        page: 'deckedit',
        handler: require('../components/Deck/ContentPanel/DeckModes/DeckEditPanel/DeckEditPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckEdit, payload, done);
        }
    },
    datasource: {
        path: '/datasource/:stype/:sid',
        method: 'get',
        page: 'datasources',
        handler: require('../components/Deck/ContentModulesPanel/DataSourcePanel/DataSourcePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDataSources, payload, done);
        }
    },
    activities: {
        path: '/activities/:stype/:sid',
        method: 'get',
        page: 'activities',
        handler: require('../components/Deck/ActivityFeedPanel/ActivityFeedPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadActivities, payload, done);
        }
    },
    translations: {
        path: '/translations/:stype/:sid',
        method: 'get',
        page: 'translations',
        handler: require('../components/Deck/TranslationPanel/TranslationPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadTranslations, payload, done);
        }
    },
    history: {
        path: '/history/:stype/:sid',
        method: 'get',
        page: 'history',
        handler: require('../components/Deck/ContentModulesPanel/ContentHistoryPanel/ContentHistoryPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentHistory, payload, done);
        }
    },
    usage: {
        path: '/usage/:stype/:sid',
        method: 'get',
        page: 'usage',
        handler: require('../components/Deck/ContentModulesPanel/ContentUsagePanel/ContentUsagePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentUsage, payload, done);
        }
    },
    questions: {
        path: '/questions/:stype/:sid',
        method: 'get',
        page: 'questions',
        handler: require('../components/Deck/ContentModulesPanel/ContentQuestionsPanel/ContentQuestionsPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentQuestions, payload, done);
        }
    },
    discussion: {
        path: '/discussion/:stype/:sid',
        method: 'get',
        page: 'discussion',
        handler: require('../components/Deck/ContentModulesPanel/ContentDiscussionPanel/ContentDiscussionPanel'),
        action: (context, payload, done) => {
            context.executeAction(loadContentDiscussion, payload, done);
        }
    },
    decktree: {
        path: '/decktree/:id/:spath?',
        method: 'get',
        page: 'decktree',
        handler: require('../components/Deck/TreePanel/TreePanel'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckTree, payload, done);
        }
    },
    presentation: {
        path: '/presentation/:id/',
        method: 'get',
        page: 'presentation',
        handler: require('../components/Deck/Presentation/Presentation'),
        action: (context, payload, done) => {
            context.executeAction(loadDeckTree, payload, done);
            //context.executeAction(loadPresentation, payload, done);
            context.executeAction(loadDeck, payload, done);
        }
    },
    importfile: {
        path: '/importfile',
        method: 'post',
        page: 'importfile',
        handler: require('../actions/loadImportFile'),
        action: (context, payload, done) => {
            context.executeAction(loadImportFile, payload, done);
            //context.executeAction(loadPresentation, payload, done);
            //context.executeAction(loadDeck, payload, done);
        }
    },
    notfound: {
        path: '*',
        method: 'get',
        handler: require('../components/RouteNotFound/RouteNotFound'),
        action: (context, payload, done) => {
            context.executeAction(loadRouteNotFound, payload, done);
        }
    }
};
<|endoftext|>


<commit_before>module.exports = function (destination) {
  var i, source, key;

  for (i = 1; i < arguments.length; i++) {
    source = arguments[i];
    for (key in source) {
      if (source.hasOwnProperty(key)) {
        destination[key] = source[key];
      }
    }
  }

  return destination;
};
<commit_msg>assign should use Object.assign<commit_after>module.exports = Object.assign || function (destination) {
  var i, source, key;

  for (i = 1; i < arguments.length; i++) {
    source = arguments[i];
    for (key in source) {
      if (source.hasOwnProperty(key)) {
        destination[key] = source[key];
      }
    }
  }

  return destination;
};
<|endoftext|>


<commit_before>// @flow

import React, { Component } from 'react';
import withRedux from 'next-redux-wrapper';
import Error from 'next/error';
import { createStore } from '../store';
import { loadGame } from '../actions';
import { addCurUserToState, getGame } from '../utils/api';
import { SocketProvider } from '../utils/socket/Provider';
import Layout from '../components/Layout';
import GameContainer from '../components/GameContainer';
import FlatrisGame from '../components/FlatrisGame';

type Props = {
  gameExists: boolean
};

class JoinPage extends Component<Props> {
  static async getInitialProps({ req, query, store }) {
    if (req) {
      await addCurUserToState(req, store);
    }

    try {
      const game = await getGame(query.g);
      store.dispatch(loadGame(game));

      return {
        gameExists: true
      };
    } catch (err) {
      return { gameExists: false };
    }
  }

  render() {
    const { gameExists } = this.props;
    if (!gameExists) {
      // TODO: Identify and signal 500s differently
      return <Error statusCode={404} />;
    }

    return (
      <Layout>
        <SocketProvider>
          <GameContainer>
            <FlatrisGame />
          </GameContainer>
        </SocketProvider>
      </Layout>
    );
  }
}

export default withRedux(createStore)(JoinPage);
<commit_msg>Include status code to 404 pages<commit_after>// @flow

import React, { Component } from 'react';
import withRedux from 'next-redux-wrapper';
import Error from 'next/error';
import { createStore } from '../store';
import { loadGame } from '../actions';
import { addCurUserToState, getGame } from '../utils/api';
import { SocketProvider } from '../utils/socket/Provider';
import Layout from '../components/Layout';
import GameContainer from '../components/GameContainer';
import FlatrisGame from '../components/FlatrisGame';

type Props = {
  statusCode: false | number
};

class JoinPage extends Component<Props> {
  static async getInitialProps({ req, res, query, store }) {
    if (req) {
      await addCurUserToState(req, store);
    }

    try {
      const game = await getGame(query.g);
      store.dispatch(loadGame(game));

      return {
        statusCode: false
      };
    } catch (err) {
      // TODO: Identify and signal 500s differently
      const statusCode = 404;

      // Server side rendered pages will return this status code, whereas
      // client side rendered pages will just render the appropriate message
      if (res) {
        res.statusCode = statusCode;
      }

      return { statusCode };
    }
  }

  render() {
    const { statusCode } = this.props;
    if (statusCode) {
      return <Error statusCode={404} />;
    }

    return (
      <Layout>
        <SocketProvider>
          <GameContainer>
            <FlatrisGame />
          </GameContainer>
        </SocketProvider>
      </Layout>
    );
  }
}

export default withRedux(createStore)(JoinPage);
<|endoftext|>


<commit_before>/**
 * @fileOverview Tab Related Confirmation Library for Firefox 3.5 or later
 * @author       SHIMODA "Piro" Hiroshi
 * @version      7
 * Basic usage:
 *
 * @example
 *   Components.utils.import('resource://my-modules/confirmWithTab.js');
 *
 *   var checkbox = {
 *         label   : 'Never ask me',
 *         checked : false
 *       };
 *
 *   confirmWithTab({
 *     tab      : gBrowser.selectedTab,           // the related tab
 *     label    : 'Ara you ready?',               // the message
 *     value    : 'treestyletab-undo-close-tree', // the internal key (optional)
 *     buttons  : ['Yes', 'No'],                  // button labels
 *     checkbox : checkbox,                       // checkbox definition (optional)
 *     cancelEvents : ['SSTabRestoring']          // events to cancel this deferred (optional)
 *   })
 *   .next(function(aButtonIndex) {
 *     // the next callback receives the index of the clicked button.
 *     switch (aButtonIndex) {
 *       case 0: // Yes
 *         ...
 *         break;
 *       case 1: // No
 *         ...
 *         break;
 *     }
 *     // after the notification bar is closed, "checked" indicates
 *     // the state of the checkbox when the user clicks a button.
 *     var checked = checkbox.checked;
 *     ...
 *   })
 *   .error(function(aError) {
 *     // if the tab is closed, or any event in the cancelEvents array
 *     // is fired, then an exception is raised.
 *     ...
 *   });
 *
 * @license
 *   The MIT License, Copyright (c) 2010-2011 SHIMODA "Piro" Hiroshi
 *   http://github.com/piroor/fxaddonlibs/blob/master/license.txt
 * @url http://github.com/piroor/fxaddonlibs/blob/master/confirmWithTab.js
 * @url http://github.com/piroor/fxaddonlibs
 */

if (typeof window == 'undefined')
	this.EXPORTED_SYMBOLS = ['confirmWithTab'];

// var namespace;
if (typeof namespace == 'undefined') {
	// If namespace.jsm is available, export symbols to the shared namespace.
	// See: http://github.com/piroor/fxaddonlibs/blob/master/namespace.jsm
	try {
		let ns = {};
		Components.utils.import('resource://my-modules/namespace.jsm', ns);
		namespace = ns.getNamespaceFor('piro.sakura.ne.jp');
	}
	catch(e) {
		namespace = (typeof window != 'undefined' ? window : null ) || {};
	}
}

// This depends on JSDeferred.
// See: https://github.com/cho45/jsdeferred
if (typeof namespace.Deferred == 'undefined')
	Components.utils.import('resource://my-modules/jsdeferred.js', namespace);

var confirmWithTab;
(function() {
	const currentRevision = 7;

	var loadedRevision = 'confirmWithTab' in namespace ?
			namespace.confirmWithTab.revision :
			0 ;
	if (loadedRevision && loadedRevision > currentRevision) {
		confirmWithTab = namespace.confirmWithTab;
		return;
	}

	const Ci = Components.interfaces;

	confirmWithTab = function confirmWithTab(aOptions)
	{
		var deferred = new namespace.Deferred();
		if (!aOptions.buttons) {
			return deferred
					.next(function() {
						throw new Error('confirmWithTab requires one or more buttons!');
					});
		}

		var buttonClicked = false;
		var checkbox;
		namespace.Deferred.next(function() {
			aOptions.cancelEvents = (aOptions.cancelEvents || [])
										.concat(['TabClose'])
										.sort()
										.join('\n')
										.replace(/^(.+)$\n(\1\n)+/gm, '$1\n')
										.split('\n');

			var tab = aOptions.tab;
			var b = getTabBrowserFromChild(tab);
			var box = b.getNotificationBox(tab.linkedBrowser);
			var accessKeys = [];
			var numericAccessKey = 0;
			var notification = box.appendNotification(
					aOptions.label,
					aOptions.value || 'confirmWithTab-'+encodeURIComponent(aOptions.label),
					aOptions.image || null,
					(aOptions.priority ?
						(typeof aOptions.priority == 'number' ? aOptions.priority : box[aOptions.priority] ) :
						box.PRIORITY_INFO_MEDIUM
					),
					aOptions.buttons.map(function(aLabel, aIndex) {
						var accessKey;
						var match = aLabel.match(/\s*\(&([0-9a-z])\)/i);
						if (match) {
							accessKey = match[1];
							aLabel = aLabel.replace(match[0], '');
						}
						else {
							let lastUniqueKey;
							let sanitizedLabel = [];
							for (let i = 0, maxi = aLabel.length; i < maxi; i++)
							{
								let possibleAccessKey = aLabel.charAt(i);
								if (possibleAccessKey == '&' && i < maxi-1) {
									possibleAccessKey = aLabel.charAt(i+1);
									if (possibleAccessKey != '&') {
										accessKey = possibleAccessKey;
									}
									i++;
								}
								else if (!lastUniqueKey &&
										accessKeys.indexOf(possibleAccessKey) < 0) {
									lastUniqueKey = possibleAccessKey;
								}
								sanitizedLabel.push(possibleAccessKey);
							}
							if (!accessKey)
								accessKey = lastUniqueKey;
							if (!accessKey || !/[0-9a-z]/i.test(accessKey))
								accessKey = ++numericAccessKey;

							aLabel = sanitizedLabel.join('');
						}

						accessKeys.push(accessKey);

						return {
							label     : aLabel,
							accessKey : accessKey,
							callback  : function() {
								buttonClicked = true;
								deferred.call(aIndex);
							}
						};
					})
				);

			if (aOptions.checkbox) {
				checkbox = notification.ownerDocument.createElement('checkbox');
				checkbox.setAttribute('label', aOptions.checkbox.label);
				if (aOptions.checkbox.checked)
					checkbox.setAttribute('checked', 'true');

				let container = notification.ownerDocument.createElement('hbox');
				container.setAttribute('align', 'center');
				container.appendChild(checkbox);

				notification.appendChild(container);
			}

			if (aOptions.persistence)
				notification.persistence = aOptions.persistence;

			var strip = b.tabContainer || b.mTabContainer;
			var handleEvent = function handleEvent(aEvent) {
					if (aEvent.type == 'DOMNodeRemoved' && aEvent.target != notification)
						return;
					if (aOptions.cancelEvents)
						aOptions.cancelEvents.forEach(function(aEventType) {
							strip.removeEventListener(aEventType, handleEvent, false);
						});
					if (notification.parentNode)
						notification.parentNode.removeEventListener('DOMNodeRemoved', handleEvent, false);
					if (aEvent.type != 'DOMNodeRemoved')
						notification.close();
					if (!buttonClicked)
						deferred.fail(aEvent);
				};
			if (aOptions.cancelEvents)
				aOptions.cancelEvents.forEach(function(aEventType) {
					strip.addEventListener(aEventType, handleEvent, false);
				});
			notification.parentNode.addEventListener('DOMNodeRemoved', handleEvent, false);
		});

		return deferred
				.next(function(aButtonIndex) {
					buttonClicked = true;
					if (aOptions.checkbox)
						aOptions.checkbox.checked = checkbox.checked;
					return aButtonIndex;
				});
	};

	function getTabBrowserFromChild(aTabBrowserChild)
	{
		var b = aTabBrowserChild.ownerDocument.evaluate(
				'ancestor::*[local-name()="tabbrowser"] | '+
				'ancestor::*[local-name()="tabs"][@tabbrowser] |'+
				'ancestor::*[local-name()="toolbar"]/descendant::*[local-name()="tabs"]',
				aTabBrowserChild,
				null,
				Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,
				null
			).singleNodeValue;
		return (b && b.tabbrowser) || b;
	}

	namespace.confirmWithTab = confirmWithTab;
})();
<commit_msg>confirmWithTab can work without JSDeferred<commit_after>/**
 * @fileOverview Tab Related Confirmation Library for Firefox 3.5 or later
 * @author       SHIMODA "Piro" Hiroshi
 * @version      8
 * Basic usage:
 *
 * @example
 *   Components.utils.import('resource://my-modules/confirmWithTab.js');
 *
 *   var checkbox = {
 *         label   : 'Never ask me',
 *         checked : false
 *       };
 *
 *   confirmWithTab({
 *     tab      : gBrowser.selectedTab,           // the related tab
 *     label    : 'Ara you ready?',               // the message
 *     value    : 'treestyletab-undo-close-tree', // the internal key (optional)
 *     buttons  : ['Yes', 'No'],                  // button labels
 *     checkbox : checkbox,                       // checkbox definition (optional)
 *     cancelEvents : ['SSTabRestoring']          // events to cancel this deferred (optional)
 *   })
 *   .next(function(aButtonIndex) {
 *     // the next callback receives the index of the clicked button.
 *     switch (aButtonIndex) {
 *       case 0: // Yes
 *         ...
 *         break;
 *       case 1: // No
 *         ...
 *         break;
 *     }
 *     // after the notification bar is closed, "checked" indicates
 *     // the state of the checkbox when the user clicks a button.
 *     var checked = checkbox.checked;
 *     ...
 *   })
 *   .error(function(aError) {
 *     // if the tab is closed, or any event in the cancelEvents array
 *     // is fired, then an exception is raised.
 *     ...
 *   });
 *
 * @license
 *   The MIT License, Copyright (c) 2010-2011 SHIMODA "Piro" Hiroshi
 *   http://github.com/piroor/fxaddonlibs/blob/master/license.txt
 * @url http://github.com/piroor/fxaddonlibs/blob/master/confirmWithTab.js
 * @url http://github.com/piroor/fxaddonlibs
 */

if (typeof window == 'undefined')
	this.EXPORTED_SYMBOLS = ['confirmWithTab'];

// var namespace;
if (typeof namespace == 'undefined') {
	// If namespace.jsm is available, export symbols to the shared namespace.
	// See: http://github.com/piroor/fxaddonlibs/blob/master/namespace.jsm
	try {
		let ns = {};
		Components.utils.import('resource://my-modules/namespace.jsm', ns);
		namespace = ns.getNamespaceFor('piro.sakura.ne.jp');
	}
	catch(e) {
		namespace = (typeof window != 'undefined' ? window : null ) || {};
	}
}

// This can be extended by JSDeferred.
// See: https://github.com/cho45/jsdeferred
try {
	if (typeof namespace.Deferred == 'undefined')
		Components.utils.import('resource://my-modules/jsdeferred.js', namespace);
}
catch(e) {
}

var confirmWithTab;
(function() {
	const currentRevision = 8;

	var loadedRevision = 'confirmWithTab' in namespace ?
			namespace.confirmWithTab.revision :
			0 ;
	if (loadedRevision && loadedRevision > currentRevision) {
		confirmWithTab = namespace.confirmWithTab;
		return;
	}

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

	function next(aCallback) {
		Cc['@mozilla.org/timer;1']
			.createInstance(Ci.nsITimer)
			.init(aCallback, 0, Ci.nsITimer.TYPE_ONE_SHOT);
	}

	function normalizeOptions(aOptions) {
		// we should accept single button type popup
		if (!aOptions.buttons && aOptions.button)
			aOptions.buttons = [aOptions.button];
		return aOptions;
	}

	confirmWithTab = function confirmWithTab(aOptions)
	{
		var options = normalizeOptions(aOptions);
		var deferred = namespace.Deferred ?
						new namespace.Deferred() :
						{ // fake deferred
							next : next,
							call : function(aValue) { options.callback && options.callback.call(aOptions, aValue); },
							fail : function(aError) { options.onerror && options.onerror.call(aOptions, aError); }
						};

		if (!options.buttons) {
			return deferred
					.next(function() {
						throw new Error('confirmWithTab requires any button!');
					});
		}

		var done = false;
		var checkbox;
		var postProcess = function () {
			done = true;
			if (options.checkbox)
				options.checkbox.checked = checkbox.checked;
		};
		var showBar = function() {
			options.cancelEvents = (options.cancelEvents || [])
										.concat(['TabClose'])
										.sort()
										.join('\n')
										.replace(/^(.+)$\n(\1\n)+/gm, '$1\n')
										.split('\n');

			var tab = options.tab;
			var b = getTabBrowserFromChild(tab);
			var box = b.getNotificationBox(tab.linkedBrowser);
			var accessKeys = [];
			var numericAccessKey = 0;
			var notification = box.appendNotification(
					options.label,
					options.value || 'confirmWithTab-'+encodeURIComponent(options.label),
					options.image || null,
					(options.priority ?
						(typeof options.priority == 'number' ? options.priority : box[options.priority] ) :
						box.PRIORITY_INFO_MEDIUM
					),
					options.buttons.map(function(aLabel, aIndex) {
						var accessKey;
						var match = aLabel.match(/\s*\(&([0-9a-z])\)/i);
						if (match) {
							accessKey = match[1];
							aLabel = aLabel.replace(match[0], '');
						}
						else {
							let lastUniqueKey;
							let sanitizedLabel = [];
							for (let i = 0, maxi = aLabel.length; i < maxi; i++)
							{
								let possibleAccessKey = aLabel.charAt(i);
								if (possibleAccessKey == '&' && i < maxi-1) {
									possibleAccessKey = aLabel.charAt(i+1);
									if (possibleAccessKey != '&') {
										accessKey = possibleAccessKey;
									}
									i++;
								}
								else if (!lastUniqueKey &&
										accessKeys.indexOf(possibleAccessKey) < 0) {
									lastUniqueKey = possibleAccessKey;
								}
								sanitizedLabel.push(possibleAccessKey);
							}
							if (!accessKey)
								accessKey = lastUniqueKey;
							if (!accessKey || !/[0-9a-z]/i.test(accessKey))
								accessKey = ++numericAccessKey;

							aLabel = sanitizedLabel.join('');
						}

						accessKeys.push(accessKey);

						return {
							label     : aLabel,
							accessKey : accessKey,
							callback  : function() {
								done = true;
								try {
									deferred.call(aIndex);
								}
								finally {
									postProcess();
								}
							}
						};
					})
				);

			if (options.checkbox) {
				checkbox = notification.ownerDocument.createElement('checkbox');
				checkbox.setAttribute('label', options.checkbox.label);
				if (options.checkbox.checked)
					checkbox.setAttribute('checked', 'true');

				let container = notification.ownerDocument.createElement('hbox');
				container.setAttribute('align', 'center');
				container.appendChild(checkbox);

				notification.appendChild(container);
			}

			if (options.persistence)
				notification.persistence = options.persistence;

			var strip = b.tabContainer || b.mTabContainer;
			var handleEvent = function handleEvent(aEvent) {
					if (aEvent.type == 'DOMNodeRemoved' && aEvent.target != notification)
						return;
					if (options.cancelEvents)
						options.cancelEvents.forEach(function(aEventType) {
							strip.removeEventListener(aEventType, handleEvent, false);
						});
					if (notification.parentNode)
						notification.parentNode.removeEventListener('DOMNodeRemoved', handleEvent, false);
					try {
						if (aEvent.type != 'DOMNodeRemoved')
							notification.close();
						if (!done)
							deferred.fail(aEvent);
					}
					finally {
						postProcess();
					}
				};
			if (options.cancelEvents)
				options.cancelEvents.forEach(function(aEventType) {
					strip.addEventListener(aEventType, handleEvent, false);
				});
			notification.parentNode.addEventListener('DOMNodeRemoved', handleEvent, false);
		};

		if (namespace.Deferred) {
			namespace.Deferred.next(showBar);
			return deferred;
		}
		else {
			next(showBar);

			let originalCall = deferred.call;
			deferred.call = function(aButtonIndex) {
				try {
					originalCall(aButtonIndex);
				}
				finally {
					postProcess();
				}
			};
			let originalFail = deferred.fail;
			deferred.fail = function(aError) {
				try {
					originalFail(aError);
				}
				finally {
					postProcess();
				}
			};
		}
	};

	function getTabBrowserFromChild(aTabBrowserChild)
	{
		var b = aTabBrowserChild.ownerDocument.evaluate(
				'ancestor::*[local-name()="tabbrowser"] | '+
				'ancestor::*[local-name()="tabs"][@tabbrowser] |'+
				'ancestor::*[local-name()="toolbar"]/descendant::*[local-name()="tabs"]',
				aTabBrowserChild,
				null,
				Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,
				null
			).singleNodeValue;
		return (b && b.tabbrowser) || b;
	}

	namespace.confirmWithTab = confirmWithTab;
})();
<|endoftext|>


<commit_before>(function () { "use strict";

var _ = require('underscore');
var lzma = require('lzma-native');
var util = require('util');
var assert = require('assert');
var buscomponent = require('./buscomponent.js');
var Access = require('./access.js').Access;

function ConnectionData(socket) {
	this.lzmaSupport = false;
	this.user = null;
	this.hsheaders = _.omit(socket.handshake.headers, ['authorization', 'proxy-authorization']);
	this.remoteip = this.hsheaders['x-forwarded-for'] || this.hsheaders['x-real-ip'] || '127.0.0.182';
	this.cdid = new Date().getTime() + '-' + this.remoteip + '-' + ConnectionData.uniqueCount++;
	this.access = new Access();
	this.pushEventsTimer = null;
	this.lastInfoPush = 0;
	this.mostRecentEventTime = 0;
	this.socket = socket;
	this.isShuttingDown = false;
	this.unansweredCount = 0;
	this.pushesServerStatistics = false;

	this.queryCount = 0;
	this.queryLZMACount = 0;
	this.queryLZMAUsedCount = 0;

	this.query_ = _.bind(this.query, this);
	this.disconnected_ = _.bind(this.disconnected, this);

	socket.on('query', this.query_);
	socket.on('disconnect', this.disconnected_);
}
util.inherits(ConnectionData, buscomponent.BusComponent);

ConnectionData.prototype.onBusConnect = function() {
	this.getServerConfig(function(cfg) {
		this.push({type: 'server-config', 'config': _.pick(cfg, cfg.clientconfig)});
	});
};

ConnectionData.prototype.stats = function() {
	return {
		lzma: this.lzmaSupport,
		user: this.user ? { name: this.user.name, uid: this.user.uid } : null,
		lastInfoPush: this.lastInfoPush,
		mostRecentEventTime: this.mostRecentEventTime,
		queryCount: this.queryCount,
		queryLZMACount: this.queryLZMACount,
		queryLZMAUsedCount: this.queryLZMAUsedCount,
		ip: this.remoteip,
		xff: this.hsheaders['x-forwarded-for'],
		xrip: this.hsheaders['x-real-ip'],
		unanswered: this.unansweredCount
	};
};

ConnectionData.prototype.pickTextFields = function() {
	return _.pick(this, 'user', 'remoteip', 'hsheaders', 'cdid', 'access', 'lastInfoPush', 'mostRecentEventTime');
};

ConnectionData.prototype.toString = function() {
	return JSON.stringify(this.pickTextFields());
};

ConnectionData.uniqueCount = 0;

ConnectionData.loginIgnore = ['list-schools', 'password-reset', 'register', 'emailverif', 'login', 'prod', 'ping', 'school-exists', 'server-config'];

ConnectionData.prototype.fetchEvents = function(query) {
	if (!this.user)
		return; // no user – no events.

	// possibly push info
	this.pushSelfInfo();

	// fetch regular events
	this.request({name: 'feedFetchEvents', query: query, user: this.user, access: this.access}, _.bind(function(evlist) {
		_.each(evlist, _.bind(function(ev) {
			this.mostRecentEventTime = Math.max(this.mostRecentEventTime, ev.eventtime);
		}, this));

		this.wrapForReply({pushes: evlist}, function(r) {
			if (this.socket)
				this.socket.emit('push-container', r)
		});
	}, this));
};

ConnectionData.prototype.pushServerStatistics = buscomponent.listener('pushServerStatistics', function(data) {
	if (this.pushesServerStatistics) {
		data.type = 'push-server-statistics';
		this.push(data);
	}
});

ConnectionData.prototype.push = function(data) {
	this.wrapForReply(data, function(r) {
		if (this.socket)
			this.socket.emit('push', r);
	});

	this.pushSelfInfo();
};

ConnectionData.prototype.pushSelfInfo = function() {
	if (!this.user || !this.socket)
		return;

	assert.ok(this.bus);

	this.getServerConfig(function(cfg) {
		var curUnixTime = new Date().getTime();
		if (curUnixTime > this.lastInfoPush + cfg['infopush-mindelta']) {
			this.lastInfoPush = curUnixTime;
			this.request({name: 'client-get-user-info', query: {lookfor: '$self', nohistory: true}, user: this.user, access: this.access, xdata: this.pickTextFields()}, _.bind(function(code, info) {
				assert.ok(code == 'get-user-info-success');
				assert.ok(info);

				info.type = 'self-info';
				this.push(info);
			}, this));
		}
	});
};

ConnectionData.prototype.pushEvents = buscomponent.listener('push-events', function() {
	if (this.pushEventsTimer || !this.user || !this.user.uid)
		return;

	this.pushEventsTimer = setTimeout(_.bind(function() {
		this.pushEventsTimer = null;

		if (this.socket === null)
			return;

		this.fetchEvents({since: this.mostRecentEventTime, count: null});
	}, this), 1000);
});

ConnectionData.prototype.response = function(data) {
	this.wrapForReply(data, function(r) {
		if (this.socket)
			this.socket.emit('response', r)
	});

	if (this.isShuttingDown)
		this.shutdown();
};

ConnectionData.prototype.onUserConnected = function() {
	this.request({name: 'checkAchievements', user: this.user});
};

ConnectionData.prototype.query = buscomponent.errorWrap(function(query) {
	var recvTime = new Date().getTime();

	// sanitize by removing everything enclosed in '__'s
	var sanitizeQuery = function(q) {
		if (q.query)
			q.query = sanitizeQuery(q.query);

		return _.omit(q, _.chain(q).keys().filter(function(k) { return /^__.*__$/.test(k); }));
	};

	query = sanitizeQuery(query);

	this.queryCount++;
	if (query.lzma) {
		this.queryLZMACount++;
		this.lzmaSupport = true;
	}

	var hadUser = this.user ? true : false;

	assert.ok(this.bus);
	assert.ok(this.socket);

	this.request({name: 'loadSessionUser', key: query.key}, function(user) {
		if (!this.bus) {
			assert.ok(!this.socket);
			return;
		}

		this.request({name: 'getAuthorizationKey'}, function(authorizationKey) {

		var access = new Access();
		if (user != null)
			access.update(Access.fromJSON(user.access));

		this.access.update(access);

		if (query.authorizationKey == authorizationKey) {
			console.log('Received query with master authorization of type', query.type);
			this.access.grantAny();
			if (user == null && query.uid != null)
				user = {uid: query.uid, id: query.uid};
		}

		this.user = user;
		this.access[['grant', 'drop'][this.user && this.user.email_verif ? 0 : 1]]('email_verif');

		if (!hadUser && this.user != null)
			this.onUserConnected();

		var cb = _.bind(function(code, obj, extra) {
			this.unansweredCount--;

			var now = new Date().getTime();
			obj = obj || {};
			obj['code'] = code;
			obj['is-reply-to'] = query.id;
			obj['_t_sdone'] = now;
			obj['_t_srecv'] = recvTime;

			this.response(obj);

			if (extra == 'repush' && this.bus && this.socket) {
				this.lastInfoPush = 0;

				this.request({name: 'loadSessionUser', key: query.key}, function(newUser) {
					if (newUser)
						this.user = newUser;

					this.pushSelfInfo();
				});
			} else if (extra == 'logout') {
				this.user = null;
				this.pushesServerStatistics = false;
				this.access = new Access();
			}
		}, this);

		this.unansweredCount++;
		if (this.isShuttingDown) {
			cb('server-shutting-down');
		} else if (ConnectionData.loginIgnore.indexOf(query.type) == -1 && this.user === null && !this.access.has('login_override')) {
			cb('not-logged-in');
		} else {
			switch (query.type) {
				case 'get-server-statistics':
					if (!this.access.has('userdb'))
						return cb('permission-denied');
					this.pushesServerStatistics = true;

					this.emit('getServerStatistics');

					var interval = setInterval(_.bind(function() {
						if (this.bus && this.socket && this.pushServerStatistics) {
							this.emit('getServerStatistics');
						} else {
							clearInterval(interval);
							interval = null;
						}
					}, this), 10000);

					return cb('get-server-statistics-success');
				case 'fetch-events':
					this.fetchEvents(query);
					return cb('fetching-events');
			}

			this.request({
				name: 'client-' + query.type,
				query: query,
				user: this.user,
				access: this.access,
				xdata: this.pickTextFields()
			}, cb);
		}

		});
	});
});

ConnectionData.prototype.disconnected = buscomponent.errorWrap(function() {
	if (this.socket) {
		this.socket.removeListener('query', this.query_);
		this.socket.removeListener('disconnect', this.disconnected_);
	}

	if (this.bus) {
		this.request({name: 'deleteConnectionData', id: this.cdid}, function() {
			this.unplugBus();
			this.socket = null;
		});
	} else {
		// make sure we don't have a bus because we don't *want* one
		assert.ok(this.wantsUnplug);
	}
});

ConnectionData.prototype.close = function() {
	if (this.socket)
		this.socket.disconnect();
	else // disconnected would also be called via socket.disconnect()
		this.disconnected();
};

ConnectionData.prototype.shutdown = buscomponent.listener(['localShutdown', 'globalShutdown'], function() {
	this.isShuttingDown = true;

	if (this.unansweredCount == 0)
		this.close();
});

ConnectionData.prototype.wrapForReply = function(obj, cb) {
	cb = _.bind(cb, this);

	var s = JSON.stringify(obj);

	_.bind(s.length > 20480 && this.lzmaSupport ? function(cont) {
		this.queryLZMAUsedCount++;

		var buflist = [];

		var encoder = lzma.createStream('aloneEncoder', {preset: 3});
		encoder.on('data', function(data) { buflist.push(data); });
		encoder.on('end', function() { cont(Buffer.concat(buflist), 'lzma'); });
		encoder.end(s);
	} : function(cont) {
		cont(s, 'raw');
	}, this)(function(result, encoding) {
		cb({
			s: result,
			e: encoding,
			t: new Date().getTime()
		});
	});
};

exports.ConnectionData = ConnectionData;

})();
<commit_msg><commit_after>(function () { "use strict";

var _ = require('underscore');
var lzma = require('lzma-native');
var util = require('util');
var assert = require('assert');
var buscomponent = require('./buscomponent.js');
var Access = require('./access.js').Access;

function ConnectionData(socket) {
	this.lzmaSupport = false;
	this.user = null;
	this.hsheaders = _.omit(socket.handshake.headers, ['authorization', 'proxy-authorization']);
	this.remoteip = this.hsheaders['x-forwarded-for'] || this.hsheaders['x-real-ip'] || '127.0.0.182';
	this.cdid = new Date().getTime() + '-' + this.remoteip + '-' + ConnectionData.uniqueCount++;
	this.access = new Access();
	this.pushEventsTimer = null;
	this.lastInfoPush = 0;
	this.mostRecentEventTime = 0;
	this.socket = socket;
	this.isShuttingDown = false;
	this.unansweredCount = 0;
	this.pushesServerStatistics = false;

	this.queryCount = 0;
	this.queryLZMACount = 0;
	this.queryLZMAUsedCount = 0;

	this.query_ = _.bind(this.query, this);
	this.disconnected_ = _.bind(this.disconnected, this);

	socket.on('query', this.query_);
	socket.on('disconnect', this.disconnected_);
}
util.inherits(ConnectionData, buscomponent.BusComponent);

ConnectionData.prototype.onBusConnect = function() {
	this.getServerConfig(function(cfg) {
		this.push({type: 'server-config', 'config': _.pick(cfg, cfg.clientconfig)});
	});
};

ConnectionData.prototype.stats = function() {
	return {
		lzma: this.lzmaSupport,
		user: this.user ? { name: this.user.name, uid: this.user.uid } : null,
		lastInfoPush: this.lastInfoPush,
		mostRecentEventTime: this.mostRecentEventTime,
		queryCount: this.queryCount,
		queryLZMACount: this.queryLZMACount,
		queryLZMAUsedCount: this.queryLZMAUsedCount,
		ip: this.remoteip,
		xff: this.hsheaders['x-forwarded-for'],
		xrip: this.hsheaders['x-real-ip'],
		unanswered: this.unansweredCount
	};
};

ConnectionData.prototype.pickTextFields = function() {
	return _.pick(this, 'user', 'remoteip', 'hsheaders', 'cdid', 'access', 'lastInfoPush', 'mostRecentEventTime');
};

ConnectionData.prototype.toString = function() {
	return JSON.stringify(this.pickTextFields());
};

ConnectionData.uniqueCount = 0;

ConnectionData.loginIgnore = ['list-schools', 'password-reset', 'register', 'emailverif', 'login', 'prod', 'ping', 'school-exists', 'server-config'];

ConnectionData.prototype.fetchEvents = function(query) {
	if (!this.user)
		return; // no user – no events.

	// possibly push info
	this.pushSelfInfo();

	// fetch regular events
	this.request({name: 'feedFetchEvents', query: query, user: this.user, access: this.access}, _.bind(function(evlist) {
		_.each(evlist, _.bind(function(ev) {
			this.mostRecentEventTime = Math.max(this.mostRecentEventTime, ev.eventtime);
		}, this));

		this.wrapForReply({pushes: evlist}, function(r) {
			if (this.socket)
				this.socket.emit('push-container', r)
		});
	}, this));
};

ConnectionData.prototype.pushServerStatistics = buscomponent.listener('pushServerStatistics', function(data) {
	if (this.pushesServerStatistics) {
		data.type = 'push-server-statistics';
		this.push(data);
	}
});

ConnectionData.prototype.push = function(data) {
	this.wrapForReply(data, function(r) {
		if (this.socket)
			this.socket.emit('push', r);
	});

	this.pushSelfInfo();
};

ConnectionData.prototype.pushSelfInfo = function() {
	if (!this.user || !this.socket)
		return;

	assert.ok(this.bus);

	this.getServerConfig(function(cfg) {
		var curUnixTime = new Date().getTime();
		if (curUnixTime > this.lastInfoPush + cfg['infopush-mindelta']) {
			this.lastInfoPush = curUnixTime;
			this.request({name: 'client-get-user-info', query: {lookfor: '$self', nohistory: true}, user: this.user, access: this.access, xdata: this.pickTextFields()}, _.bind(function(code, info) {
				assert.ok(code == 'get-user-info-success');
				assert.ok(info);

				info.type = 'self-info';
				this.push(info);
			}, this));
		}
	});
};

ConnectionData.prototype.pushEvents = buscomponent.listener('push-events', function() {
	if (this.pushEventsTimer || !this.user || !this.user.uid)
		return;

	this.pushEventsTimer = setTimeout(_.bind(function() {
		this.pushEventsTimer = null;

		if (this.socket === null)
			return;

		this.fetchEvents({since: this.mostRecentEventTime, count: null});
	}, this), 1000);
});

ConnectionData.prototype.response = function(data) {
	this.wrapForReply(data, function(r) {
		if (this.socket)
			this.socket.emit('response', r)
	});

	if (this.isShuttingDown)
		this.shutdown();
};

ConnectionData.prototype.onUserConnected = function() {
	this.request({name: 'checkAchievements', user: this.user});
};

ConnectionData.prototype.query = buscomponent.errorWrap(function(query) {
	var recvTime = new Date().getTime();

	// sanitize by removing everything enclosed in '__'s
	var sanitizeQuery = function(q) {
		if (q.query)
			q.query = sanitizeQuery(q.query);

		return _.omit(q, _.chain(q).keys().filter(function(k) { return /^__.*__$/.test(k); }));
	};

	query = sanitizeQuery(query);

	this.queryCount++;
	if (query.lzma) {
		this.queryLZMACount++;
		this.lzmaSupport = true;
	}

	var hadUser = this.user ? true : false;

	assert.ok(this.bus);
	assert.ok(this.socket);

	this.request({name: 'loadSessionUser', key: query.key}, function(user) {
		if (!this.bus) {
			assert.ok(!this.socket);
			return;
		}

		this.request({name: 'getAuthorizationKey'}, function(authorizationKey) {

		var access = new Access();
		if (user != null)
			access.update(Access.fromJSON(user.access));

		this.access.update(access);

		if (query.authorizationKey == authorizationKey) {
			console.log('Received query with master authorization of type', query.type);
			this.access.grantAny();
			if (user == null && query.uid != null)
				user = {uid: query.uid, id: query.uid};
		}

		this.user = user;
		this.access[['grant', 'drop'][this.user && this.user.email_verif ? 0 : 1]]('email_verif');

		if (!hadUser && this.user != null)
			this.onUserConnected();

		var cb = _.bind(function(code, obj, extra) {
			this.unansweredCount--;

			var now = new Date().getTime();
			obj = obj || {};
			obj['code'] = code;
			obj['is-reply-to'] = query.id;
			obj['_t_sdone'] = now;
			obj['_t_srecv'] = recvTime;

			this.response(obj);

			if (extra == 'repush' && this.bus && this.socket) {
				this.lastInfoPush = 0;

				this.request({name: 'loadSessionUser', key: query.key}, function(newUser) {
					if (newUser)
						this.user = newUser;

					this.pushSelfInfo();
				});
			} else if (extra == 'logout') {
				this.user = null;
				this.pushesServerStatistics = false;
				this.access = new Access();
			}
		}, this);

		this.unansweredCount++;
		if (this.isShuttingDown) {
			cb('server-shutting-down');
		} else if (ConnectionData.loginIgnore.indexOf(query.type) == -1 && this.user === null && !this.access.has('login_override')) {
			cb('not-logged-in');
		} else {
			switch (query.type) {
				case 'get-server-statistics':
					if (!this.access.has('userdb'))
						return cb('permission-denied');
					this.pushesServerStatistics = true;

					this.emit('getServerStatistics');

					var interval = setInterval(_.bind(function() {
						if (this.bus && this.socket && this.pushServerStatistics) {
							this.emit('getServerStatistics');
						} else {
							clearInterval(interval);
							interval = null;
						}
					}, this), 10000);

					return cb('get-server-statistics-success');
				case 'fetch-events':
					this.fetchEvents(query);
					return cb('fetching-events');
			}

			this.request({
				name: 'client-' + query.type,
				query: query,
				user: this.user,
				access: this.access,
				xdata: this.pickTextFields()
			}, cb);
		}

		});
	});
});

ConnectionData.prototype.disconnected = buscomponent.errorWrap(function() {
	if (this.socket) {
		this.socket.removeListener('query', this.query_);
		this.socket.removeListener('disconnect', this.disconnected_);
	}

	if (this.bus) {
		this.request({name: 'deleteConnectionData', id: this.cdid}, function() {
			this.unplugBus();
			this.socket = null;
		});
	} else {
		// make sure we don't have a bus because we don't *want* one
		assert.ok(this.wantsUnplug);
	}
});

ConnectionData.prototype.close = function() {
	if (this.socket)
		this.socket.disconnect();
	else // disconnected would also be called via socket.disconnect()
		this.disconnected();
};

ConnectionData.prototype.shutdown = buscomponent.listener(['localShutdown', 'globalShutdown'], function() {
	this.isShuttingDown = true;

	if (this.unansweredCount == 0)
		this.close();
});

ConnectionData.prototype.wrapForReply = function(obj, cb) {
	cb = _.bind(cb, this);

	var s = JSON.stringify(obj);

	if (!this.socket)
		this.lzmaSupport = false;

	_.bind(s.length > 20480 && this.lzmaSupport ? function(cont) {
		this.queryLZMAUsedCount++;

		var buflist = [];

		var encoder = lzma.createStream('aloneEncoder', {preset: 3});
		encoder.on('data', function(data) { buflist.push(data); });
		encoder.on('end', function() { cont(Buffer.concat(buflist), 'lzma'); });
		encoder.end(s);
	} : function(cont) {
		cont(s, 'raw');
	}, this)(function(result, encoding) {
		cb({
			s: result,
			e: encoding,
			t: new Date().getTime()
		});
	});
};

exports.ConnectionData = ConnectionData;

})();
<|endoftext|>