Reformat project into workspaces
This commit is contained in:
1 parent
20bb10114b
commit
c3e2cf5ed3
38 files changed
+4863
-4186
No files matched your search
@@ -0,0 +1,14 @@
|
||||
-- migrate:up
|
||||
CREATE EXTENSION IF NOT EXISTS ltree;
|
||||
|
||||
CREATE TABLE categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
path ltree NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_categories_path_gist ON categories USING gist (path);
|
||||
|
||||
-- migrate:down
|
||||
DROP TABLE IF EXISTS categories;
|
||||
DROP EXTENSION IF EXISTS ltree;
|
||||
@@ -0,0 +1,118 @@
|
||||
\restrict dbmate
|
||||
|
||||
-- Dumped from database version 18.4 (Ubuntu 18.4-1.pgdg24.04+1)
|
||||
-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1)
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET transaction_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- Name: ltree; Type: EXTENSION; Schema: -; Owner: -
|
||||
--
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS ltree WITH SCHEMA public;
|
||||
|
||||
|
||||
--
|
||||
-- Name: EXTENSION ltree; Type: COMMENT; Schema: -; Owner: -
|
||||
--
|
||||
|
||||
COMMENT ON EXTENSION ltree IS 'data type for hierarchical tree-like structures';
|
||||
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: categories; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.categories (
|
||||
id integer NOT NULL,
|
||||
name text NOT NULL,
|
||||
path public.ltree NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.categories_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.categories_id_seq OWNED BY public.categories.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.schema_migrations (
|
||||
version character varying NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: categories id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.categories ALTER COLUMN id SET DEFAULT nextval('public.categories_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: categories categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.categories
|
||||
ADD CONSTRAINT categories_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.schema_migrations
|
||||
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_categories_path_gist; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx_categories_path_gist ON public.categories USING gist (path);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict dbmate
|
||||
|
||||
|
||||
--
|
||||
-- Dbmate schema migrations
|
||||
--
|
||||
|
||||
INSERT INTO public.schema_migrations (version) VALUES
|
||||
('20260824235922');
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "book-test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "tsx run-tests.ts",
|
||||
"db:migrate": "dotenv dbmate up"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Chris Smith",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/morgan": "^1.9.10",
|
||||
"@types/node": "^26.3.0",
|
||||
"@types/pg": "^8.23.1",
|
||||
"dbmate": "^2.35.0",
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"express": "^5.2.1",
|
||||
"morgan": "^1.11.0",
|
||||
"pg": "^8.23.0"
|
||||
},
|
||||
"allowScripts": {
|
||||
"dotenv-cli@11.0.0": true,
|
||||
"esbuild@0.28.2": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { run } from 'node:test';
|
||||
import { globSync } from 'node:fs';
|
||||
import { spec } from 'node:test/reporters';
|
||||
|
||||
const files = globSync('src/**/*.test.ts');
|
||||
|
||||
if (files.length === 0) {
|
||||
console.error('❌ No test files found in src/**/*.test.ts');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
run({ files })
|
||||
.compose(new spec())
|
||||
.pipe(process.stdout);
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { CategoryRepository } from './category.repository';
|
||||
|
||||
describe('CategoryRepository - ltree SQL Generator', () => {
|
||||
const repository = new CategoryRepository();
|
||||
|
||||
describe('generateDescendantsQuery()', () => {
|
||||
it('should generate valid SQL using the ltree descenant operator (<@)', () => {
|
||||
const parentPath = 'Top.Science';
|
||||
const result = repository.generateDescendantsQuery(parentPath);
|
||||
|
||||
// veriy the generated SQL syntax as correct ltree
|
||||
assert.match(result.sql, /<@/);
|
||||
assert.match(result.sql, /::ltree/);
|
||||
assert.deepEqual(result.values, ['Top.Science']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateWildcardQuery()', () => {
|
||||
it('should generate valid SQL utilizing an lquery matching operator (~)', () => {
|
||||
const result = repository.generateWildcardQuery('Top.*.Astronomy');
|
||||
|
||||
assert.match(result.sql, /~/);
|
||||
assert.match(result.sql, /::lquery/);
|
||||
assert.equal(result.values[0], 'Top.*.Astronomy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidLtreePath() validation utility', () => {
|
||||
it('should pass for correctly formatted dot-separated ltree structures', () => {
|
||||
assert.equal(repository.isValidLtreePath('Top.Hobbies.Amateurs_Astronomy'), true);
|
||||
});
|
||||
|
||||
it('should fail if special characters or spaces are introduced into the path', () => {
|
||||
assert.equal(repository.isValidLtreePath('Top.Hobbies.Bad Path!'), false);
|
||||
assert.equal(repository.isValidLtreePath('Top..InvalidDoubleDot'), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// structural interface to the test table
|
||||
export interface CatetoryNode {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string; // ltree field
|
||||
}
|
||||
|
||||
export class CategoryRepository {
|
||||
// simulate generating a SQL query targeting child paths
|
||||
generateDescendantsQuery(parentPath: string): { sql: string; values: string[] } {
|
||||
return {
|
||||
sql: 'SELECT id, name, path FROM categories WHERE path <@ $1::ltree',
|
||||
values: [parentPath]
|
||||
};
|
||||
}
|
||||
|
||||
generateWildcardQuery(pattern: string): { sql: string, values: string[] } {
|
||||
// Simulate genrating a SQL query using ltree wildcards
|
||||
return {
|
||||
sql: 'SELECT id, name, path FROM categories WHERE path ~ $1::lquery',
|
||||
values: [pattern]
|
||||
};
|
||||
}
|
||||
|
||||
// helper utility to validate a string as an ltree value
|
||||
isValidLtreePath(path: string): boolean {
|
||||
const ltreeRegex = /^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/;
|
||||
return ltreeRegex.test(path);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user