Bladeren bron

rebase onto v3

marvin-wtt 1 jaar geleden
bovenliggende
commit
a3b7a5a350

+ 2 - 0
.example.env

@@ -11,12 +11,14 @@ DEFAULT_DOMAIN=localhost:3000
 LINK_LENGTH=6
 
 # Postgres database credential details
+DB_CLIENT=postgresql
 DB_HOST=localhost
 DB_PORT=5432
 DB_NAME=postgres
 DB_USER=
 DB_PASSWORD=
 DB_SSL=false
+DB_FILENAME=
 
 # Redis host and port
 REDIS_HOST=127.0.0.1

+ 13 - 0
server/env.js

@@ -1,11 +1,24 @@
 require("dotenv").config();
 const { cleanEnv, num, str, bool } = require("envalid");
 
+const supportedDBClients = [
+  "pg",
+  "pg-native",
+  "sqlite3",
+  "better-sqlite3",
+  "mysql",
+  "mysql2",
+  "oracledb",
+  "tedious"
+];
+
 const env = cleanEnv(process.env, {
   PORT: num({ default: 3000 }),
   SITE_NAME: str({ example: "Kutt" }),
   DEFAULT_DOMAIN: str({ example: "kutt.it" }),
   LINK_LENGTH: num({ default: 6 }),
+  DB_CLIENT: str({ choices: supportedDBClients, default: "pg" }),
+  DB_FILENAME: str(),
   DB_HOST: str({ default: "localhost" }),
   DB_PORT: num({ default: 5432 }),
   DB_NAME: str({ default: "postgres" }),

+ 2 - 1
server/knex.js

@@ -3,8 +3,9 @@ const knex = require("knex");
 const env = require("./env");
 
 const db = knex({
-  client: "postgres",
+  client: env.DB_CLIENT,
   connection: {
+    filename: env.DB_FILENAME,
     host: env.DB_HOST,
     port: env.DB_PORT,
     database: env.DB_NAME,

+ 24 - 24
server/migrations/20200211220920_constraints.js

@@ -9,30 +9,30 @@ async function up(knex) {
   await models.createVisitTable(knex);
 
   await Promise.all([
-    knex.raw(`
-      ALTER TABLE domains
-      DROP CONSTRAINT domains_user_id_foreign,
-      ADD CONSTRAINT domains_user_id_foreign
-        FOREIGN KEY (user_id) 
-        REFERENCES users (id)
-        ON DELETE SET NULL;
-    `),
-    knex.raw(`
-      ALTER TABLE links
-      DROP CONSTRAINT links_user_id_foreign,
-      ADD CONSTRAINT links_user_id_foreign
-        FOREIGN KEY (user_id)
-        REFERENCES users (id)
-        ON DELETE CASCADE;
-    `),
-    knex.raw(`
-      ALTER TABLE visits
-      DROP CONSTRAINT visits_link_id_foreign,
-      ADD CONSTRAINT visits_link_id_foreign
-        FOREIGN KEY (link_Id)
-        REFERENCES links (id)
-        ON DELETE CASCADE;
-    `)
+    knex.schema.alterTable('domains', (table) => {
+      table.dropForeign('ignored', 'domains_user_id_foreign');
+      table.foreign('user_id')
+        .references('id')
+        .inTable('users')
+        .onDelete('SET NULL')
+        .withKeyName('domains_user_id_foreign');
+    }),
+    knex.schema.alterTable('links', (table) => {
+      table.dropForeign('ignored', 'links_user_id_foreign');
+      table.foreign('user_id')
+        .references('id')
+        .inTable('users')
+        .onDelete('CASCADE')
+        .withKeyName('links_user_id_foreign');
+    }),
+    knex.schema.alterTable('visits', (table) => {
+      table.dropForeign('ignored', 'visits_link_id_foreign');
+      table.foreign('link_Id')
+        .references('id')
+        .inTable('links')
+        .onDelete('CASCADE')
+        .withKeyName('visits_link_id_foreign');
+    }),
   ]);
 }
 

+ 11 - 8
server/migrations/20200510140704_domains.js

@@ -9,14 +9,17 @@ async function up(knex) {
   await models.createVisitTable(knex);
 
   await Promise.all([
-    knex.raw(`
-      ALTER TABLE domains
-      DROP CONSTRAINT IF EXISTS domains_user_id_unique
-    `),
-    knex.raw(`
-      ALTER TABLE domains
-      ADD COLUMN IF NOT EXISTS uuid UUID DEFAULT uuid_generate_v4()
-    `)
+    async () => {
+      try {
+        await knex.schema.alterTable("domains", (table) => {
+          table.dropUnique([], "domains_user_id_unique");
+        });
+      } catch (ignored) {
+      }
+    },
+    await knex.schema.alterTable("domains", (table) => {
+      table.uuid("uuid").defaultTo(knex.fn.uuid());
+    }),
   ]);
 }
 

+ 1 - 1
server/models/domain.model.js

@@ -25,7 +25,7 @@ async function createDomainTable(knex) {
       table
         .uuid("uuid")
         .notNullable()
-        .defaultTo(knex.raw("uuid_generate_v4()"));
+        .defaultTo(knex.fn.uuid());
       table.timestamps(false, true);
     });
   }

+ 1 - 2
server/models/link.model.js

@@ -4,7 +4,6 @@ async function createLinkTable(knex) {
   if (!hasTable) {
     await knex.schema.raw('create extension if not exists "uuid-ossp"');
     await knex.schema.createTable("links", table => {
-      knex.raw('create extension if not exists "uuid-ossp"');
       table.increments("id").primary();
       table.string("address").notNullable();
       table.string("description");
@@ -43,7 +42,7 @@ async function createLinkTable(knex) {
       table
         .uuid("uuid")
         .notNullable()
-        .defaultTo(knex.raw("uuid_generate_v4()"));
+        .defaultTo(knex.fn.uuid());
     });
   }
 }

+ 18 - 9
server/queries/visit.queries.js

@@ -11,13 +11,22 @@ async function add(params) {
     referrer: params.referrer.toLowerCase()
   };
 
-  const visit = await knex("visits")
+  const truncatedNow = new Date();
+  truncatedNow.setMinutes(0, 0, 0);
+
+  // Create a subquery first that truncates the
+  const subquery = knex("visits")
+    .select("visits.*")
+    .select({
+      created_at_hours: utils.knexUtils(knex).truncatedTimestamp("created_at", "hour")
+    })
     .where({ link_id: params.id })
-    .andWhere(
-      knex.raw("date_trunc('hour', created_at) = date_trunc('hour', ?)", [
-        knex.fn.now()
-      ])
-    )
+    .as("subquery");
+
+  const visit = await knex
+    .select("*")
+    .from(subquery)
+    .where("created_at_hours", "=", truncatedNow.toISOString())
     .first();
 
   if (visit) {
@@ -49,7 +58,7 @@ async function add(params) {
   }
 
   return visit;
-};
+}
 
 async function find(match, total) {
   if (match.link_id) {
@@ -176,5 +185,5 @@ async function find(match, total) {
 
 module.exports = {
   add,
-  find,
-}
+  find
+};

+ 56 - 0
server/utils/knex.js

@@ -0,0 +1,56 @@
+
+function knexUtils(knex) {
+
+  function truncatedTimestamp(columnName, precision = 'hour') {
+    switch (knex.client.driverName) {
+      case 'sqlite3':
+      case 'better-sqlite3':
+        // SQLite uses strftime for date truncation
+        const sqliteFormats = {
+          second: '%Y-%m-%d %H:%M:%S',
+          minute: '%Y-%m-%d %H:%M:00',
+          hour: '%Y-%m-%d %H:00:00',
+          day: '%Y-%m-%d 00:00:00',
+        };
+        return knex.raw(`strftime('${sqliteFormats[precision]}', ${columnName})`); // Default to 'hour'
+      case 'mssql':
+        // For MSSQL, we can use FORMAT or CONVERT to truncate the timestamp
+        const mssqlFormats = {
+          second: 'yyyy-MM-dd HH:mm:ss',
+          minute: 'yyyy-MM-dd HH:mm:00',
+          hour: 'yyyy-MM-dd HH:00:00',
+          day: 'yyyy-MM-dd 00:00:00',
+        };
+        return knex.raw(`FORMAT(${columnName}, '${mssqlFormats[precision]}'`);
+      case 'pg':
+      case 'pgnative':
+      case 'cockroachdb':
+        // PostgreSQL has the `date_trunc` function, which is ideal for this task
+        return knex.raw(`date_trunc(?, ${columnName})`, [precision]);
+      case 'oracle':
+      case 'oracledb':
+        // Oracle truncates dates using the `TRUNC` function
+        return knex.raw(`TRUNC(${columnName}, ?)`, [precision]);
+      case 'mysql':
+      case 'mysql2':
+        // MySQL can use the DATE_FORMAT function to truncate
+        const mysqlFormats = {
+          second: '%Y-%m-%d %H:%i:%s',
+          minute: '%Y-%m-%d %H:%i:00',
+          hour: '%Y-%m-%d %H:00:00',
+          day: '%Y-%m-%d 00:00:00',
+        };
+        return knex.raw(`DATE_FORMAT(${columnName}, '${mysqlFormats[precision]}')`);
+      default:
+        throw new Error(`${this.client.driverName} does not support timestamp truncation with precision`);
+    }
+  }
+
+  return {
+    truncatedTimestamp
+  }
+}
+
+module.exports = {
+  knexUtils
+}

+ 2 - 0
server/utils/utils.js

@@ -4,6 +4,7 @@ const JWT = require("jsonwebtoken");
 const path = require("path");
 const hbs = require("hbs");
 const ms = require("ms");
+const knexUtils = require('./knex')
 
 const env = require("../env");
 
@@ -305,4 +306,5 @@ module.exports = {
   sleep,
   statsObjectToArray,
   urlRegex,
+  ...knexUtils,
 }