import { MockRequest, MockStatusError } from '@delon/mock';
import { deepCopy } from '@delon/util';
import format from 'date-fns/format';
import getDaysInMonth from 'date-fns/getDaysInMonth';
import startOfMonth from 'date-fns/startOfMonth';
import { Random } from 'mockjs';

import { genArr, genBigMp, genColorName, genData, genLabel, genMp, genName, genTag } from './utils';

let ID = 1;
const DATA: any = {
  kanban: null,
  task: null,
  email: null,
  project: null,
  client: null,
  contact: null,
  pricing: null,
  billing: null,
  course: null,
  chat: null,
  gallery: null,
  article: null,
  voting: null,
  invoice: null,
  faq: null,
  calendar: null,
  quick: null,
  dd: null
};

function getIdx(type: string, id: number): number {
  id = +id;
  const idx = DATA[type].findIndex((w: any) => w.id === id);
  if (idx === -1) {
    throw new MockStatusError(404);
  }
  return idx;
}

function save(type: string, body: any): any {
  const id = body.id || 0;
  if (id > 0) {
    const idx = getIdx(type, id);
    DATA[type][idx] = { ...DATA[type][idx], ...body };
    return { msg: 'ok', item: DATA[type][idx], type: 'edit' };
  }

  const item = { ...body, id: DATA[type].sort((a: any, b: any) => b.id - a.id)[0].id + 1 };
  (DATA[type] as any[]).splice(0, 0, item);
  return { msg: 'ok', item, type: 'add' };
}

function del(type: string, p: any): any {
  const cid = +(p.cid || '0');
  let list: any[] = DATA[type];
  if (cid > 0) {
    list = DATA[type].find((w: any) => w.id === cid).list;
  }

  const idx = list.findIndex(w => w.id === p.id);
  list.splice(idx, 1);
  return { msg: 'ok' };
}

function genHtml(): string {
  return `<p>${new Array(Random.natural(1, 3))
    .fill('')
    .map(v => Random.sentence())
    .join('</p><p>')}</p>`;
}

function attachements(): any {
  return new Array(Random.natural(2, 6)).fill({}).map((v, idx) => {
    const item = {
      url: Random.url(),
      type: genArr(['jpg', 'zip', 'pdf']),
      filename: Random.name(false),
      size: genArr(['100KB', '980KB', '1.56MB'])
    };
    if (item.type === 'jpg') {
      item.url = genBigMp();
    }
    return item;
  });
}

function genPage(type: string, queryString: any, qField: string = 'name'): any {
  const pi = +(queryString.pi || 1);
  const ps = +(queryString.ps || 10);
  // data
  let data = deepCopy(DATA[type]);
  if (queryString.q) {
    data = data.filter((data: any) => data[qField].indexOf(queryString.q) > -1);
  }
  return {
    total: data.length,
    list: data.slice((pi - 1) * ps, pi * ps)
  };
}

// #region kanban

function kanbanList(): any {
  if (DATA.kanban) {
    return DATA.kanban;
  }
  const res: any[] = [
    {
      id: 1,
      title: 'To Do',
      list: [],
      color: '#fadb14',
      icon: 'warning'
    },
    { id: 2, title: 'In progress', color: '#1890ff', icon: 'tool', list: [] },
    { id: 3, title: 'Done', color: '#52c41a', icon: 'check-circle', list: [] },
    { id: 4, title: 'Gone', color: '#f5222d', icon: 'delete', list: [] }
  ];
  for (const i of res) {
    i.list = new Array(Random.natural(2, 6)).fill({}).map((v, idx) => ({
      id: idx + 1,
      title: Random.ctitle(3, 4),
      content: Random.ctitle(0, 50),
      attachement: Random.boolean() && Random.boolean() && Random.boolean()
    }));
  }
  // new
  if (res[0].list.length > 0) {
    res[0].list[Random.natural(0, res[0].list.length - 1)].label = {
      color: 'green',
      text: 'Clients'
    };
  }
  if (res[1].list.length > 0) {
    res[1].list[Random.natural(0, res[1].list.length - 1)].label = {
      color: 'red',
      text: 'Important'
    };
  }
  if (res[2].list.length > 0) {
    res[2].list[Random.natural(0, res[2].list.length - 1)].label = {
      color: 'blue',
      text: 'Other'
    };
  }
  // labels
  DATA.kanban = res;
  return res;
}

// #endregion

// #region task

function taskList(): any {
  if (DATA.task) {
    return DATA.task;
  }
  const res: any[] = [
    { id: 1, title: 'Today', list: [] },
    { id: 2, title: 'Tomorrow', list: [] },
    { id: 3, title: 'Next week', list: [] }
  ];
  for (const i of res) {
    i.list = new Array(Random.natural(2, 8)).fill({}).map((v, idx) => ({
      id: idx + 1,
      title: Random.ctitle(3, 16),
      due: i.id === 1 && Random.boolean() ? `${Random.natural(1, 59)} ${Random.boolean() ? 'mins' : 'hours'} left` : null
    }));
  }
  // new
  if (res[0].list.length > 0) {
    res[0].list[Random.natural(0, res[0].list.length - 1)].label = {
      color: 'green',
      text: 'Clients'
    };
  }
  if (res[1].list.length > 0) {
    res[1].list[Random.natural(0, res[1].list.length - 1)].label = {
      color: 'red',
      text: 'Important'
    };
    res[0].list[Random.natural(0, res[0].list.length - 1)].done = true;
  }
  if (res[2].list.length > 0) {
    res[2].list[Random.natural(0, res[2].list.length - 1)].label = {
      color: 'blue',
      text: 'Other'
    };
    res[0].list[Random.natural(0, res[0].list.length - 1)].done = true;
  }
  // labels
  DATA.task = res;
  return res;
}

// #endregion

// #region email

function emailList(queryString: any): any {
  if (DATA.email) {
    return genPage('email', queryString, 'subject');
  }
  const res: any[] = new Array(20).fill({}).map((v, idx) => ({
    id: ID++,
    from: Random.email(),
    from_name: genName(),
    to: Random.email(),
    to_name: Random.name(),
    cc: [Random.email(), Random.email()],
    subject: Random.title(1, 6),
    body: Random.paragraph(),
    read: idx % 2 === 0 && Random.boolean(),
    star: Random.boolean(),
    label: genLabel(),
    attach: idx % 3 === 0 && Random.boolean(),
    time: `1${Random.boolean() ? 'h' : 'd'} ago`
  }));
  // labels
  DATA.email = res;
  return genPage('email', queryString, 'subject');
}

function emailGet(id: number): any {
  const idx = getIdx('email', id || 0);
  const item = { ...DATA.email[idx], mp: genMp(), desc: genHtml(), attachements: attachements() };
  return item;
}
// #endregion

// #region project

function projectList(): any {
  if (DATA.project) {
    return DATA.project;
  }
  const res: any[] = new Array(5).fill({}).map((v, idx) => ({
    id: idx + 1,
    title: genArr(['UI update', 'Web Design', 'Pro Design', 'Ng Alain', 'Delon', 'SEO']),
    status: idx % 2 ? genArr(['active', 'pending', 'complete']) : 'active',
    task: {
      process: Random.natural(1, 100),
      opened: Random.natural(1, 100),
      completed: Random.natural(1, 100)
    },
    remark: Random.title(5, 10),
    created: Random.date(),
    deadline: Random.date(),
    tean: new Array(Random.natural(1, 6)).fill({}).map(() => ({
      name: genName(),
      mp: genMp()
    })),
    leaders: new Array(Random.natural(1, 2)).fill({}).map(() => ({
      name: genName(),
      mp: genMp()
    })),
    participants: new Array(Random.natural(1, 6)).fill({}).map(() => ({
      name: Random.name(),
      mp: genMp()
    }))
  }));
  // labels
  DATA.project = res;
  return res;
}

function projectGet(id: number): any {
  const idx = getIdx('project', id || 0);
  const item = {
    ...DATA.project[idx],
    user_name: genName(),
    desc: genHtml(),
    attachements: attachements(),
    tasks: DATA.task,
    discussions: new Array(Random.natural(3, 8)).fill({}).map((v, idx) => ({
      id: idx + 1,
      user_avatar: genMp(),
      user_name: genName(),
      time: Random.datetime(),
      content: Random.paragraph(1, 1)
    })),
    activities: new Array(Random.natural(3, 12)).fill({}).map((v, idx) => ({
      id: idx + 1,
      user_avatar: genMp(),
      user_name: genName(),
      time: Random.datetime(),
      type: idx % 2 === 0 ? genArr(['add', 'completed', 'assigned']) : 'push',
      commit: Random.natural(10000, 99999),
      assigne_name: Random.name(),
      message: Random.title()
    }))
  };
  return item;
}

// #endregion

// #region billing

function billingList(queryString: any): any {
  if (DATA.billing) {
    return genPage('billing', queryString, 'client');
  }
  const res: any[] = new Array(11).fill({}).map((v, idx) => ({
    id: idx + 1,
    order: `FR0${Random.natural(10, 99)}`,
    client: genArr(['Google', 'Alibaba', 'Tencent']),
    fee: Random.float(0, 9.0),
    amount: Random.float(0.1, 99999.0),
    date: Random.now('day'),
    status: idx % 2 ? genArr(['Completed', 'Pending', 'Rejected']) : 'Completed',
    auth_code: Random.natural(100000000),
    address: (Random as any).county(true),
    first_name: Random.first(),
    last_name: Random.last(),
    country: 'China'
  }));
  // labels
  DATA.billing = res;
  return genPage('billing', queryString, 'client');
}

function billingGet(id: number): any {
  const idx = getIdx('billing', id || 0);
  const item = {
    ...DATA.billing[idx],
    messages: new Array(Random.natural(0, 5)).fill({}).map((v, idx) => ({
      id: idx + 1,
      time: `${Random.natural(1, 6)} day ago`,
      message: Random.paragraph(1, 1)
    }))
  };
  return item;
}

// #endregion

// #region contact

function contactList(queryString: any): any {
  if (DATA.contact) {
    return genPage('contact', queryString, 'contact');
  }
  const res: any[] = new Array(11).fill({}).map((v, idx) => ({
    id: idx + 1,
    mp: genMp(),
    name: genName(),
    user_name: Random.name(false),
    company: Random.title(1, 3),
    email: Random.email(),
    tel: Random.natural(10000000000, 16000000000)
  }));
  // labels
  DATA.contact = res;
  return genPage('contact', queryString, 'company');
}

// #endregion

// #region pricing

function pricingList(): any {
  if (DATA.pricing) {
    return DATA.pricing;
  }
  const res: any = {
    prices: [
      {
        id: 1,
        title: 'Basic',
        icon: 'shop',
        mo: 12,
        yr: 12 * 12,
        user: 5,
        project: 5,
        space: '100GB'
      },
      {
        id: 2,
        title: 'Company',
        icon: 'bank',
        mo: 25,
        yr: 25 * 12,
        user: 30,
        project: 150,
        space: '300GB'
      },
      {
        id: 3,
        title: 'Enterprise',
        icon: 'crown',
        mo: 50,
        yr: 50 * 12,
        user: -1,
        project: -1,
        space: '1000GB'
      }
    ],
    faq: [
      {
        q: 'Can I cancel at anytime?',
        a: 'Yes, you can cancel anytime no questions are asked while you cancel but we would highly appreciate if you will give us some feedback.'
      },
      {
        q: 'My team has credits. How do we use them?',
        a: 'Once your team signs up for a subscription plan. enim eiusmod high life accusamus eoset dignissimos.'
      },
      {
        q: `How does Front's pricing work?`,
        a: 'Our subscriptions are tiered. based on the number of people enim eiusmod high life accusamus terry richardson ad squid.'
      },
      {
        q: 'How secure is Front?',
        a: 'Protecting the data you trust to Front is our first priority. at vero eoset dignissimos ducimus qui blanditiis.'
      },
      {
        q: 'Do you offer discounts?',
        a: `We've built in discounts at each tier for teams. leggings occaecat craft beer farm-to-table. raw denim aesthetic synth nesciunt.`
      },
      {
        q: 'What is your refund policy?',
        a: 'We do not offer refunds apart leggings occaecat craft beer farm-to-table. raw leggings occaecat craft.'
      }
    ]
  };
  // labels
  DATA.pricing = res;
  return res;
}

// #endregion

// #region client

function clientList(queryString: any): any {
  if (DATA.client) {
    return genPage('client', queryString, 'company');
  }
  const res: any[] = new Array(11).fill({}).map((v, idx) => ({
    id: idx + 1,
    mp: genMp(),
    name: genName(),
    user_name: Random.name(false),
    company: Random.title(1, 3),
    email: Random.email(),
    tel: Random.natural(10000000000, 16000000000),
    status: idx % 2 ? genArr(['active', 'pending', 'progress']) : 'active'
  }));
  // labels
  DATA.client = res;
  return genPage('client', queryString, 'company');
}

function clientGet(id: number): any {
  const idx = getIdx('client', id || 0);
  const item = {
    ...DATA.client[idx],
    messages: new Array(Random.natural(0, 5)).fill({}).map((v, idx) => ({
      id: idx + 1,
      time: `${Random.natural(1, 6)} day ago`,
      message: Random.paragraph(1, 1)
    }))
  };
  return item;
}

// #endregion

// #region course

function courseList(queryString: any): any {
  if (DATA.course) {
    return genPage('course', queryString, 'title');
  }
  const res: any[] = new Array(10).fill({}).map((v, idx) => ({
    id: idx + 1,
    mp: genBigMp(),
    tags: genTag(),
    price: idx === 0 ? 0 : Random.natural(0, 100),
    title: Random.title(2, 5),
    remark: Random.paragraph(1, 1),
    star: genArr([4, 4.5, 5]),
    hour: Random.natural(10, 99)
  }));
  // labels
  DATA.course = res;
  return genPage('course', queryString, 'title');
}

// #endregion

// #region chat

function chatList(): any {
  if (DATA.chat) {
    return DATA.chat;
  }
  const res: any = {
    users: new Array(10).fill({}).map((v, idx) => ({
      id: idx + 1,
      mp: genMp(),
      name: genName(),
      count: idx % 3 === 0 ? Random.natural(0, 5) : 0,
      online: idx < 5 ? true : false,
      unread: Random.boolean() && Random.boolean() ? Random.natural(0, 5) : 0
    }))
  };
  // labels
  DATA.chat = res;
  return res;
}

// #endregion

// #region gallery

function galleryList(): any {
  if (DATA.gallery) {
    return DATA.gallery;
  }
  const res: any = new Array(16).fill({}).map((v, idx) => ({
    id: idx + 1,
    url: genBigMp(),
    title: Random.title(),
    type: genArr(['Nature', 'Beach', 'Animal', 'Other'])
  }));
  // labels
  DATA.gallery = res;
  return res;
}

// #endregion

// #region article

function articleList(queryString: any): any {
  if (DATA.article) {
    return genPage('article', queryString, 'title');
  }
  const res: any[] = new Array(11).fill({}).map((v, idx) => ({
    id: idx + 1,
    mp: genMp(),
    name: genName(),
    title: Random.ctitle(),
    likes: Random.natural(0, 1000),
    comments: Random.natural(0, 1000),
    created: Random.now('day'),
    status: idx % 2 ? genArr(['Published', 'Draft', 'Deleted']) : 'Published'
  }));
  // labels
  DATA.article = res;
  return genPage('article', queryString, 'title');
}

function articleGet(id: number): any {
  const idx = getIdx('article', id || 0);
  const item = { ...DATA.article[idx] };
  return item;
}

// #endregion

// #region voting

function votingList(queryString: any): any {
  if (DATA.voting) {
    return genPage('voting', queryString, 'title');
  }
  const res: any[] = new Array(11).fill({}).map((v, idx) => ({
    id: idx + 1,
    voting: Random.integer(-10, 10000),
    title: Random.title(5, 10),
    content: Random.paragraph(),
    likes: Random.natural(0, 1000),
    created: Random.now('day')
  }));
  // labels
  DATA.voting = res;
  return genPage('voting', queryString, 'title');
}

function votingSave(req: any): any {
  const idx = getIdx('voting', req.id || 0);
  DATA.voting[idx].value += req.voting;
  return { msg: 'ok', item: DATA.voting[idx] };
}

// #endregion

// #region voting

function invoice(): any {
  if (DATA.invoice) {
    return deepCopy(DATA.invoice);
  }
  const res: any = {
    id: Random.integer(10000, 99999),
    zone: 'Mountain View, CA 94043 United States',
    address: '1600 Amphitheatre Parkway',
    tel: '15900000000, +86 (021) 99999999',
    date: genData(0),
    to: {
      company: 'XXX Company LTD',
      zone: 'Mountain View, CA 94043 United States',
      address: '1600 Amphitheatre Parkway',
      tel: '15900000000, +86 (021) 99999999',
      email: 'cipchk@qq.com'
    },
    payment: {
      total: 0,
      bank: 'XXXX Bank',
      country: 'China',
      city: 'Shang Hai',
      address: 'xxx xxxx',
      code: '012384'
    },
    wares: [
      {
        id: 1,
        title: Random.title(),
        remark: Random.title(),
        price: +Random.float(0.1, 999).toFixed(2),
        num: +Random.natural(1, 10)
      },
      {
        id: 2,
        title: Random.title(),
        remark: Random.title(),
        price: +Random.float(0.1, 999).toFixed(2),
        num: +Random.natural(1, 10)
      }
    ],
    note: Random.paragraph()
  };
  // total
  res.wares.forEach((i: any) => {
    i.total = +(i.price * i.num).toFixed(2);
  });
  res.tax_rate = 0.2;
  res.total = +(res.wares as any[]).reduce((a, b) => (a += b.total), 0).toFixed(2);
  res.tax = +(res.total * 0.2).toFixed(2);
  res.payment_total = +(res.total + res.tax).toFixed(2);
  DATA.invoice = res;

  return deepCopy(DATA.invoice);
}

// #endregion

// #region faq

function faq(): any {
  if (DATA.faq) {
    return deepCopy(DATA.faq);
  }
  DATA.faq = new Array(6).fill({}).map((v, idx) => ({
    title: `Knowledge ${idx + 1}`,
    icon: 'question-circle',
    primary: idx < 3,
    remark: 'The list of FAQ',
    children: new Array(Random.natural(3, 6)).fill({}).map((v, idx) => ({
      active: idx === 0,
      q: 'What is a product key?',
      a: genHtml()
    }))
  }));

  return deepCopy(DATA.faq);
}

// #endregion

// #region calendar

function calendar(req: any): any {
  const cur = new Date(+req.time || new Date());
  const startDate = startOfMonth(cur);
  const max = getDaysInMonth(cur);
  const start = format(startDate, 'yyyy-MM');
  const now = format(new Date(), 'yyyy-MM-dd');
  return [
    {
      id: 1,
      title: 'All Day Event',
      start: `${start}-1`,
      className: 'fc-event-danger fc-event-fill-warning'
    },
    {
      id: 2,
      title: 'Reporting',
      start: `${start}-7T13:30:00`,
      end: `${start}-7`,
      className: 'fc-event-success'
    },
    {
      id: 3,
      title: 'Company Trip',
      start: `${start}-12`,
      end: `${start}-14`,
      className: 'fc-event-primary'
    },
    {
      id: 4,
      title: 'Product Release',
      start: `${start}-3`,
      end: `${start}-5`,
      className: 'fc-event-light fc-event-fill-primary'
    },
    {
      id: 5,
      title: 'Repeating Event',
      start: `${start}-09T16:00:00`,
      className: 'fc-event-purple'
    },
    { id: 6, title: 'Repeating Event', start: `${start}-11T16:00:00` },
    {
      id: 7,
      title: 'Meeting',
      start: `${now}T10:00:00`,
      end: `${now}T11:30:00`
    },
    { id: 8, title: 'Lunch', start: `${now}T12:00:00` },
    {
      id: 9,
      title: 'Meeting',
      start: `${now}T14:00:00`,
      className: 'fc-event-warning'
    },
    {
      id: 10,
      title: 'Happy Hour',
      start: `${now}T17:30:00`,
      className: 'fc-event-success'
    },
    {
      id: 11,
      title: 'Dinner',
      start: `${now}T18:30:00`,
      className: 'fc-event-fill-danger fc-event-light'
    },
    {
      id: 12,
      title: 'Birthday Party',
      start: `${now}T21:00:00`,
      className: 'fc-event-primary'
    },
    {
      id: 13,
      title: 'Click for Ng Alain',
      url: 'https://ng-alain.com',
      start: `${start}-27`,
      className: 'fc-event-fill-success fc-event-light'
    },
    {
      id: 14,
      title: 'Repeating Event',
      start: `${start}-09T08:00:00`,
      className: 'fc-event-magenta'
    }
  ];
}

// #endregion

// #region quick

function quick(): any {
  if (DATA.quick) {
    return deepCopy(DATA.quick);
  }
  DATA.quick = {
    notifications: new Array(6).fill({}).map(() => ({
      dot: genArr([
        { icon: 'warning', bg: 'error' },
        { icon: 'pie-chart', bg: 'primary' },
        { icon: 'message', bg: 'success' },
        { icon: 'bell', bg: 'cyan' }
      ]),
      content: Random.title(5, 15),
      time: genArr(['01:01 PM', '09:00 AM', '18:56']),
      tags: genTag().join(',')
    })),
    actions: new Array(6).fill({}).map(() => ({
      bg: genColorName(),
      title: Random.title(2, 3),
      content: Random.title(5, 15)
    })),
    settings: {
      notification: true,
      audit_log: false,
      new_order: false,
      tracking_order: false,
      reports_order: true,
      new_customer: true,
      reporting_customer: true
    }
  };

  return deepCopy(DATA.quick);
}

// #endregion

// #region dd

function ddList(): any {
  if (DATA.dd) {
    return DATA.dd;
  }
  DATA.dd = [
    {
      name: 'total-sales',
      title: '总数-总销售额',
      enabled: true,
      params: { title: '总销售额', total: 100, week: 10, day: 11, daySales: 1000 }
    },
    {
      name: 'total-sales',
      title: '总数-总订单量',
      enabled: false,
      params: { title: '总订单量', total: 5500, week: 320, day: 5, daySales: 5422 }
    },
    {
      name: 'total-sales',
      title: '总数-总用户量',
      enabled: false,
      params: { title: '总用户量', total: 500, week: 80, day: 23, daySales: 6666 }
    },
    {
      name: 'total-sales',
      title: '总数-其他',
      enabled: false,
      params: { title: '其他', total: 200, week: 80, day: 23, daySales: 77777 }
    },
    {
      name: 'visits',
      title: '访问量',
      enabled: true,
      params: { url: '/chart' }
    },
    {
      name: 'effect',
      title: '访问量',
      enabled: true,
      params: { percent: 66, week: 11, day: 23 }
    },
    {
      name: 'gauge',
      title: '核销率',
      enabled: true
    },
    {
      name: 'radar',
      title: '指数',
      enabled: true,
      params: { title: '贡献指数', url: '/chart' }
    },
    {
      name: 'activities',
      title: '动态',
      enabled: true,
      params: { title: '动态', url: '/api/activities' }
    }
  ];
  return DATA.dd;
}

function ddSave(req: any): any {
  DATA.dd = req;
  return { msg: 'ok' };
}

// #endregion

export const OTHERS = {
  '/kanban-board': kanbanList(),
  'DELETE /kanban-board': (req: MockRequest) => del('kanban', req.queryString),
  '/task': taskList(),
  'DELETE /task': (req: MockRequest) => del('task', req.queryString),
  '/email': (req: MockRequest) => emailList(req.queryString),
  '/email/:id': (req: MockRequest) => emailGet(+req.params.id),
  'DELETE /email': (req: MockRequest) => del('email', req.queryString),
  '/project': projectList(),
  '/project/:id': (req: MockRequest) => projectGet(+req.params.id),
  'DELETE /project': (req: MockRequest) => del('project', req.queryString),
  '/client': (req: MockRequest) => clientList(req.queryString),
  '/client/:id': (req: MockRequest) => clientGet(+req.params.id),
  '/contact': (req: MockRequest) => contactList(req.queryString),
  'DELETE /contact': (req: MockRequest) => del('contact', req.queryString),
  '/pricing': () => pricingList(),
  '/billing': (req: MockRequest) => billingList(req.queryString),
  '/course': (req: MockRequest) => courseList(req.queryString),
  '/chat': () => chatList(),
  '/chat/message': () => {
    const item: any = {
      type: Random.boolean() && Random.boolean() ? 'image' : 'text',
      dir: Random.boolean() ? 'left' : 'right'
    };
    item.msg = item.type === 'text' ? Random.paragraph(1, 1) : genBigMp();
    return item;
  },
  '/gallery': () => galleryList(),
  '/article': (req: MockRequest) => articleList(req.queryString),
  '/article/:id': (req: MockRequest) => articleGet(+req.params.id),
  'DELETE /article': (req: MockRequest) => del('article', req.queryString),
  'POST /article': (req: MockRequest) => save('article', req.body),
  '/voting': (req: MockRequest) => votingList(req.queryString),
  'POST /voting': (req: MockRequest) => votingSave(req.body),
  '/invoice': () => invoice(),
  '/faq': () => faq(),
  '/calendar': (req: MockRequest) => calendar(req.queryString),
  '/quick': () => quick(),
  '/dd': () => ddList(),
  'POST /dd': (req: MockRequest) => ddSave(req.body)
};
