Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecao committed Jul 17, 2020
0 parents commit f7f0c05
Show file tree
Hide file tree
Showing 27 changed files with 13,028 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "prettier/react"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"react/react-in-jsx-scope": "off"
},
"globals": {
"React": "writable"
}
}
34 changes: 34 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
.idea
*.iml
.env
.env*.local

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env
.env.development.local
.env.test.local
.env.production.local
7 changes: 7 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"arrowParens": "avoid",
"endOfLine": "lf",
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
17 changes: 17 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": [
"stylelint-config-recommended",
"stylelint-config-css-modules",
"stylelint-config-prettier"
],
"rules": {
"no-descending-specificity": null,
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["global", "horizontal", "vertical"]
}
]
},
"ignoreFiles": ["**/*.js"]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
umami - deliciously simple web stats
5 changes: 5 additions & 0 deletions components/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function Footer() {
return <footer className="container">Footer</footer>;
}
7 changes: 7 additions & 0 deletions components/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

export default function Header() {
return <header className="container">
<h1>umami</h1>
</header>;
}
23 changes: 23 additions & 0 deletions components/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import Head from 'next/head';
import Header from 'components/header';
import Footer from 'components/footer';

export default function Layout({ title, children }) {
return (
<>
<Head>
<title>umami{title && ` - ${title}`}</title>
<link rel="icon" href="/favicon.ico" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400&display=swap"
rel="stylesheet"
/>
<script async defer data-website-id="865234ad-6a92-11e7-8846-b05adad3f099" src="/umami.js" />
</Head>
<Header />
<main className="container">{children}</main>
<Footer />
</>
);
}
5 changes: 5 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "."
}
}
65 changes: 65 additions & 0 deletions lib/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function runQuery(query) {
return query
.catch(e => {
throw e;
})
.finally(async () => {
await prisma.disconnect();
});
}

export async function getWebsite(website_id) {
return runQuery(
prisma.website.findOne({
where: {
website_id,
},
}),
);
}

export async function createSession(website_id, session_id, data) {
await runQuery(
prisma.session.create({
data: {
session_id,
website: {
connect: {
website_id,
},
},
...data,
},
}),
);
}

export async function getSession(session_id) {
return runQuery(
prisma.session.findOne({
where: {
session_id,
},
}),
);
}

export async function savePageView(session_id, url, referrer) {
return runQuery(
prisma.pageview.create({
data: {
session: {
connect: {
session_id,
},
},
url,
referrer,
},
}),
);
}
72 changes: 72 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import crypto from 'crypto';
import { v5 as uuid } from 'uuid';
import requestIp from 'request-ip';
import { browserName, detectOS } from 'detect-browser';

export function md5(s) {
return crypto.createHash('md5').update(s).digest('hex');
}

export function hash(s) {
return uuid(s, md5(process.env.HASH_SALT));
}

export function getIpAddress(req) {
if (req.headers['cf-connecting-ip']) {
return req.headers['cf-connecting-ip'];
}
return requestIp.getClientIp(req);
}

export function getDevice(req) {
const userAgent = req.headers['user-agent'];
const browser = browserName(userAgent);
const os = detectOS(userAgent);

return { userAgent, browser, os };
}

export function getCountry(req) {
return req.headers['cf-ipcountry'];
}

export function parseSessionRequest(req) {
const ip = getIpAddress(req);
const { website_id, screen, language } = JSON.parse(req.body);
const { userAgent, browser, os } = getDevice(req);
const country = getCountry(req);
const session_id = hash(`${website_id}${ip}${userAgent}${os}`);

return {
website_id,
session_id,
browser,
os,
screen,
language,
country,
};
}

export function parseCollectRequest(req) {
const { type, payload } = JSON.parse(req.body);

if (payload.session) {
const {
url,
referrer,
session: { website_id, session_id, time, hash: validationHash },
} = payload;

if (hash(`${website_id}${session_id}${time}`) === validationHash) {
return {
type,
session_id,
url,
referrer,
};
}
}

return null;
}
15 changes: 15 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require('dotenv').config();

module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
issuer: {
test: /\.js$/,
},
use: ['@svgr/webpack'],
});

return config;
},
};
67 changes: 67 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "umami",
"version": "0.1.0",
"description": "Deliciously simple website analytics",
"main": "index.js",
"author": "Mike Cao",
"license": "MIT",
"scripts": {
"dev": "next dev -p 8000",
"build": "next build",
"start": "next start",
"build-script": "rollup -c"
},
"lint-staged": {
"**/*.js": [
"prettier --write"
],
"**/*.css": [
"stylelint --fix",
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"dependencies": {
"@prisma/client": "2.2.2",
"classnames": "^2.2.6",
"date-fns": "^2.14.0",
"detect-browser": "^5.1.1",
"dotenv": "^8.2.0",
"next": "9.3.5",
"node-fetch": "^2.6.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"request-ip": "^2.1.3",
"uuid": "^8.2.0",
"whatwg-fetch": "^3.2.0"
},
"devDependencies": {
"@prisma/cli": "2.2.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@svgr/webpack": "^5.4.0",
"eslint": "^7.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.20.0",
"eslint-plugin-react-hooks": "^4.0.4",
"husky": "^4.2.5",
"less": "^3.11.3",
"lint-staged": "^10.2.9",
"postcss-flexbugs-fixes": "^4.2.1",
"postcss-import": "^12.0.1",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.0.5",
"prettier-eslint": "^10.1.1",
"rollup": "^2.21.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^6.1.0",
"stylelint": "^13.6.0",
"stylelint-config-css-modules": "^2.2.0",
"stylelint-config-prettier": "^8.0.1",
"stylelint-config-recommended": "^3.0.0"
}
}
10 changes: 10 additions & 0 deletions pages/404.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import Layout from 'components/layout';

export default function Custom404() {
return (
<Layout title="404 - Page Not Found">
<h1>oops</h1>
</Layout>
);
}
7 changes: 7 additions & 0 deletions pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import 'styles/index.css';
import 'styles/bootstrap-grid.css';

export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
16 changes: 16 additions & 0 deletions pages/api/collect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { parseCollectRequest } from 'lib/utils';
import { savePageView } from '../../lib/db';

export default async (req, res) => {
const values = parseCollectRequest(req);

if (values) {
const { type, session_id, url, referrer } = values;

if (type === 'pageview') {
await savePageView(session_id, url, referrer);
}
}

res.status(200).json({ status: 'ok' });
};
Loading

0 comments on commit f7f0c05

Please sign in to comment.