@@ -56,6 +66,7 @@ export default function AccountSettings() {
render: Checkmark,
},
{
+ key: 'actions',
className: classNames(styles.buttons, 'col-12 col-md-4 pt-2 pt-md-0'),
render: Buttons,
},
diff --git a/lang/de-DE.json b/lang/de-DE.json
index fbc499f1..df6adaa1 100644
--- a/lang/de-DE.json
+++ b/lang/de-DE.json
@@ -12,12 +12,14 @@
"button.more": "Mehr",
"button.save": "Speichern",
"button.view-details": "Details anzeigen",
+ "button.websites": "Webseiten",
"footer.powered-by": "Powered by",
"header.nav.dashboard": "Übersicht",
"header.nav.settings": "Einstellungen",
"label.administrator": "Administrator",
"label.confirm-password": "Passwort wiederholen",
"label.current-password": "Derzeitiges Passwort",
+ "label.custom-range": "Custom range",
"label.domain": "Domain",
"label.enable-share-url": "Freigabe-URL aktivieren",
"label.invalid": "Ungültig",
@@ -42,6 +44,7 @@
"message.failure": "Es it ein Fehler aufgetreten.",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
"message.no-data-available": "Keine Daten vorhanden.",
+ "message.page-not-found": "Seite nicht gefunden.",
"message.save-success": "Erfolgreich gespeichert.",
"message.share-url": "Dies ist der öffentliche URL zum Teilen für {target}.",
"message.track-stats": "Um die Statistiken für {target} zu übermitteln, platzieren Sie bitte den folgenden Quelltext im {head} ihrer Homepage.",
diff --git a/lang/en-US.json b/lang/en-US.json
index df557819..e24b9347 100644
--- a/lang/en-US.json
+++ b/lang/en-US.json
@@ -12,12 +12,14 @@
"button.more": "More",
"button.save": "Save",
"button.view-details": "View details",
+ "button.websites": "Websites",
"footer.powered-by": "Powered by",
"header.nav.dashboard": "Dashboard",
"header.nav.settings": "Settings",
"label.administrator": "Administrator",
"label.confirm-password": "Confirm password",
"label.current-password": "Current password",
+ "label.custom-range": "Custom range",
"label.domain": "Domain",
"label.enable-share-url": "Enable share URL",
"label.invalid": "Invalid",
@@ -42,6 +44,7 @@
"message.failure": "Something went wrong.",
"message.incorrect-username-password": "Incorrect username/password.",
"message.no-data-available": "No data available.",
+ "message.page-not-found": "Page not found.",
"message.save-success": "Saved successfully.",
"message.share-url": "This is the publicly shared URL for {target}.",
"message.track-stats": "To track stats for {target}, place the following code in the {head} section of your website.",
diff --git a/lang/es-MX.json b/lang/es-MX.json
index 783992d8..e089ef7f 100644
--- a/lang/es-MX.json
+++ b/lang/es-MX.json
@@ -12,12 +12,14 @@
"button.more": "Más",
"button.save": "Guardar",
"button.view-details": "Ver detalles",
+ "button.websites": "Sitios",
"footer.powered-by": "Desarrollado con",
"header.nav.dashboard": "Panel de control",
"header.nav.settings": "Configuraciones",
"label.administrator": "Administrador",
"label.confirm-password": "Confirmar contraseña",
"label.current-password": "Contraseña actual",
+ "label.custom-range": "Custom range",
"label.domain": "Dominio",
"label.enable-share-url": "Habilitar compartir URL",
"label.invalid": "Inválido",
@@ -42,6 +44,7 @@
"message.failure": "Algo falló.",
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
"message.no-data-available": "Sin información disponible.",
+ "message.page-not-found": "Page not found",
"message.save-success": "Guardado exitosamente.",
"message.share-url": "Esta es la URL compartida públicamente para {target}.",
"message.track-stats": "Para registrar estadísticas para {target}, copia el siguiente código dentro de la etiqueta {head} de tu sitio.",
diff --git a/lang/ja-JP.json b/lang/ja-JP.json
index 7fe9095f..cdeec4c8 100644
--- a/lang/ja-JP.json
+++ b/lang/ja-JP.json
@@ -12,12 +12,14 @@
"button.more": "さらに表示",
"button.save": "保存",
"button.view-details": "詳細表示",
+ "button.websites": "Webサイト",
"footer.powered-by": "Powered by",
"header.nav.dashboard": "ダッシュボード",
"header.nav.settings": "設定",
"label.administrator": "管理者",
"label.confirm-password": "パスワード(確認)",
"label.current-password": "現在のパスワード",
+ "label.custom-range": "Custom range",
"label.domain": "ドメイン",
"label.enable-share-url": "共有リンクを有効にする",
"label.invalid": "無効",
@@ -42,6 +44,7 @@
"message.failure": "問題が発生しました。",
"message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。",
"message.no-data-available": "データがありません。",
+ "message.page-not-found": "ページが見つかりません。",
"message.save-success": "正常に保存されました。",
"message.share-url": "これは {target} の共有リンクです。",
"message.track-stats": "{target}のアクセス解析を開始するには、次のコードをWebサイトの{head}セクションへ追加してください。",
diff --git a/lang/nl-NL.json b/lang/nl-NL.json
index d48696ef..8d97ca43 100644
--- a/lang/nl-NL.json
+++ b/lang/nl-NL.json
@@ -12,12 +12,14 @@
"button.more": "Toon meer",
"button.save": "Opslaan",
"button.view-details": "Meer details",
+ "button.websites": "Websites",
"footer.powered-by": "mogelijk gemaakt door",
"header.nav.dashboard": "Dashboard",
"header.nav.settings": "Instellingen",
"label.administrator": "Administrator",
"label.confirm-password": "Wachtwoord bevestigen",
"label.current-password": "Huidig wachtwoord",
+ "label.custom-range": "Custom range",
"label.domain": "Domein",
"label.enable-share-url": "Sta delen via openbare URL toe",
"label.invalid": "Ongeldig",
@@ -42,6 +44,7 @@
"message.failure": "Er is iets misgegaan.",
"message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.",
"message.no-data-available": "Geen gegevens beschikbaar.",
+ "message.page-not-found": "Pagina niet gevonden.",
"message.save-success": "Opslaan succesvol.",
"message.share-url": "Met deze URL kan {target} openbaar gedeeld worden.",
"message.track-stats": "Om statistieken voor {target} bij te houden, plaats je de volgende code in het {head} gedeelte van je website.",
diff --git a/lang/ru-RU.json b/lang/ru-RU.json
index 55500e00..5c6e9df7 100644
--- a/lang/ru-RU.json
+++ b/lang/ru-RU.json
@@ -12,12 +12,14 @@
"button.more": "Больше",
"button.save": "Сохранить",
"button.view-details": "Посмотреть детали",
+ "button.websites": "Сайты",
"footer.powered-by": "на движке",
"header.nav.dashboard": "Информационная панель",
"header.nav.settings": "Настройки",
"label.administrator": "Администратор",
"label.confirm-password": "Подтвердить пароль",
"label.current-password": "Текущий пароль",
+ "label.custom-range": "Custom range",
"label.domain": "Домен",
"label.enable-share-url": "Разрешить делиться ссылкой",
"label.invalid": "Некорректный",
@@ -42,6 +44,7 @@
"message.failure": "Что-то пошло не так.",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.",
"message.no-data-available": "Нет данных.",
+ "message.page-not-found": "Страница не найдена.",
"message.save-success": "Успешно сохранено.",
"message.share-url": "Это публичная ссылка для {target}.",
"message.track-stats": "Чтобы отслеживать статистику для {target}, поместите следующий код в раздел {head} вашего сайта.",
diff --git a/lang/tr-TR.json b/lang/tr-TR.json
index bac433c5..8e9f100c 100644
--- a/lang/tr-TR.json
+++ b/lang/tr-TR.json
@@ -12,12 +12,14 @@
"button.more": "Detaylı göster",
"button.save": "Kaydet",
"button.view-details": "Detayı incele",
+ "button.websites": "Web siteleri",
"footer.powered-by": "Sağlayıcı:",
"header.nav.dashboard": "Kontrol Paneli",
"header.nav.settings": "Ayarlar",
"label.administrator": "Yönetici",
"label.confirm-password": "Parolayı onayla",
"label.current-password": "Mevcut parola",
+ "label.custom-range": "Custom range",
"label.domain": "Alan adı",
"label.enable-share-url": "Anonim paylaşım URL'i aktif",
"label.invalid": "Geçeriz",
@@ -42,6 +44,7 @@
"message.failure": "Bir şeyler ters gitti!",
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
"message.no-data-available": "Henüz hiç veri yok.",
+ "message.page-not-found": "Sayfa bulunamadı.",
"message.save-success": "Başarıyla kaydedildi.",
"message.share-url": "{target} için kullanılabilir anonim paylaşım adresidir.",
"message.track-stats": "{target} alanı adı istatistiklerini takip etmek için, aşağıdaki kodu web sitenizin {head} bloğuna yerleştirin.",
diff --git a/lang/zh-CN.json b/lang/zh-CN.json
index b8290d8c..1f21eb0f 100644
--- a/lang/zh-CN.json
+++ b/lang/zh-CN.json
@@ -12,12 +12,14 @@
"button.more": "更多",
"button.save": "保存",
"button.view-details": "查看更多",
+ "button.websites": "网站",
"footer.powered-by": "运行",
"header.nav.dashboard": "仪表板",
"header.nav.settings": "设置",
"label.administrator": "管理员",
"label.confirm-password": "确认密码",
"label.current-password": "目前密码",
+ "label.custom-range": "Custom range",
"label.domain": "域名",
"label.enable-share-url": "激活共享链接",
"label.invalid": "输入无效",
@@ -42,6 +44,7 @@
"message.failure": "出现错误.",
"message.incorrect-username-password": "用户名密码不正确.",
"message.no-data-available": "无可用数据.",
+ "message.page-not-found": "网页未找到.",
"message.save-success": "成功保存.",
"message.share-url": "这是 {target} 的共享链接.",
"message.track-stats": "把以下代码放到你的网站的{head}部分来收集{target}的数据.",
diff --git a/lib/array.js b/lib/array.js
new file mode 100644
index 00000000..5b9aa1f2
--- /dev/null
+++ b/lib/array.js
@@ -0,0 +1,11 @@
+export function chunk(arr, size) {
+ const chunks = [];
+
+ let index = 0;
+ while (index < arr.length) {
+ chunks.push(arr.slice(index, size + index));
+ index += size;
+ }
+
+ return chunks;
+}
diff --git a/lib/date.js b/lib/date.js
index 13c2e55f..363e68dd 100644
--- a/lib/date.js
+++ b/lib/date.js
@@ -4,6 +4,7 @@ import {
addHours,
addDays,
addMonths,
+ addYears,
subHours,
subDays,
startOfHour,
@@ -18,7 +19,8 @@ import {
endOfYear,
differenceInHours,
differenceInCalendarDays,
- differenceInMonths,
+ differenceInCalendarMonths,
+ differenceInCalendarYears,
} from 'date-fns';
export function getTimezone() {
@@ -85,10 +87,24 @@ export function getDateRange(value) {
}
}
+export function getDateRangeValues(startDate, endDate) {
+ let unit = 'year';
+ if (differenceInHours(endDate, startDate) <= 72) {
+ unit = 'hour';
+ } else if (differenceInCalendarDays(endDate, startDate) <= 90) {
+ unit = 'day';
+ } else if (differenceInCalendarMonths(endDate, startDate) <= 24) {
+ unit = 'month';
+ }
+
+ return { startDate: startOfDay(startDate), endDate: endOfDay(endDate), unit };
+}
+
const dateFuncs = {
hour: [differenceInHours, addHours, startOfHour],
day: [differenceInCalendarDays, addDays, startOfDay],
- month: [differenceInMonths, addMonths, startOfMonth],
+ month: [differenceInCalendarMonths, addMonths, startOfMonth],
+ year: [differenceInCalendarYears, addYears, startOfYear],
};
export function getDateArray(data, startDate, endDate, unit) {
@@ -98,11 +114,12 @@ export function getDateArray(data, startDate, endDate, unit) {
function findData(t) {
const x = data.find(e => {
- if (unit === 'day') {
- const [year, month, day] = e.t.split('-');
- return normalize(new Date(year, month - 1, day)).getTime() === t.getTime();
+ if (unit === 'hour') {
+ return normalize(new Date(e.t)).getTime() === t.getTime();
}
- return normalize(new Date(e.t)).getTime() === t.getTime();
+
+ const [year, month, day] = e.t.split('-');
+ return normalize(new Date(year, month - 1, day)).getTime() === t.getTime();
});
return x?.y || 0;
diff --git a/lib/lang.js b/lib/lang.js
index 4f7993ca..3f097a2e 100644
--- a/lib/lang.js
+++ b/lib/lang.js
@@ -10,7 +10,7 @@ import jaMessages from 'lang-compiled/ja-JP.json';
import esMXMessages from 'lang-compiled/es-MX.json';
export const messages = {
- en: enMessages,
+ 'en-US': enMessages,
'nl-NL': nlMessages,
'zh-CN': zhCNMessages,
'de-DE': deDEMessages,
@@ -21,7 +21,7 @@ export const messages = {
};
export const dateLocales = {
- en: enUS,
+ 'en-US': enUS,
'nl-NL': nl,
'zh-CN': zhCN,
'de-DE': de,
@@ -33,13 +33,13 @@ export const dateLocales = {
export const menuOptions = [
{ label: 'English', value: 'en', display: 'EN' },
- { label: '中文 (Chinese Simplified)', value: 'zh-CN', display: '中文' },
- { label: 'Nederlands (Dutch)', value: 'nl-NL', display: 'NL' },
- { label: 'Deutsch (German)', value: 'de-DE', display: 'DE' },
- { label: '日本語 (Japanese)', value: 'ja-JP', display: '日本語' },
- { label: 'Русский (Russian)', value: 'ru-RU', display: 'РУ' },
+ { label: '中文', value: 'zh-CN', display: 'CN' },
+ { label: 'Deutsch', value: 'de-DE', display: 'DE' },
+ { label: 'Español', value: 'es-MX', display: 'ES' },
+ { label: '日本語', value: 'ja-JP', display: 'JP' },
+ { label: 'Nederlands', value: 'nl-NL', display: 'NL' },
+ { label: 'Русский', value: 'ru-RU', display: 'RU' },
{ label: 'Turkish', value: 'tr-TR', display: 'TR' },
- { label: 'Español (Mexicano)', value: 'es-MX', display: 'ES' },
];
export function dateFormat(date, str, locale) {
diff --git a/package.json b/package.json
index 67418849..25c50c7d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.29.0",
+ "version": "0.32.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao
",
"license": "MIT",
@@ -23,10 +23,10 @@
"build-postgresql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.postgresql.prisma",
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
"build-lang": "npm-run-all format-lang compile-lang",
- "extract-lang": "formatjs extract {pages,components}/**/*.js --out-file lang/en-US.json",
+ "extract-lang": "formatjs extract {pages,components}/**/*.js --out-file build/messages.json",
"merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js",
- "compile-lang": "formatjs compile-folder --ast lang-formatted lang-compiled"
+ "compile-lang": "formatjs compile-folder --ast build lang-compiled"
},
"lint-staged": {
"**/*.js": [
diff --git a/pages/404.js b/pages/404.js
index 9cc200b5..4c85b10a 100644
--- a/pages/404.js
+++ b/pages/404.js
@@ -1,11 +1,14 @@
import React from 'react';
import Layout from 'components/layout/Layout';
+import { FormattedMessage } from 'react-intl';
export default function Custom404() {
return (
-
oops! page not found
+
+
+
);
diff --git a/pages/account.js b/pages/account.js
deleted file mode 100644
index a7ed6862..00000000
--- a/pages/account.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import Layout from 'components/layout/Layout';
-import AccountSettings from 'components/settings/AccountSettings';
-import useRequireLogin from 'hooks/useRequireLogin';
-
-export default function AccountPage() {
- const { loading } = useRequireLogin();
-
- if (loading) {
- return null;
- }
-
- return (
-
-
-
- );
-}
diff --git a/pages/api/account/[id].js b/pages/api/account/[id].js
index b8fe2245..c87f948b 100644
--- a/pages/api/account/[id].js
+++ b/pages/api/account/[id].js
@@ -9,24 +9,20 @@ export default async (req, res) => {
const { id } = req.query;
const user_id = +id;
- if (req.method === 'GET') {
- if (is_admin) {
- const account = await getAccountById(user_id);
-
- return ok(res, account);
- }
-
+ if (is_admin) {
return unauthorized(res);
}
+ if (req.method === 'GET') {
+ const account = await getAccountById(user_id);
+
+ return ok(res, account);
+ }
+
if (req.method === 'DELETE') {
- if (is_admin) {
- await deleteAccount(user_id);
+ await deleteAccount(user_id);
- return ok(res);
- }
-
- return unauthorized(res);
+ return ok(res);
}
return methodNotAllowed(res);
diff --git a/pages/api/account.js b/pages/api/account/index.js
similarity index 100%
rename from pages/api/account.js
rename to pages/api/account/index.js
diff --git a/pages/api/account/password.js b/pages/api/account/password.js
index 32f87960..c9c955fa 100644
--- a/pages/api/account/password.js
+++ b/pages/api/account/password.js
@@ -1,14 +1,18 @@
import { getAccountById, updateAccount } from 'lib/queries';
import { useAuth } from 'lib/middleware';
-import { badRequest, methodNotAllowed, ok } from 'lib/response';
+import { badRequest, methodNotAllowed, ok, unauthorized } from 'lib/response';
import { checkPassword, hashPassword } from 'lib/crypto';
export default async (req, res) => {
await useAuth(req, res);
- const { user_id } = req.auth;
+ const { user_id, is_admin } = req.auth;
const { current_password, new_password } = req.body;
+ if (is_admin) {
+ return unauthorized(res);
+ }
+
if (req.method === 'POST') {
const account = await getAccountById(user_id);
const valid = await checkPassword(current_password, account.password);
diff --git a/pages/api/website/[id]/index.js b/pages/api/website/[id].js
similarity index 100%
rename from pages/api/website/[id]/index.js
rename to pages/api/website/[id].js
diff --git a/pages/api/website/[id]/active.js b/pages/api/website/[id]/active.js
index 06731408..492074a9 100644
--- a/pages/api/website/[id]/active.js
+++ b/pages/api/website/[id]/active.js
@@ -1,11 +1,18 @@
import { getActiveVisitors } from 'lib/queries';
-import { ok } from 'lib/response';
+import { methodNotAllowed, ok } from 'lib/response';
+import { useAuth } from 'lib/middleware';
export default async (req, res) => {
- const { id } = req.query;
- const website_id = +id;
+ await useAuth(req, res);
- const result = await getActiveVisitors(website_id);
+ if (req.method === 'GET') {
+ const { id } = req.query;
+ const website_id = +id;
- return ok(res, result);
+ const result = await getActiveVisitors(website_id);
+
+ return ok(res, result);
+ }
+
+ return methodNotAllowed(res);
};
diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js
index d7ead27c..143c745a 100644
--- a/pages/api/website/[id]/events.js
+++ b/pages/api/website/[id]/events.js
@@ -1,21 +1,28 @@
import moment from 'moment-timezone';
import { getEvents } from 'lib/queries';
-import { ok, badRequest } from 'lib/response';
+import { ok, badRequest, methodNotAllowed } from 'lib/response';
+import { useAuth } from 'lib/middleware';
-const unitTypes = ['month', 'hour', 'day'];
+const unitTypes = ['year', 'month', 'hour', 'day'];
export default async (req, res) => {
- const { id, start_at, end_at, unit, tz } = req.query;
+ await useAuth(req, res);
- if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
- return badRequest(res);
+ if (req.method === 'GET') {
+ const { id, start_at, end_at, unit, tz } = req.query;
+
+ if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
+ return badRequest(res);
+ }
+
+ const websiteId = +id;
+ const startDate = new Date(+start_at);
+ const endDate = new Date(+end_at);
+
+ const events = await getEvents(websiteId, startDate, endDate, tz, unit);
+
+ return ok(res, events);
}
- const websiteId = +id;
- const startDate = new Date(+start_at);
- const endDate = new Date(+end_at);
-
- const events = await getEvents(websiteId, startDate, endDate, tz, unit);
-
- return ok(res, events);
+ return methodNotAllowed(res);
};
diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js
index 4b0d71e1..70a9d1e6 100644
--- a/pages/api/website/[id]/metrics.js
+++ b/pages/api/website/[id]/metrics.js
@@ -1,18 +1,25 @@
import { getMetrics } from 'lib/queries';
-import { ok } from 'lib/response';
+import { methodNotAllowed, ok } from 'lib/response';
+import { useAuth } from 'lib/middleware';
export default async (req, res) => {
- const { id, start_at, end_at } = req.query;
- const websiteId = +id;
- const startDate = new Date(+start_at);
- const endDate = new Date(+end_at);
+ await useAuth(req, res);
- const metrics = await getMetrics(websiteId, startDate, endDate);
+ if (req.method === 'GET') {
+ const { id, start_at, end_at } = req.query;
+ const websiteId = +id;
+ const startDate = new Date(+start_at);
+ const endDate = new Date(+end_at);
- const stats = Object.keys(metrics[0]).reduce((obj, key) => {
- obj[key] = Number(metrics[0][key]) || 0;
- return obj;
- }, {});
+ const metrics = await getMetrics(websiteId, startDate, endDate);
- return ok(res, stats);
+ const stats = Object.keys(metrics[0]).reduce((obj, key) => {
+ obj[key] = Number(metrics[0][key]) || 0;
+ return obj;
+ }, {});
+
+ return ok(res, stats);
+ }
+
+ return methodNotAllowed(res);
};
diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js
index c90e1c6b..398e52e4 100644
--- a/pages/api/website/[id]/pageviews.js
+++ b/pages/api/website/[id]/pageviews.js
@@ -1,24 +1,31 @@
import moment from 'moment-timezone';
import { getPageviews } from 'lib/queries';
-import { ok, badRequest } from 'lib/response';
+import { ok, badRequest, methodNotAllowed } from 'lib/response';
+import { useAuth } from 'lib/middleware';
-const unitTypes = ['month', 'hour', 'day'];
+const unitTypes = ['year', 'month', 'hour', 'day'];
export default async (req, res) => {
- const { id, start_at, end_at, unit, tz } = req.query;
+ await useAuth(req, res);
- if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
- return badRequest(res);
+ if (req.method === 'GET') {
+ const { id, start_at, end_at, unit, tz } = req.query;
+
+ if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
+ return badRequest(res);
+ }
+
+ const websiteId = +id;
+ const startDate = new Date(+start_at);
+ const endDate = new Date(+end_at);
+
+ const [pageviews, uniques] = await Promise.all([
+ getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
+ getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
+ ]);
+
+ return ok(res, { pageviews, uniques });
}
- const websiteId = +id;
- const startDate = new Date(+start_at);
- const endDate = new Date(+end_at);
-
- const [pageviews, uniques] = await Promise.all([
- getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
- getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
- ]);
-
- return ok(res, { pageviews, uniques });
+ return methodNotAllowed(res);
};
diff --git a/pages/api/website/[id]/rankings.js b/pages/api/website/[id]/rankings.js
index 4e613d0d..158a4bc2 100644
--- a/pages/api/website/[id]/rankings.js
+++ b/pages/api/website/[id]/rankings.js
@@ -1,6 +1,7 @@
import { getRankings } from 'lib/queries';
-import { ok, badRequest } from 'lib/response';
-import { DOMAIN_REGEX } from '../../../../lib/constants';
+import { ok, badRequest, methodNotAllowed } from 'lib/response';
+import { DOMAIN_REGEX } from 'lib/constants';
+import { useAuth } from 'lib/middleware';
const sessionColumns = ['browser', 'os', 'device', 'country'];
const pageviewColumns = ['url', 'referrer'];
@@ -25,29 +26,35 @@ function getColumn(type) {
}
export default async (req, res) => {
- const { id, type, start_at, end_at, domain } = req.query;
- const websiteId = +id;
- const startDate = new Date(+start_at);
- const endDate = new Date(+end_at);
+ await useAuth(req, res);
- if (
- type !== 'event' &&
- !sessionColumns.includes(type) &&
- !pageviewColumns.includes(type) &&
- domain &&
- DOMAIN_REGEX.test(domain)
- ) {
- return badRequest(res);
+ if (req.method === 'GET') {
+ const { id, type, start_at, end_at, domain } = req.query;
+ const websiteId = +id;
+ const startDate = new Date(+start_at);
+ const endDate = new Date(+end_at);
+
+ if (
+ type !== 'event' &&
+ !sessionColumns.includes(type) &&
+ !pageviewColumns.includes(type) &&
+ domain &&
+ DOMAIN_REGEX.test(domain)
+ ) {
+ return badRequest(res);
+ }
+
+ const rankings = await getRankings(
+ websiteId,
+ startDate,
+ endDate,
+ getColumn(type),
+ getTable(type),
+ domain,
+ );
+
+ return ok(res, rankings);
}
- const rankings = await getRankings(
- websiteId,
- startDate,
- endDate,
- getColumn(type),
- getTable(type),
- domain,
- );
-
- return ok(res, rankings);
+ return methodNotAllowed(res);
};
diff --git a/pages/api/website.js b/pages/api/website/index.js
similarity index 100%
rename from pages/api/website.js
rename to pages/api/website/index.js
diff --git a/pages/api/websites.js b/pages/api/websites.js
index af83c13e..b4633950 100644
--- a/pages/api/websites.js
+++ b/pages/api/websites.js
@@ -1,14 +1,19 @@
import { getUserWebsites } from 'lib/queries';
import { useAuth } from 'lib/middleware';
-import { ok, methodNotAllowed } from 'lib/response';
+import { ok, methodNotAllowed, unauthorized } from 'lib/response';
export default async (req, res) => {
await useAuth(req, res);
- const { user_id } = req.auth;
+ const { user_id, is_admin } = req.auth;
+ const { userId } = req.query;
if (req.method === 'GET') {
- const websites = await getUserWebsites(user_id);
+ if (userId && !is_admin) {
+ return unauthorized(res);
+ }
+
+ const websites = await getUserWebsites(+userId || user_id);
return ok(res, websites);
}
diff --git a/pages/dashboard.js b/pages/dashboard/[[...id]].js
similarity index 67%
rename from pages/dashboard.js
rename to pages/dashboard/[[...id]].js
index aeb9d772..90f08492 100644
--- a/pages/dashboard.js
+++ b/pages/dashboard/[[...id]].js
@@ -1,10 +1,14 @@
import React from 'react';
+import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
import WebsiteList from 'components/WebsiteList';
import useRequireLogin from 'hooks/useRequireLogin';
export default function DashboardPage() {
const { loading } = useRequireLogin();
+ const router = useRouter();
+ const { id } = router.query;
+ const userId = id?.[0];
if (loading) {
return null;
@@ -12,7 +16,7 @@ export default function DashboardPage() {
return (
-
+
);
}
diff --git a/scripts/format-lang.js b/scripts/format-lang.js
index f26c0249..5b03239e 100644
--- a/scripts/format-lang.js
+++ b/scripts/format-lang.js
@@ -3,7 +3,7 @@ const path = require('path');
const prettier = require('prettier');
const src = path.resolve(__dirname, '../lang');
-const dest = path.resolve(__dirname, '../lang-formatted');
+const dest = path.resolve(__dirname, '../build');
const files = fs.readdirSync(src);
if (!fs.existsSync(dest)) {
diff --git a/scripts/merge-lang.js b/scripts/merge-lang.js
index 885bf759..f1b86df9 100644
--- a/scripts/merge-lang.js
+++ b/scripts/merge-lang.js
@@ -1,11 +1,11 @@
const fs = require('fs');
const path = require('path');
const prettier = require('prettier');
-const root = require('../lang/en-US.json');
+const messages = require('../build/messages.json');
-const dir = path.resolve(__dirname, '../lang');
-const files = fs.readdirSync(dir);
-const keys = Object.keys(root).sort();
+const dest = path.resolve(__dirname, '../lang');
+const files = fs.readdirSync(dest);
+const keys = Object.keys(messages).sort();
files.forEach(file => {
const lang = require(`../lang/${file}`);
@@ -15,7 +15,7 @@ files.forEach(file => {
const merged = keys.reduce((obj, key) => {
const message = lang[key];
- obj[key] = message || root[key];
+ obj[key] = message || messages[key].defaultMessage;
if (!message) {
console.log(`* Added key ${key}`);
@@ -26,5 +26,5 @@ files.forEach(file => {
const json = prettier.format(JSON.stringify(merged), { parser: 'json' });
- fs.writeFileSync(path.resolve(dir, file), json);
+ fs.writeFileSync(path.resolve(dest, file), json);
});
diff --git a/styles/index.css b/styles/index.css
index c8e1709e..5435364f 100644
--- a/styles/index.css
+++ b/styles/index.css
@@ -15,7 +15,10 @@ body {
.zh-CN {
font-family: 'Noto Sans SC', sans-serif !important;
- font-size: 110%;
+}
+
+.ja-JP {
+ font-family: 'Noto Sans JP', sans-serif !important;
}
*,
@@ -40,6 +43,10 @@ h6 {
height: 100%;
}
+#__modals {
+ z-index: 10;
+}
+
button,
input,
select {