mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
143 changed files with 54800 additions and 1266 deletions
@ -0,0 +1,24 @@
|
||||
spec: |
||||
name: nocodb |
||||
services: |
||||
- name: nocodb |
||||
image: |
||||
registry_type: DOCKER_HUB |
||||
registry: nocodb |
||||
repository: nocodb |
||||
tag: latest |
||||
run_command: "./server/scripts/digitalocean-postbuild.sh" |
||||
instance_size_slug: "basic-s" |
||||
health_check: |
||||
initial_delay_seconds: 10 |
||||
http_path: /api/health |
||||
envs: |
||||
- key: NODE_ENV |
||||
value: "production" |
||||
- key: DATABASE_URL |
||||
scope: RUN_TIME |
||||
value: ${postgres.DATABASE_URL} |
||||
databases: |
||||
- name: postgres |
||||
engine: PG |
||||
production: false |
@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Drop metadb" type="NodeJSConfigurationType" path-to-js-file="$PROJECT_DIR$/packages/nocodb/src/run/deleteMetaDb.js" working-dir="$PROJECT_DIR$/packages/nocodb/src/run"> |
||||
<configuration default="false" name="Drop metadb" type="NodeJSConfigurationType" path-to-js-file="deleteMetaDb.js" working-dir="$PROJECT_DIR$/packages/nocodb/src/run"> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -1,12 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Run GUI" type="js.build_tools.npm"> |
||||
<package-json value="$PROJECT_DIR$/packages/nc-gui/package.json" /> |
||||
<command value="run" /> |
||||
<scripts> |
||||
<script value="dev" /> |
||||
</scripts> |
||||
<node-interpreter value="project" /> |
||||
<envs /> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Start::IDE" type="CompoundRunConfigurationType"> |
||||
<toRun name="Run::Backend" type="js.build_tools.npm" /> |
||||
<toRun name="Run::Frontend" type="js.build_tools.npm" /> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -0,0 +1,29 @@
|
||||
<script setup lang="ts"> |
||||
import { useSidebar } from '#imports' |
||||
|
||||
const rightSidebar = useSidebar('nc-right-sidebar') |
||||
const leftSidebar = useSidebar('nc-left-sidebar') |
||||
|
||||
const isSidebarsOpen = computed({ |
||||
get: () => rightSidebar.isOpen.value || leftSidebar.isOpen.value, |
||||
set: (value) => { |
||||
rightSidebar.toggle(value) |
||||
leftSidebar.toggle(value) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-tooltip> |
||||
<!-- todo: i18n --> |
||||
<template #title> {{ isSidebarsOpen ? 'Full width' : 'Exit full width' }}</template> |
||||
<div |
||||
v-e="['c:toolbar:fullscreen']" |
||||
class="nc-fullscreen-btn cursor-pointer flex align-center self-center px-2 py-2 mr-2" |
||||
@click="isSidebarsOpen = !isSidebarsOpen" |
||||
> |
||||
<IcTwotoneWidthFull v-if="isSidebarsOpen" class="text-gray-300" /> |
||||
<IcTwotoneWidthNormal v-else class="text-gray-300" /> |
||||
</div> |
||||
</a-tooltip> |
||||
</template> |
@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup> |
||||
import { onKeyStroke } from '@vueuse/core' |
||||
|
||||
interface Props { |
||||
// Key to be pressed on hover to trigger the tooltip |
||||
modifierKey?: string |
||||
wrapperClass?: string |
||||
} |
||||
|
||||
const { modifierKey } = defineProps<Props>() |
||||
|
||||
const showTooltip = ref(false) |
||||
const isMouseOver = ref(false) |
||||
|
||||
if (modifierKey) { |
||||
onKeyStroke(modifierKey, (e) => { |
||||
e.preventDefault() |
||||
if (modifierKey && isMouseOver.value) { |
||||
showTooltip.value = true |
||||
} |
||||
}) |
||||
} |
||||
|
||||
watch(isMouseOver, (val) => { |
||||
if (!val) { |
||||
showTooltip.value = false |
||||
} |
||||
|
||||
// Show tooltip on mouseover if no modifier key is provided |
||||
if (val && !modifierKey) { |
||||
showTooltip.value = true |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-tooltip v-model:visible="showTooltip" :trigger="[]"> |
||||
<template #title> |
||||
<slot name="title" /> |
||||
</template> |
||||
<div class="w-full" :class="wrapperClass" @mouseenter="isMouseOver = true" @mouseleave="isMouseOver = false"> |
||||
<slot /> |
||||
</div> |
||||
</a-tooltip> |
||||
</template> |
@ -0,0 +1,26 @@
|
||||
import { useClipboard } from '#imports' |
||||
|
||||
export const useCopy = () => { |
||||
/** fallback for copy if clipboard api is not supported */ |
||||
const copyFallback = (text: string) => { |
||||
const textAreaEl = document.createElement('textarea') |
||||
textAreaEl.innerHTML = text |
||||
document.body.appendChild(textAreaEl) |
||||
textAreaEl.select() |
||||
const result = document.execCommand('copy') |
||||
document.body.removeChild(textAreaEl) |
||||
return result |
||||
} |
||||
|
||||
const { copy: _copy, isSupported } = useClipboard() |
||||
|
||||
const copy = async (text: string) => { |
||||
if (isSupported) { |
||||
await _copy(text) |
||||
} else { |
||||
copyFallback(text) |
||||
} |
||||
} |
||||
|
||||
return { copy } |
||||
} |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Stores all currently created store instances |
||||
*/ |
||||
export class MemStorage<T = any> { |
||||
public currentId = 0 |
||||
public items = new Map<string, T>() |
||||
static instance: MemStorage |
||||
|
||||
public static getInstance(): MemStorage { |
||||
if (!MemStorage.instance) { |
||||
MemStorage.instance = new MemStorage() |
||||
} |
||||
|
||||
return MemStorage.instance |
||||
} |
||||
|
||||
public set(id: string, item: T) { |
||||
return this.items.set(id, item) |
||||
} |
||||
|
||||
public get(id: string) { |
||||
return this.items.get(id) |
||||
} |
||||
|
||||
public has(id: string) { |
||||
return this.items.has(id) |
||||
} |
||||
|
||||
public remove(id: string) { |
||||
return this.items.delete(id) |
||||
} |
||||
|
||||
public getId(prefix?: string) { |
||||
return `${prefix}${this.currentId++}` |
||||
} |
||||
} |
@ -0,0 +1,70 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"skipLibCheck": true, |
||||
"composite": true, |
||||
"target": "es2017", |
||||
"outDir": "build/main", |
||||
"rootDir": "src", |
||||
"moduleResolution": "node", |
||||
"module": "commonjs", |
||||
"declaration": true, |
||||
"inlineSourceMap": true, |
||||
"esModuleInterop": true |
||||
/* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||
"allowJs": false, |
||||
// "strict": true /* Enable all strict type-checking options. */, |
||||
|
||||
/* Strict Type-Checking Options */ |
||||
// "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, |
||||
// "strictNullChecks": true /* Enable strict null checks. */, |
||||
// "strictFunctionTypes": true /* Enable strict checking of function types. */, |
||||
// "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, |
||||
// "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, |
||||
// "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, |
||||
"resolveJsonModule": true, |
||||
/* Additional Checks */ |
||||
"noUnusedLocals": false |
||||
/* Report errors on unused locals. */, |
||||
"noUnusedParameters": false |
||||
/* Report errors on unused parameters. */, |
||||
"noImplicitReturns": false |
||||
/* Report error when not all code paths in function return a value. */, |
||||
"noFallthroughCasesInSwitch": false |
||||
/* Report errors for fallthrough cases in switch statement. */, |
||||
/* Debugging Options */ |
||||
"traceResolution": false |
||||
/* Report module resolution log messages. */, |
||||
"listEmittedFiles": false |
||||
/* Print names of generated files part of the compilation. */, |
||||
"listFiles": false |
||||
/* Print names of files part of the compilation. */, |
||||
"pretty": true |
||||
/* Stylize errors and messages using color and context. */, |
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, |
||||
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, |
||||
|
||||
"lib": [ |
||||
"es2017" |
||||
], |
||||
"types": [ |
||||
"mocha", "node" |
||||
], |
||||
"typeRoots": [ |
||||
"node_modules/@types", |
||||
"src/types" |
||||
] |
||||
}, |
||||
"include": [ |
||||
"src/**/*.ts", |
||||
// "src/lib/xgene/migrations/*.js", |
||||
"src/**/*.json" |
||||
], |
||||
"exclude": [ |
||||
"node_modules/**", |
||||
"node_modules", |
||||
"../../../xc-lib-private/**", |
||||
"../../../xc-lib-private" |
||||
], |
||||
"compileOnSave": false |
||||
} |
@ -0,0 +1,34 @@
|
||||
import Knex from 'knex'; |
||||
import { MetaTable } from '../../utils/globals'; |
||||
|
||||
const up = async (knex: Knex) => { |
||||
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => { |
||||
table.text('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => { |
||||
table.text('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.GRID_VIEW, (table) => { |
||||
table.text('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.GALLERY_VIEW, (table) => { |
||||
table.text('meta'); |
||||
}); |
||||
}; |
||||
|
||||
const down = async (knex) => { |
||||
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => { |
||||
table.dropColumns('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => { |
||||
table.dropColumns('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.GRID_VIEW, (table) => { |
||||
table.dropColumns('meta'); |
||||
}); |
||||
await knex.schema.alterTable(MetaTable.GALLERY_VIEW, (table) => { |
||||
table.dropColumns('meta'); |
||||
}); |
||||
}; |
||||
|
||||
export { up, down }; |
@ -0,0 +1,19 @@
|
||||
export const serializeJSON = (data: string | Record<string, any>) => { |
||||
// if already in string format ignore stringify
|
||||
if (typeof data === 'string') { |
||||
return data; |
||||
} |
||||
return JSON.stringify(data); |
||||
}; |
||||
|
||||
export const deserializeJSON = (data: string | Record<string, any>) => { |
||||
// if already in object format ignore parse
|
||||
if (typeof data === 'object') { |
||||
return data ?? {}; |
||||
} |
||||
try { |
||||
return JSON.parse(data) ?? {}; |
||||
} catch (e) { |
||||
return {}; |
||||
} |
||||
}; |
@ -0,0 +1,658 @@
|
||||
-- Sakila Sample Database Schema |
||||
-- Version 1.2 |
||||
|
||||
-- Copyright (c) 2006, 2019, Oracle and/or its affiliates. |
||||
|
||||
-- Redistribution and use in source and binary forms, with or without |
||||
-- modification, are permitted provided that the following conditions are |
||||
-- met: |
||||
|
||||
-- * Redistributions of source code must retain the above copyright notice, |
||||
-- this list of conditions and the following disclaimer. |
||||
-- * Redistributions in binary form must reproduce the above copyright |
||||
-- notice, this list of conditions and the following disclaimer in the |
||||
-- documentation and/or other materials provided with the distribution. |
||||
-- * Neither the name of Oracle nor the names of its contributors may be used |
||||
-- to endorse or promote products derived from this software without |
||||
-- specific prior written permission. |
||||
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
||||
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
SET NAMES utf8mb4; |
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; |
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; |
||||
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; |
||||
|
||||
DROP SCHEMA IF EXISTS test_sakila; |
||||
CREATE SCHEMA test_sakila; |
||||
USE test_sakila; |
||||
|
||||
-- |
||||
-- Table structure for table `actor` |
||||
-- |
||||
|
||||
CREATE TABLE actor ( |
||||
actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
first_name VARCHAR(45) NOT NULL, |
||||
last_name VARCHAR(45) NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (actor_id), |
||||
KEY idx_actor_last_name (last_name) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `address` |
||||
-- |
||||
|
||||
CREATE TABLE address ( |
||||
address_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
address VARCHAR(50) NOT NULL, |
||||
address2 VARCHAR(50) DEFAULT NULL, |
||||
district VARCHAR(20) NOT NULL, |
||||
city_id SMALLINT UNSIGNED NOT NULL, |
||||
postal_code VARCHAR(10) DEFAULT NULL, |
||||
phone VARCHAR(20) NOT NULL, |
||||
-- Add GEOMETRY column for MySQL 5.7.5 and higher |
||||
-- Also include SRID attribute for MySQL 8.0.3 and higher |
||||
/*!50705 location GEOMETRY */ /*!80003 SRID 0 */ /*!50705 NOT NULL,*/ |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (address_id), |
||||
KEY idx_fk_city_id (city_id), |
||||
/*!50705 SPATIAL KEY `idx_location` (location),*/ |
||||
CONSTRAINT `fk_address_city` FOREIGN KEY (city_id) REFERENCES city (city_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `category` |
||||
-- |
||||
|
||||
CREATE TABLE category ( |
||||
category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
name VARCHAR(25) NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (category_id) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `city` |
||||
-- |
||||
|
||||
CREATE TABLE city ( |
||||
city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
city VARCHAR(50) NOT NULL, |
||||
country_id SMALLINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (city_id), |
||||
KEY idx_fk_country_id (country_id), |
||||
CONSTRAINT `fk_city_country` FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `country` |
||||
-- |
||||
|
||||
CREATE TABLE country ( |
||||
country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
country VARCHAR(50) NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (country_id) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `customer` |
||||
-- |
||||
|
||||
CREATE TABLE customer ( |
||||
customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
store_id TINYINT UNSIGNED NOT NULL, |
||||
first_name VARCHAR(45) NOT NULL, |
||||
last_name VARCHAR(45) NOT NULL, |
||||
email VARCHAR(50) DEFAULT NULL, |
||||
address_id SMALLINT UNSIGNED NOT NULL, |
||||
active BOOLEAN NOT NULL DEFAULT TRUE, |
||||
create_date DATETIME NOT NULL, |
||||
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (customer_id), |
||||
KEY idx_fk_store_id (store_id), |
||||
KEY idx_fk_address_id (address_id), |
||||
KEY idx_last_name (last_name), |
||||
CONSTRAINT fk_customer_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_customer_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `film` |
||||
-- |
||||
|
||||
CREATE TABLE film ( |
||||
film_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
title VARCHAR(128) NOT NULL, |
||||
description TEXT DEFAULT NULL, |
||||
release_year YEAR DEFAULT NULL, |
||||
language_id TINYINT UNSIGNED NOT NULL, |
||||
original_language_id TINYINT UNSIGNED DEFAULT NULL, |
||||
rental_duration TINYINT UNSIGNED NOT NULL DEFAULT 3, |
||||
rental_rate DECIMAL(4,2) NOT NULL DEFAULT 4.99, |
||||
length SMALLINT UNSIGNED DEFAULT NULL, |
||||
replacement_cost DECIMAL(5,2) NOT NULL DEFAULT 19.99, |
||||
rating ENUM('G','PG','PG-13','R','NC-17') DEFAULT 'G', |
||||
special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (film_id), |
||||
KEY idx_title (title), |
||||
KEY idx_fk_language_id (language_id), |
||||
KEY idx_fk_original_language_id (original_language_id), |
||||
CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `film_actor` |
||||
-- |
||||
|
||||
CREATE TABLE film_actor ( |
||||
actor_id SMALLINT UNSIGNED NOT NULL, |
||||
film_id SMALLINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (actor_id,film_id), |
||||
KEY idx_fk_film_id (`film_id`), |
||||
CONSTRAINT fk_film_actor_actor FOREIGN KEY (actor_id) REFERENCES actor (actor_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_film_actor_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `film_category` |
||||
-- |
||||
|
||||
CREATE TABLE film_category ( |
||||
film_id SMALLINT UNSIGNED NOT NULL, |
||||
category_id TINYINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (film_id, category_id), |
||||
CONSTRAINT fk_film_category_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_film_category_category FOREIGN KEY (category_id) REFERENCES category (category_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `film_text` |
||||
-- |
||||
-- InnoDB added FULLTEXT support in 5.6.10. If you use an |
||||
-- earlier version, then consider upgrading (recommended) or |
||||
-- changing InnoDB to MyISAM as the film_text engine |
||||
-- |
||||
|
||||
-- Use InnoDB for film_text as of 5.6.10, MyISAM prior to 5.6.10. |
||||
SET @old_default_storage_engine = @@default_storage_engine; |
||||
SET @@default_storage_engine = 'MyISAM'; |
||||
/*!50610 SET @@default_storage_engine = 'InnoDB'*/; |
||||
|
||||
CREATE TABLE film_text ( |
||||
film_id SMALLINT NOT NULL, |
||||
title VARCHAR(255) NOT NULL, |
||||
description TEXT, |
||||
PRIMARY KEY (film_id), |
||||
FULLTEXT KEY idx_title_description (title,description) |
||||
) DEFAULT CHARSET=utf8mb4; |
||||
|
||||
SET @@default_storage_engine = @old_default_storage_engine; |
||||
|
||||
-- |
||||
-- Triggers for loading film_text from film |
||||
-- |
||||
|
||||
CREATE TRIGGER `ins_film` AFTER INSERT ON `film` FOR EACH ROW BEGIN |
||||
INSERT INTO film_text (film_id, title, description) |
||||
VALUES (new.film_id, new.title, new.description); |
||||
END; |
||||
|
||||
|
||||
CREATE TRIGGER `upd_film` AFTER UPDATE ON `film` FOR EACH ROW BEGIN |
||||
IF (old.title != new.title) OR (old.description != new.description) OR (old.film_id != new.film_id) |
||||
THEN |
||||
UPDATE film_text |
||||
SET title=new.title, |
||||
description=new.description, |
||||
film_id=new.film_id |
||||
WHERE film_id=old.film_id; |
||||
END IF; |
||||
END; |
||||
|
||||
|
||||
CREATE TRIGGER `del_film` AFTER DELETE ON `film` FOR EACH ROW BEGIN |
||||
DELETE FROM film_text WHERE film_id = old.film_id; |
||||
END; |
||||
|
||||
-- |
||||
-- Table structure for table `inventory` |
||||
-- |
||||
|
||||
CREATE TABLE inventory ( |
||||
inventory_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
film_id SMALLINT UNSIGNED NOT NULL, |
||||
store_id TINYINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (inventory_id), |
||||
KEY idx_fk_film_id (film_id), |
||||
KEY idx_store_id_film_id (store_id,film_id), |
||||
CONSTRAINT fk_inventory_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_inventory_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `language` |
||||
-- |
||||
|
||||
CREATE TABLE language ( |
||||
language_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
name CHAR(20) NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (language_id) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `payment` |
||||
-- |
||||
|
||||
CREATE TABLE payment ( |
||||
payment_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
customer_id SMALLINT UNSIGNED NOT NULL, |
||||
staff_id TINYINT UNSIGNED NOT NULL, |
||||
rental_id INT DEFAULT NULL, |
||||
amount DECIMAL(5,2) NOT NULL, |
||||
payment_date DATETIME NOT NULL, |
||||
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (payment_id), |
||||
KEY idx_fk_staff_id (staff_id), |
||||
KEY idx_fk_customer_id (customer_id), |
||||
CONSTRAINT fk_payment_rental FOREIGN KEY (rental_id) REFERENCES rental (rental_id) ON DELETE SET NULL ON UPDATE CASCADE, |
||||
CONSTRAINT fk_payment_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_payment_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
|
||||
-- |
||||
-- Table structure for table `rental` |
||||
-- |
||||
|
||||
CREATE TABLE rental ( |
||||
rental_id INT NOT NULL AUTO_INCREMENT, |
||||
rental_date DATETIME NOT NULL, |
||||
inventory_id MEDIUMINT UNSIGNED NOT NULL, |
||||
customer_id SMALLINT UNSIGNED NOT NULL, |
||||
return_date DATETIME DEFAULT NULL, |
||||
staff_id TINYINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (rental_id), |
||||
UNIQUE KEY (rental_date,inventory_id,customer_id), |
||||
KEY idx_fk_inventory_id (inventory_id), |
||||
KEY idx_fk_customer_id (customer_id), |
||||
KEY idx_fk_staff_id (staff_id), |
||||
CONSTRAINT fk_rental_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_rental_inventory FOREIGN KEY (inventory_id) REFERENCES inventory (inventory_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_rental_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `staff` |
||||
-- |
||||
|
||||
CREATE TABLE staff ( |
||||
staff_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
first_name VARCHAR(45) NOT NULL, |
||||
last_name VARCHAR(45) NOT NULL, |
||||
address_id SMALLINT UNSIGNED NOT NULL, |
||||
picture BLOB DEFAULT NULL, |
||||
email VARCHAR(50) DEFAULT NULL, |
||||
store_id TINYINT UNSIGNED NOT NULL, |
||||
active BOOLEAN NOT NULL DEFAULT TRUE, |
||||
username VARCHAR(16) NOT NULL, |
||||
password VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (staff_id), |
||||
KEY idx_fk_store_id (store_id), |
||||
KEY idx_fk_address_id (address_id), |
||||
CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- Table structure for table `store` |
||||
-- |
||||
|
||||
CREATE TABLE store ( |
||||
store_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, |
||||
manager_staff_id TINYINT UNSIGNED NOT NULL, |
||||
address_id SMALLINT UNSIGNED NOT NULL, |
||||
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (store_id), |
||||
UNIQUE KEY idx_unique_manager (manager_staff_id), |
||||
KEY idx_fk_address_id (address_id), |
||||
CONSTRAINT fk_store_staff FOREIGN KEY (manager_staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, |
||||
CONSTRAINT fk_store_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
||||
|
||||
-- |
||||
-- View structure for view `customer_list` |
||||
-- |
||||
|
||||
CREATE VIEW customer_list |
||||
AS |
||||
SELECT cu.customer_id AS ID, CONCAT(cu.first_name, _utf8mb4' ', cu.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, |
||||
a.phone AS phone, city.city AS city, country.country AS country, IF(cu.active, _utf8mb4'active',_utf8mb4'') AS notes, cu.store_id AS SID |
||||
FROM customer AS cu JOIN address AS a ON cu.address_id = a.address_id JOIN city ON a.city_id = city.city_id |
||||
JOIN country ON city.country_id = country.country_id; |
||||
|
||||
-- |
||||
-- View structure for view `film_list` |
||||
-- |
||||
|
||||
CREATE VIEW film_list |
||||
AS |
||||
SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, |
||||
film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(actor.first_name, _utf8mb4' ', actor.last_name) SEPARATOR ', ') AS actors |
||||
FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id |
||||
JOIN film_actor ON film.film_id = film_actor.film_id |
||||
JOIN actor ON film_actor.actor_id = actor.actor_id |
||||
GROUP BY film.film_id, category.name; |
||||
|
||||
-- |
||||
-- View structure for view `nicer_but_slower_film_list` |
||||
-- |
||||
|
||||
CREATE VIEW nicer_but_slower_film_list |
||||
AS |
||||
SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, |
||||
film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(CONCAT(UCASE(SUBSTR(actor.first_name,1,1)), |
||||
LCASE(SUBSTR(actor.first_name,2,LENGTH(actor.first_name))),_utf8mb4' ',CONCAT(UCASE(SUBSTR(actor.last_name,1,1)), |
||||
LCASE(SUBSTR(actor.last_name,2,LENGTH(actor.last_name)))))) SEPARATOR ', ') AS actors |
||||
FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id |
||||
JOIN film_actor ON film.film_id = film_actor.film_id |
||||
JOIN actor ON film_actor.actor_id = actor.actor_id |
||||
GROUP BY film.film_id, category.name; |
||||
|
||||
-- |
||||
-- View structure for view `staff_list` |
||||
-- |
||||
|
||||
CREATE VIEW staff_list |
||||
AS |
||||
SELECT s.staff_id AS ID, CONCAT(s.first_name, _utf8mb4' ', s.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, |
||||
city.city AS city, country.country AS country, s.store_id AS SID |
||||
FROM staff AS s JOIN address AS a ON s.address_id = a.address_id JOIN city ON a.city_id = city.city_id |
||||
JOIN country ON city.country_id = country.country_id; |
||||
|
||||
-- |
||||
-- View structure for view `sales_by_store` |
||||
-- |
||||
|
||||
CREATE VIEW sales_by_store |
||||
AS |
||||
SELECT |
||||
CONCAT(c.city, _utf8mb4',', cy.country) AS store |
||||
, CONCAT(m.first_name, _utf8mb4' ', m.last_name) AS manager |
||||
, SUM(p.amount) AS total_sales |
||||
FROM payment AS p |
||||
INNER JOIN rental AS r ON p.rental_id = r.rental_id |
||||
INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id |
||||
INNER JOIN store AS s ON i.store_id = s.store_id |
||||
INNER JOIN address AS a ON s.address_id = a.address_id |
||||
INNER JOIN city AS c ON a.city_id = c.city_id |
||||
INNER JOIN country AS cy ON c.country_id = cy.country_id |
||||
INNER JOIN staff AS m ON s.manager_staff_id = m.staff_id |
||||
GROUP BY s.store_id |
||||
ORDER BY cy.country, c.city; |
||||
|
||||
-- |
||||
-- View structure for view `sales_by_film_category` |
||||
-- |
||||
-- Note that total sales will add up to >100% because |
||||
-- some titles belong to more than 1 category |
||||
-- |
||||
|
||||
CREATE VIEW sales_by_film_category |
||||
AS |
||||
SELECT |
||||
c.name AS category |
||||
, SUM(p.amount) AS total_sales |
||||
FROM payment AS p |
||||
INNER JOIN rental AS r ON p.rental_id = r.rental_id |
||||
INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id |
||||
INNER JOIN film AS f ON i.film_id = f.film_id |
||||
INNER JOIN film_category AS fc ON f.film_id = fc.film_id |
||||
INNER JOIN category AS c ON fc.category_id = c.category_id |
||||
GROUP BY c.name |
||||
ORDER BY total_sales DESC; |
||||
|
||||
-- |
||||
-- View structure for view `actor_info` |
||||
-- |
||||
|
||||
CREATE DEFINER=CURRENT_USER SQL SECURITY INVOKER VIEW actor_info |
||||
AS |
||||
SELECT |
||||
a.actor_id, |
||||
a.first_name, |
||||
a.last_name, |
||||
GROUP_CONCAT(DISTINCT CONCAT(c.name, ': ', |
||||
(SELECT GROUP_CONCAT(f.title ORDER BY f.title SEPARATOR ', ') |
||||
FROM test_sakila.film f |
||||
INNER JOIN test_sakila.film_category fc |
||||
ON f.film_id = fc.film_id |
||||
INNER JOIN test_sakila.film_actor fa |
||||
ON f.film_id = fa.film_id |
||||
WHERE fc.category_id = c.category_id |
||||
AND fa.actor_id = a.actor_id |
||||
) |
||||
) |
||||
ORDER BY c.name SEPARATOR '; ') |
||||
AS film_info |
||||
FROM test_sakila.actor a |
||||
LEFT JOIN test_sakila.film_actor fa |
||||
ON a.actor_id = fa.actor_id |
||||
LEFT JOIN test_sakila.film_category fc |
||||
ON fa.film_id = fc.film_id |
||||
LEFT JOIN test_sakila.category c |
||||
ON fc.category_id = c.category_id |
||||
GROUP BY a.actor_id, a.first_name, a.last_name; |
||||
|
||||
-- |
||||
-- Procedure structure for procedure `rewards_report` |
||||
-- |
||||
|
||||
CREATE PROCEDURE rewards_report ( |
||||
IN min_monthly_purchases TINYINT UNSIGNED |
||||
, IN min_dollar_amount_purchased DECIMAL(10,2) |
||||
, OUT count_rewardees INT |
||||
) |
||||
LANGUAGE SQL |
||||
NOT DETERMINISTIC |
||||
READS SQL DATA |
||||
SQL SECURITY DEFINER |
||||
COMMENT 'Provides a customizable report on best customers' |
||||
proc: BEGIN |
||||
|
||||
DECLARE last_month_start DATE; |
||||
DECLARE last_month_end DATE; |
||||
|
||||
/* Some sanity checks... */ |
||||
IF min_monthly_purchases = 0 THEN |
||||
SELECT 'Minimum monthly purchases parameter must be > 0'; |
||||
LEAVE proc; |
||||
END IF; |
||||
IF min_dollar_amount_purchased = 0.00 THEN |
||||
SELECT 'Minimum monthly dollar amount purchased parameter must be > $0.00'; |
||||
LEAVE proc; |
||||
END IF; |
||||
|
||||
/* Determine start and end time periods */ |
||||
SET last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH); |
||||
SET last_month_start = STR_TO_DATE(CONCAT(YEAR(last_month_start),'-',MONTH(last_month_start),'-01'),'%Y-%m-%d'); |
||||
SET last_month_end = LAST_DAY(last_month_start); |
||||
|
||||
/* |
||||
Create a temporary storage area for |
||||
Customer IDs. |
||||
*/ |
||||
CREATE TEMPORARY TABLE tmpCustomer (customer_id SMALLINT UNSIGNED NOT NULL PRIMARY KEY); |
||||
|
||||
/* |
||||
Find all customers meeting the |
||||
monthly purchase requirements |
||||
*/ |
||||
INSERT INTO tmpCustomer (customer_id) |
||||
SELECT p.customer_id |
||||
FROM payment AS p |
||||
WHERE DATE(p.payment_date) BETWEEN last_month_start AND last_month_end |
||||
GROUP BY customer_id |
||||
HAVING SUM(p.amount) > min_dollar_amount_purchased |
||||
AND COUNT(customer_id) > min_monthly_purchases; |
||||
|
||||
/* Populate OUT parameter with count of found customers */ |
||||
SELECT COUNT(*) FROM tmpCustomer INTO count_rewardees; |
||||
|
||||
/* |
||||
Output ALL customer information of matching rewardees. |
||||
Customize output as needed. |
||||
*/ |
||||
SELECT c.* |
||||
FROM tmpCustomer AS t |
||||
INNER JOIN customer AS c ON t.customer_id = c.customer_id; |
||||
|
||||
/* Clean up */ |
||||
DROP TABLE tmpCustomer; |
||||
END; |
||||
|
||||
CREATE FUNCTION IF NOT EXISTS get_customer_balance(p_customer_id INT, p_effective_date DATETIME) RETURNS DECIMAL(5,2) |
||||
DETERMINISTIC |
||||
READS SQL DATA |
||||
BEGIN |
||||
|
||||
#OK, WE NEED TO CALCULATE THE CURRENT BALANCE GIVEN A CUSTOMER_ID AND A DATE |
||||
#THAT WE WANT THE BALANCE TO BE EFFECTIVE FOR. THE BALANCE IS: |
||||
# 1) RENTAL FEES FOR ALL PREVIOUS RENTALS |
||||
# 2) ONE DOLLAR FOR EVERY DAY THE PREVIOUS RENTALS ARE OVERDUE |
||||
# 3) IF A FILM IS MORE THAN RENTAL_DURATION * 2 OVERDUE, CHARGE THE REPLACEMENT_COST |
||||
# 4) SUBTRACT ALL PAYMENTS MADE BEFORE THE DATE SPECIFIED |
||||
|
||||
DECLARE v_rentfees DECIMAL(5,2); #FEES PAID TO RENT THE VIDEOS INITIALLY |
||||
DECLARE v_overfees INTEGER; #LATE FEES FOR PRIOR RENTALS |
||||
DECLARE v_payments DECIMAL(5,2); #SUM OF PAYMENTS MADE PREVIOUSLY |
||||
|
||||
SELECT IFNULL(SUM(film.rental_rate),0) INTO v_rentfees |
||||
FROM film, inventory, rental |
||||
WHERE film.film_id = inventory.film_id |
||||
AND inventory.inventory_id = rental.inventory_id |
||||
AND rental.rental_date <= p_effective_date |
||||
AND rental.customer_id = p_customer_id; |
||||
|
||||
SELECT IFNULL(SUM(IF((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) > film.rental_duration, |
||||
((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) - film.rental_duration),0)),0) INTO v_overfees |
||||
FROM rental, inventory, film |
||||
WHERE film.film_id = inventory.film_id |
||||
AND inventory.inventory_id = rental.inventory_id |
||||
AND rental.rental_date <= p_effective_date |
||||
AND rental.customer_id = p_customer_id; |
||||
|
||||
|
||||
SELECT IFNULL(SUM(payment.amount),0) INTO v_payments |
||||
FROM payment |
||||
|
||||
WHERE payment.payment_date <= p_effective_date |
||||
AND payment.customer_id = p_customer_id; |
||||
|
||||
RETURN v_rentfees + v_overfees - v_payments; |
||||
END; |
||||
|
||||
CREATE PROCEDURE film_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) |
||||
READS SQL DATA |
||||
BEGIN |
||||
SELECT inventory_id |
||||
FROM inventory |
||||
WHERE film_id = p_film_id |
||||
AND store_id = p_store_id |
||||
AND inventory_in_stock(inventory_id); |
||||
|
||||
SELECT COUNT(*) |
||||
FROM inventory |
||||
WHERE film_id = p_film_id |
||||
AND store_id = p_store_id |
||||
AND inventory_in_stock(inventory_id) |
||||
INTO p_film_count; |
||||
END; |
||||
|
||||
CREATE PROCEDURE film_not_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) |
||||
READS SQL DATA |
||||
BEGIN |
||||
SELECT inventory_id |
||||
FROM inventory |
||||
WHERE film_id = p_film_id |
||||
AND store_id = p_store_id |
||||
AND NOT inventory_in_stock(inventory_id); |
||||
|
||||
SELECT COUNT(*) |
||||
FROM inventory |
||||
WHERE film_id = p_film_id |
||||
AND store_id = p_store_id |
||||
AND NOT inventory_in_stock(inventory_id) |
||||
INTO p_film_count; |
||||
END; |
||||
|
||||
|
||||
CREATE FUNCTION IF NOT EXISTS inventory_held_by_customer(p_inventory_id INT) RETURNS INT |
||||
READS SQL DATA |
||||
BEGIN |
||||
DECLARE v_customer_id INT; |
||||
DECLARE EXIT HANDLER FOR NOT FOUND RETURN NULL; |
||||
|
||||
SELECT customer_id INTO v_customer_id |
||||
FROM rental |
||||
WHERE return_date IS NULL |
||||
AND inventory_id = p_inventory_id; |
||||
|
||||
RETURN v_customer_id; |
||||
END; |
||||
|
||||
CREATE FUNCTION IF NOT EXISTS inventory_in_stock(p_inventory_id INT) RETURNS BOOLEAN |
||||
READS SQL DATA |
||||
BEGIN |
||||
DECLARE v_rentals INT; |
||||
DECLARE v_out INT; |
||||
|
||||
#AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE |
||||
#FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED |
||||
|
||||
SELECT COUNT(*) INTO v_rentals |
||||
FROM rental |
||||
WHERE inventory_id = p_inventory_id; |
||||
|
||||
IF v_rentals = 0 THEN |
||||
RETURN TRUE; |
||||
END IF; |
||||
|
||||
SELECT COUNT(rental_id) INTO v_out |
||||
FROM inventory LEFT JOIN rental USING(inventory_id) |
||||
WHERE inventory.inventory_id = p_inventory_id |
||||
AND rental.return_date IS NULL; |
||||
|
||||
IF v_out > 0 THEN |
||||
RETURN FALSE; |
||||
ELSE |
||||
RETURN TRUE; |
||||
END IF; |
||||
END; |
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE; |
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; |
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; |
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,243 @@
|
||||
import { DbConfig } from "../../src/interface/config"; |
||||
import { NcConfigFactory } from "../../src/lib"; |
||||
import SqlMgrv2 from "../../src/lib/db/sql-mgr/v2/SqlMgrv2"; |
||||
import fs from 'fs'; |
||||
import knex from "knex"; |
||||
import process from "process"; |
||||
|
||||
export default class TestDbMngr { |
||||
public static readonly dbName = 'test_meta'; |
||||
public static readonly sakilaDbName = 'test_sakila'; |
||||
public static metaKnex: knex; |
||||
public static sakilaKnex: knex; |
||||
|
||||
public static defaultConnection = { |
||||
user: process.env['DB_USER'] || 'root', |
||||
password: process.env['DB_PASSWORD'] || 'password', |
||||
host: process.env['DB_HOST'] || 'localhost', |
||||
port: Number(process.env['DB_PORT']) || 3306, |
||||
client: 'mysql2', |
||||
} |
||||
|
||||
public static dbConfig: DbConfig; |
||||
|
||||
static async testConnection(config: DbConfig) { |
||||
try { |
||||
return await SqlMgrv2.testConnection(config); |
||||
} catch (e) { |
||||
console.log(e); |
||||
return { code: -1, message: 'Connection invalid' }; |
||||
} |
||||
} |
||||
|
||||
static async init() { |
||||
if(await TestDbMngr.isMysqlConfigured()){ |
||||
await TestDbMngr.connectMysql(); |
||||
} else { |
||||
await TestDbMngr.switchToSqlite(); |
||||
} |
||||
} |
||||
|
||||
static async isMysqlConfigured() { |
||||
const { user, password, host, port, client } = TestDbMngr.defaultConnection; |
||||
const config = NcConfigFactory.urlToDbConfig(`${client}://${user}:${password}@${host}:${port}`); |
||||
config.connection = { |
||||
user, |
||||
password, |
||||
host, |
||||
port, |
||||
} |
||||
const result = await TestDbMngr.testConnection(config); |
||||
return result.code !== -1; |
||||
} |
||||
|
||||
static async connectMysql() { |
||||
const { user, password, host, port, client } = TestDbMngr.defaultConnection; |
||||
if(!process.env[`DATABASE_URL`]){ |
||||
process.env[`DATABASE_URL`] = `${client}://${user}:${password}@${host}:${port}/${TestDbMngr.dbName}`; |
||||
} |
||||
|
||||
TestDbMngr.dbConfig = NcConfigFactory.urlToDbConfig( |
||||
NcConfigFactory.extractXcUrlFromJdbc(process.env[`DATABASE_URL`]) |
||||
); |
||||
this.dbConfig.meta = { |
||||
tn: 'nc_evolutions', |
||||
dbAlias: 'db', |
||||
api: { |
||||
type: 'rest', |
||||
prefix: '', |
||||
graphqlDepthLimit: 10, |
||||
}, |
||||
inflection: { |
||||
tn: 'camelize', |
||||
cn: 'camelize', |
||||
}, |
||||
} |
||||
|
||||
await TestDbMngr.setupMeta(); |
||||
await TestDbMngr.setupSakila(); |
||||
} |
||||
|
||||
static async setupMeta() { |
||||
if(TestDbMngr.metaKnex){ |
||||
await TestDbMngr.metaKnex.destroy(); |
||||
} |
||||
|
||||
if(TestDbMngr.isSqlite()){ |
||||
await TestDbMngr.resetMetaSqlite(); |
||||
TestDbMngr.metaKnex = knex(TestDbMngr.getMetaDbConfig()); |
||||
return |
||||
}
|
||||
|
||||
TestDbMngr.metaKnex = knex(TestDbMngr.getDbConfigWithNoDb()); |
||||
await TestDbMngr.resetDatabase(TestDbMngr.metaKnex, TestDbMngr.dbName); |
||||
await TestDbMngr.metaKnex.destroy(); |
||||
|
||||
TestDbMngr.metaKnex = knex(TestDbMngr.getMetaDbConfig()); |
||||
await TestDbMngr.useDatabase(TestDbMngr.metaKnex, TestDbMngr.dbName); |
||||
} |
||||
|
||||
static async setupSakila () { |
||||
if(TestDbMngr.sakilaKnex) { |
||||
await TestDbMngr.sakilaKnex.destroy(); |
||||
} |
||||
|
||||
if(TestDbMngr.isSqlite()){ |
||||
await TestDbMngr.seedSakila(); |
||||
TestDbMngr.sakilaKnex = knex(TestDbMngr.getSakilaDbConfig()); |
||||
return |
||||
}
|
||||
|
||||
TestDbMngr.sakilaKnex = knex(TestDbMngr.getDbConfigWithNoDb()); |
||||
await TestDbMngr.resetDatabase(TestDbMngr.sakilaKnex, TestDbMngr.sakilaDbName); |
||||
await TestDbMngr.sakilaKnex.destroy(); |
||||
|
||||
TestDbMngr.sakilaKnex = knex(TestDbMngr.getSakilaDbConfig()); |
||||
await TestDbMngr.useDatabase(TestDbMngr.sakilaKnex, TestDbMngr.sakilaDbName); |
||||
} |
||||
|
||||
static async switchToSqlite() { |
||||
// process.env[`DATABASE_URL`] = `sqlite3:///?database=${__dirname}/${TestDbMngr.dbName}.sqlite`;
|
||||
TestDbMngr.dbConfig = { |
||||
client: 'sqlite3', |
||||
connection: { |
||||
filename: `${__dirname}/${TestDbMngr.dbName}.db`, |
||||
database: TestDbMngr.dbName, |
||||
}, |
||||
useNullAsDefault: true, |
||||
meta: { |
||||
tn: 'nc_evolutions', |
||||
dbAlias: 'db', |
||||
api: { |
||||
type: 'rest', |
||||
prefix: '', |
||||
graphqlDepthLimit: 10, |
||||
}, |
||||
inflection: { |
||||
tn: 'camelize', |
||||
cn: 'camelize', |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
process.env[`NC_DB`] = `sqlite3:///?database=${__dirname}/${TestDbMngr.dbName}.db`; |
||||
await TestDbMngr.setupMeta(); |
||||
await TestDbMngr.setupSakila(); |
||||
} |
||||
|
||||
private static async resetDatabase(knexClient, dbName) { |
||||
if(TestDbMngr.isSqlite()){ |
||||
// return knexClient.raw(`DELETE FROM sqlite_sequence`);
|
||||
} else { |
||||
try { |
||||
await knexClient.raw(`DROP DATABASE ${dbName}`); |
||||
} catch(e) {} |
||||
await knexClient.raw(`CREATE DATABASE ${dbName}`); |
||||
console.log(`Database ${dbName} created`); |
||||
await knexClient.raw(`USE ${dbName}`); |
||||
} |
||||
} |
||||
|
||||
static isSqlite() { |
||||
return TestDbMngr.dbConfig.client === 'sqlite3'; |
||||
} |
||||
|
||||
private static async useDatabase(knexClient, dbName) { |
||||
if(!TestDbMngr.isSqlite()){ |
||||
await knexClient.raw(`USE ${dbName}`); |
||||
} |
||||
} |
||||
|
||||
static getDbConfigWithNoDb() { |
||||
const dbConfig =JSON.parse(JSON.stringify(TestDbMngr.dbConfig)); |
||||
delete dbConfig.connection.database; |
||||
return dbConfig; |
||||
} |
||||
|
||||
static getMetaDbConfig() { |
||||
return TestDbMngr.dbConfig; |
||||
} |
||||
|
||||
private static resetMetaSqlite() { |
||||
if(fs.existsSync(`${__dirname}/test_meta.db`)){ |
||||
fs.unlinkSync(`${__dirname}/test_meta.db`); |
||||
} |
||||
} |
||||
|
||||
static getSakilaDbConfig() { |
||||
const sakilaDbConfig = JSON.parse(JSON.stringify(TestDbMngr.dbConfig)); |
||||
sakilaDbConfig.connection.database = TestDbMngr.sakilaDbName; |
||||
sakilaDbConfig.connection.multipleStatements = true |
||||
if(TestDbMngr.isSqlite()){ |
||||
sakilaDbConfig.connection.filename = `${__dirname}/test_sakila.db`; |
||||
} |
||||
return sakilaDbConfig; |
||||
} |
||||
|
||||
static async seedSakila() {
|
||||
const testsDir = __dirname.replace('tests/unit', 'tests'); |
||||
|
||||
if(TestDbMngr.isSqlite()){ |
||||
if(fs.existsSync(`${__dirname}/test_sakila.db`)){ |
||||
fs.unlinkSync(`${__dirname}/test_sakila.db`); |
||||
} |
||||
fs.copyFileSync(`${testsDir}/sqlite-sakila-db/sakila.db`, `${__dirname}/test_sakila.db`); |
||||
} else { |
||||
const schemaFile = fs.readFileSync(`${testsDir}/mysql-sakila-db/03-test-sakila-schema.sql`).toString(); |
||||
const dataFile = fs.readFileSync(`${testsDir}/mysql-sakila-db/04-test-sakila-data.sql`).toString(); |
||||
await TestDbMngr.sakilaKnex.raw(schemaFile); |
||||
await TestDbMngr.sakilaKnex.raw(dataFile); |
||||
} |
||||
} |
||||
|
||||
static async disableForeignKeyChecks(knexClient) { |
||||
if(TestDbMngr.isSqlite()){ |
||||
await knexClient.raw("PRAGMA foreign_keys = OFF"); |
||||
} |
||||
else { |
||||
await knexClient.raw(`SET FOREIGN_KEY_CHECKS = 0`); |
||||
} |
||||
} |
||||
|
||||
static async enableForeignKeyChecks(knexClient) { |
||||
if(TestDbMngr.isSqlite()){ |
||||
await knexClient.raw(`PRAGMA foreign_keys = ON;`); |
||||
} |
||||
else { |
||||
await knexClient.raw(`SET FOREIGN_KEY_CHECKS = 1`); |
||||
} |
||||
} |
||||
|
||||
static async showAllTables(knexClient) { |
||||
if(TestDbMngr.isSqlite()){ |
||||
const tables = await knexClient.raw(`SELECT name FROM sqlite_master WHERE type='table'`); |
||||
return tables.filter(t => t.name !== 'sqlite_sequence' && t.name !== '_evolutions').map(t => t.name); |
||||
} |
||||
else { |
||||
const response = await knexClient.raw(`SHOW TABLES`); |
||||
return response[0].map( |
||||
(table) => Object.values(table)[0] |
||||
); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,203 @@
|
||||
import { UITypes } from 'nocodb-sdk'; |
||||
import request from 'supertest'; |
||||
import Column from '../../../src/lib/models/Column'; |
||||
import FormViewColumn from '../../../src/lib/models/FormViewColumn'; |
||||
import GalleryViewColumn from '../../../src/lib/models/GalleryViewColumn'; |
||||
import GridViewColumn from '../../../src/lib/models/GridViewColumn'; |
||||
import Model from '../../../src/lib/models/Model'; |
||||
import Project from '../../../src/lib/models/Project'; |
||||
import View from '../../../src/lib/models/View'; |
||||
import { isSqlite } from '../init/db'; |
||||
|
||||
const defaultColumns = function(context) { |
||||
return [ |
||||
{ |
||||
column_name: 'id', |
||||
title: 'Id', |
||||
uidt: 'ID', |
||||
}, |
||||
{ |
||||
column_name: 'title', |
||||
title: 'Title', |
||||
uidt: 'SingleLineText', |
||||
}, |
||||
{ |
||||
cdf: 'CURRENT_TIMESTAMP', |
||||
column_name: 'created_at', |
||||
title: 'CreatedAt', |
||||
dtxp: '', |
||||
dtxs: '', |
||||
uidt: 'DateTime', |
||||
}, |
||||
{ |
||||
cdf: isSqlite(context) ? 'CURRENT_TIMESTAMP': 'CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP', |
||||
column_name: 'updated_at', |
||||
title: 'UpdatedAt', |
||||
dtxp: '', |
||||
dtxs: '', |
||||
uidt: 'DateTime', |
||||
}, |
||||
] |
||||
}; |
||||
|
||||
const createColumn = async (context, table, columnAttr) => { |
||||
await request(context.app) |
||||
.post(`/api/v1/db/meta/tables/${table.id}/columns`) |
||||
.set('xc-auth', context.token) |
||||
.send({ |
||||
...columnAttr, |
||||
}); |
||||
|
||||
const column: Column = (await table.getColumns()).find( |
||||
(column) => column.title === columnAttr.title |
||||
); |
||||
return column; |
||||
}; |
||||
|
||||
const createRollupColumn = async ( |
||||
context, |
||||
{ |
||||
project, |
||||
title, |
||||
rollupFunction, |
||||
table, |
||||
relatedTableName, |
||||
relatedTableColumnTitle, |
||||
}: { |
||||
project: Project; |
||||
title: string; |
||||
rollupFunction: string; |
||||
table: Model; |
||||
relatedTableName: string; |
||||
relatedTableColumnTitle: string; |
||||
} |
||||
) => { |
||||
const childBases = await project.getBases(); |
||||
const childTable = await Model.getByIdOrName({ |
||||
project_id: project.id, |
||||
base_id: childBases[0].id!, |
||||
table_name: relatedTableName, |
||||
}); |
||||
const childTableColumns = await childTable.getColumns(); |
||||
const childTableColumn = await childTableColumns.find( |
||||
(column) => column.title === relatedTableColumnTitle |
||||
); |
||||
|
||||
const ltarColumn = (await table.getColumns()).find( |
||||
(column) => |
||||
column.uidt === UITypes.LinkToAnotherRecord && |
||||
column.colOptions?.fk_related_model_id === childTable.id |
||||
); |
||||
|
||||
const rollupColumn = await createColumn(context, table, { |
||||
title: title, |
||||
uidt: UITypes.Rollup, |
||||
fk_relation_column_id: ltarColumn?.id, |
||||
fk_rollup_column_id: childTableColumn?.id, |
||||
rollup_function: rollupFunction, |
||||
table_name: table.table_name, |
||||
column_name: title, |
||||
}); |
||||
|
||||
return rollupColumn; |
||||
}; |
||||
|
||||
const createLookupColumn = async ( |
||||
context, |
||||
{ |
||||
project, |
||||
title, |
||||
table, |
||||
relatedTableName, |
||||
relatedTableColumnTitle, |
||||
}: { |
||||
project: Project; |
||||
title: string; |
||||
table: Model; |
||||
relatedTableName: string; |
||||
relatedTableColumnTitle: string; |
||||
} |
||||
) => { |
||||
const childBases = await project.getBases(); |
||||
const childTable = await Model.getByIdOrName({ |
||||
project_id: project.id, |
||||
base_id: childBases[0].id!, |
||||
table_name: relatedTableName, |
||||
}); |
||||
const childTableColumns = await childTable.getColumns(); |
||||
const childTableColumn = await childTableColumns.find( |
||||
(column) => column.title === relatedTableColumnTitle |
||||
); |
||||
|
||||
if (!childTableColumn) { |
||||
throw new Error( |
||||
`Could not find column ${relatedTableColumnTitle} in ${relatedTableName}` |
||||
); |
||||
} |
||||
|
||||
const ltarColumn = (await table.getColumns()).find( |
||||
(column) => |
||||
column.uidt === UITypes.LinkToAnotherRecord && |
||||
column.colOptions?.fk_related_model_id === childTable.id |
||||
); |
||||
const lookupColumn = await createColumn(context, table, { |
||||
title: title, |
||||
uidt: UITypes.Lookup, |
||||
fk_relation_column_id: ltarColumn?.id, |
||||
fk_lookup_column_id: childTableColumn?.id, |
||||
table_name: table.table_name, |
||||
column_name: title, |
||||
}); |
||||
|
||||
return lookupColumn; |
||||
}; |
||||
|
||||
const createLtarColumn = async ( |
||||
context, |
||||
{ |
||||
title, |
||||
parentTable, |
||||
childTable, |
||||
type, |
||||
}: { |
||||
title: string; |
||||
parentTable: Model; |
||||
childTable: Model; |
||||
type: string; |
||||
} |
||||
) => { |
||||
const ltarColumn = await createColumn(context, parentTable, { |
||||
title: title, |
||||
column_name: title, |
||||
uidt: UITypes.LinkToAnotherRecord, |
||||
parentId: parentTable.id, |
||||
childId: childTable.id, |
||||
type: type, |
||||
}); |
||||
|
||||
return ltarColumn; |
||||
}; |
||||
|
||||
const updateViewColumn = async (context, {view, column, attr}: {column: Column, view: View, attr: any}) => { |
||||
const res = await request(context.app) |
||||
.patch(`/api/v1/db/meta/views/${view.id}/columns/${column.id}`) |
||||
.set('xc-auth', context.token) |
||||
.send({ |
||||
...attr, |
||||
}); |
||||
|
||||
const updatedColumn: FormViewColumn | GridViewColumn | GalleryViewColumn = (await view.getColumns()).find( |
||||
(column) => column.id === column.id |
||||
)!; |
||||
|
||||
return updatedColumn; |
||||
} |
||||
|
||||
export { |
||||
defaultColumns, |
||||
createColumn, |
||||
createRollupColumn, |
||||
createLookupColumn, |
||||
createLtarColumn, |
||||
updateViewColumn |
||||
}; |
@ -0,0 +1,64 @@
|
||||
import request from 'supertest'; |
||||
import Project from '../../../src/lib/models/Project'; |
||||
import TestDbMngr from '../TestDbMngr'; |
||||
|
||||
const externalProjectConfig = { |
||||
title: 'sakila', |
||||
bases: [ |
||||
{ |
||||
type: 'mysql2', |
||||
config: { |
||||
client: 'mysql2', |
||||
connection: { |
||||
host: 'localhost', |
||||
port: '3306', |
||||
user: 'root', |
||||
password: 'password', |
||||
database: TestDbMngr.sakilaDbName, |
||||
}, |
||||
}, |
||||
inflection_column: 'camelize', |
||||
inflection_table: 'camelize', |
||||
}, |
||||
], |
||||
external: true, |
||||
}; |
||||
|
||||
const defaultProjectValue = { |
||||
title: 'Title', |
||||
}; |
||||
|
||||
const defaultSharedBaseValue = { |
||||
roles: 'viewer', |
||||
password: 'test', |
||||
}; |
||||
|
||||
const createSharedBase = async (app, token, project, sharedBaseArgs = {}) => { |
||||
await request(app) |
||||
.post(`/api/v1/db/meta/projects/${project.id}/shared`) |
||||
.set('xc-auth', token) |
||||
.send({ |
||||
...defaultSharedBaseValue, |
||||
...sharedBaseArgs, |
||||
}); |
||||
}; |
||||
|
||||
const createSakilaProject = async (context) => { |
||||
const response = await request(context.app) |
||||
.post('/api/v1/db/meta/projects/') |
||||
.set('xc-auth', context.token) |
||||
.send(externalProjectConfig); |
||||
|
||||
return (await Project.getByTitleOrId(response.body.id)) as Project; |
||||
}; |
||||
|
||||
const createProject = async (context, projectArgs = defaultProjectValue) => { |
||||
const response = await request(context.app) |
||||
.post('/api/v1/db/meta/projects/') |
||||
.set('xc-auth', context.token) |
||||
.send(projectArgs); |
||||
|
||||
return (await Project.getByTitleOrId(response.body.id)) as Project; |
||||
}; |
||||
|
||||
export { createProject, createSharedBase, createSakilaProject }; |
@ -0,0 +1,181 @@
|
||||
import { ColumnType, UITypes } from 'nocodb-sdk'; |
||||
import request from 'supertest'; |
||||
import Column from '../../../src/lib/models/Column'; |
||||
import Filter from '../../../src/lib/models/Filter'; |
||||
import Model from '../../../src/lib/models/Model'; |
||||
import Project from '../../../src/lib/models/Project'; |
||||
import Sort from '../../../src/lib/models/Sort'; |
||||
import NcConnectionMgrv2 from '../../../src/lib/utils/common/NcConnectionMgrv2'; |
||||
|
||||
const rowValue = (column: ColumnType, index: number) => { |
||||
switch (column.uidt) { |
||||
case UITypes.Number: |
||||
return index; |
||||
case UITypes.SingleLineText: |
||||
return `test-${index}`; |
||||
case UITypes.Date: |
||||
return '2020-01-01'; |
||||
case UITypes.DateTime: |
||||
return '2020-01-01 00:00:00'; |
||||
case UITypes.Email: |
||||
return `test-${index}@example.com`; |
||||
default: |
||||
return `test-${index}`; |
||||
} |
||||
}; |
||||
|
||||
const getRow = async (context, {project, table, id}) => { |
||||
const response = await request(context.app) |
||||
.get(`/api/v1/db/data/noco/${project.id}/${table.id}/${id}`) |
||||
.set('xc-auth', context.token); |
||||
|
||||
return response.body; |
||||
}; |
||||
|
||||
const listRow = async ({ |
||||
project, |
||||
table, |
||||
options, |
||||
}: { |
||||
project: Project; |
||||
table: Model; |
||||
options?: { |
||||
limit?: any; |
||||
offset?: any; |
||||
filterArr?: Filter[]; |
||||
sortArr?: Sort[]; |
||||
}; |
||||
}) => { |
||||
const bases = await project.getBases(); |
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: table.id, |
||||
dbDriver: NcConnectionMgrv2.get(bases[0]!), |
||||
}); |
||||
|
||||
const ignorePagination = !options; |
||||
|
||||
return await baseModel.list(options, ignorePagination); |
||||
}; |
||||
|
||||
const getOneRow = async ( |
||||
context, |
||||
{ project, table }: { project: Project; table: Model } |
||||
) => { |
||||
const response = await request(context.app) |
||||
.get(`/api/v1/db/data/noco/${project.id}/${table.id}/find-one`) |
||||
.set('xc-auth', context.token); |
||||
|
||||
return response.body; |
||||
}; |
||||
|
||||
const generateDefaultRowAttributes = ({ |
||||
columns, |
||||
index = 0, |
||||
}: { |
||||
columns: ColumnType[]; |
||||
index?: number; |
||||
}) => |
||||
columns.reduce((acc, column) => { |
||||
if ( |
||||
column.uidt === UITypes.LinkToAnotherRecord || |
||||
column.uidt === UITypes.ForeignKey || |
||||
column.uidt === UITypes.ID |
||||
) { |
||||
return acc; |
||||
} |
||||
acc[column.title!] = rowValue(column, index); |
||||
return acc; |
||||
}, {}); |
||||
|
||||
const createRow = async ( |
||||
context, |
||||
{ |
||||
project, |
||||
table, |
||||
index = 0, |
||||
}: { |
||||
project: Project; |
||||
table: Model; |
||||
index?: number; |
||||
} |
||||
) => { |
||||
const columns = await table.getColumns(); |
||||
const rowData = generateDefaultRowAttributes({ columns, index }); |
||||
|
||||
const response = await request(context.app) |
||||
.post(`/api/v1/db/data/noco/${project.id}/${table.id}`) |
||||
.set('xc-auth', context.token) |
||||
.send(rowData); |
||||
|
||||
return response.body; |
||||
}; |
||||
|
||||
const createBulkRows = async ( |
||||
context, |
||||
{ |
||||
project, |
||||
table, |
||||
values |
||||
}: { |
||||
project: Project; |
||||
table: Model; |
||||
values: any[]; |
||||
}) => { |
||||
await request(context.app) |
||||
.post(`/api/v1/db/data/bulk/noco/${project.id}/${table.id}`) |
||||
.set('xc-auth', context.token) |
||||
.send(values) |
||||
.expect(200); |
||||
} |
||||
|
||||
// Links 2 table rows together. Will create rows if ids are not provided
|
||||
const createChildRow = async ( |
||||
context, |
||||
{ |
||||
project, |
||||
table, |
||||
childTable, |
||||
column, |
||||
rowId, |
||||
childRowId, |
||||
type, |
||||
}: { |
||||
project: Project; |
||||
table: Model; |
||||
childTable: Model; |
||||
column: Column; |
||||
rowId?: string; |
||||
childRowId?: string; |
||||
type: string; |
||||
} |
||||
) => { |
||||
if (!rowId) { |
||||
const row = await createRow(context, { project, table }); |
||||
rowId = row['Id']; |
||||
} |
||||
|
||||
if (!childRowId) { |
||||
const row = await createRow(context, { table: childTable, project }); |
||||
childRowId = row['Id']; |
||||
} |
||||
|
||||
await request(context.app) |
||||
.post( |
||||
`/api/v1/db/data/noco/${project.id}/${table.id}/${rowId}/${type}/${column.title}/${childRowId}` |
||||
) |
||||
.set('xc-auth', context.token); |
||||
|
||||
const row = await getRow(context, { project, table, id: rowId }); |
||||
|
||||
return row; |
||||
}; |
||||
|
||||
export { |
||||
createRow, |
||||
getRow, |
||||
createChildRow, |
||||
getOneRow, |
||||
listRow, |
||||
generateDefaultRowAttributes, |
||||
createBulkRows |
||||
}; |
@ -0,0 +1,42 @@
|
||||
import request from 'supertest'; |
||||
import Model from '../../../src/lib/models/Model'; |
||||
import Project from '../../../src/lib/models/Project'; |
||||
import { defaultColumns } from './column'; |
||||
|
||||
const defaultTableValue = (context) => ({ |
||||
table_name: 'Table1', |
||||
title: 'Table1_Title', |
||||
columns: defaultColumns(context), |
||||
}); |
||||
|
||||
const createTable = async (context, project, args = {}) => { |
||||
const defaultValue = defaultTableValue(context); |
||||
const response = await request(context.app) |
||||
.post(`/api/v1/db/meta/projects/${project.id}/tables`) |
||||
.set('xc-auth', context.token) |
||||
.send({ ...defaultValue, ...args }); |
||||
|
||||
const table: Model = await Model.get(response.body.id); |
||||
return table; |
||||
}; |
||||
|
||||
const getTable = async ({project, name}: {project: Project, name: string}) => { |
||||
const bases = await project.getBases(); |
||||
return await Model.getByIdOrName({ |
||||
project_id: project.id, |
||||
base_id: bases[0].id!, |
||||
table_name: name, |
||||
}); |
||||
} |
||||
|
||||
const getAllTables = async ({project}: {project: Project}) => { |
||||
const bases = await project.getBases(); |
||||
const tables = await Model.list({ |
||||
project_id: project.id, |
||||
base_id: bases[0].id!, |
||||
}); |
||||
|
||||
return tables; |
||||
} |
||||
|
||||
export { createTable, getTable, getAllTables }; |
@ -0,0 +1,18 @@
|
||||
import request from 'supertest'; |
||||
import User from '../../../src/lib/models/User'; |
||||
|
||||
const defaultUserArgs = { |
||||
email: 'test@example.com', |
||||
password: 'A1234abh2@dsad', |
||||
}; |
||||
|
||||
const createUser = async (context, userArgs = {}) => { |
||||
const args = { ...defaultUserArgs, ...userArgs }; |
||||
const response = await request(context.app) |
||||
.post('/api/v1/auth/user/signup') |
||||
.send(args); |
||||
const user = User.getByEmail(args.email); |
||||
return { token: response.body.token, user }; |
||||
}; |
||||
|
||||
export { createUser, defaultUserArgs }; |
@ -0,0 +1,35 @@
|
||||
import { ViewTypes } from 'nocodb-sdk'; |
||||
import request from 'supertest'; |
||||
import Model from '../../../src/lib/models/Model'; |
||||
import View from '../../../src/lib/models/View'; |
||||
|
||||
const createView = async (context, {title, table, type}: {title: string, table: Model, type: ViewTypes}) => { |
||||
const viewTypeStr = (type) => { |
||||
switch (type) { |
||||
case ViewTypes.GALLERY: |
||||
return 'galleries'; |
||||
case ViewTypes.FORM: |
||||
return 'forms'; |
||||
case ViewTypes.GRID: |
||||
return 'grids'; |
||||
case ViewTypes.KANBAN: |
||||
return 'kanbans'; |
||||
default: |
||||
throw new Error('Invalid view type'); |
||||
} |
||||
}; |
||||
|
||||
await request(context.app) |
||||
.post(`/api/v1/db/meta/tables/${table.id}/${viewTypeStr(type)}`) |
||||
.set('xc-auth', context.token) |
||||
.send({ |
||||
title, |
||||
type, |
||||
}); |
||||
|
||||
const view = await View.getByTitleOrId({fk_model_id: table.id, titleOrId:title}) as View; |
||||
|
||||
return view |
||||
} |
||||
|
||||
export {createView} |
@ -0,0 +1,20 @@
|
||||
import 'mocha'; |
||||
|
||||
import restTests from './rest/index.test'; |
||||
import modelTests from './model/index.test'; |
||||
import TestDbMngr from './TestDbMngr' |
||||
|
||||
process.env.NODE_ENV = 'test'; |
||||
process.env.TEST = 'test'; |
||||
process.env.NC_DISABLE_CACHE = 'true'; |
||||
process.env.NC_DISABLE_TELE = 'true'; |
||||
|
||||
|
||||
(async function() { |
||||
await TestDbMngr.init(); |
||||
|
||||
modelTests(); |
||||
restTests(); |
||||
|
||||
run(); |
||||
})(); |
@ -0,0 +1,56 @@
|
||||
import Model from "../../../src/lib/models/Model"; |
||||
import Project from "../../../src/lib/models/Project"; |
||||
import NcConnectionMgrv2 from "../../../src/lib/utils/common/NcConnectionMgrv2"; |
||||
import { orderedMetaTables } from "../../../src/lib/utils/globals"; |
||||
import TestDbMngr from "../TestDbMngr"; |
||||
|
||||
const dropTablesAllNonExternalProjects = async () => { |
||||
const projects = await Project.list({}); |
||||
const userCreatedTableNames: string[] = []; |
||||
await Promise.all( |
||||
projects |
||||
.filter((project) => project.is_meta) |
||||
.map(async (project) => { |
||||
await project.getBases(); |
||||
const base = project.bases && project.bases[0]; |
||||
if (!base) return; |
||||
|
||||
const models = await Model.list({ |
||||
project_id: project.id, |
||||
base_id: base.id!, |
||||
}); |
||||
models.forEach((model) => { |
||||
userCreatedTableNames.push(model.table_name); |
||||
}); |
||||
}) |
||||
); |
||||
|
||||
await TestDbMngr.disableForeignKeyChecks(TestDbMngr.metaKnex); |
||||
|
||||
for (const tableName of userCreatedTableNames) { |
||||
await TestDbMngr.metaKnex.raw(`DROP TABLE ${tableName}`); |
||||
} |
||||
|
||||
await TestDbMngr.enableForeignKeyChecks(TestDbMngr.metaKnex); |
||||
}; |
||||
|
||||
const cleanupMetaTables = async () => { |
||||
await TestDbMngr.disableForeignKeyChecks(TestDbMngr.metaKnex); |
||||
for (const tableName of orderedMetaTables) { |
||||
try { |
||||
await TestDbMngr.metaKnex.raw(`DELETE FROM ${tableName}`); |
||||
} catch (e) {} |
||||
} |
||||
await TestDbMngr.enableForeignKeyChecks(TestDbMngr.metaKnex); |
||||
}; |
||||
|
||||
export default async function () { |
||||
try { |
||||
await NcConnectionMgrv2.destroyAll(); |
||||
|
||||
await dropTablesAllNonExternalProjects(); |
||||
await cleanupMetaTables(); |
||||
} catch (e) { |
||||
console.error('cleanupMeta', e); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue