From 7e9f982ef3700f14865d3b9367ce1869ad045f50 Mon Sep 17 00:00:00 2001 From: Zhi Zhen Date: Wed, 10 Dec 2025 10:58:09 +0800 Subject: [PATCH 1/3] make upload json --- packages/data-types/types/README.md | 5 +-- .../types/__tests__/domains.pgutils.test.ts | 32 ++++++++++++++++++- .../types/__tests__/domains.test.ts | 32 ++++++++++++++++++- .../deploy/schemas/public/domains/upload.sql | 6 +++- .../types/sql/launchql-types--0.9.0.sql | 9 ++++-- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/packages/data-types/types/README.md b/packages/data-types/types/README.md index 2cda9d940..37c7e6cfb 100644 --- a/packages/data-types/types/README.md +++ b/packages/data-types/types/README.md @@ -158,6 +158,7 @@ INSERT INTO customers (domain) VALUES ### Image and Attachment Domains The `image` domain stores JSON objects with URL and MIME type information. The `attachment` domain accepts either that JSON shape or a plain URL string. +The `upload` domain uses the same JSON object shape as `image`, ensuring both the file URL and MIME type are present. ```sql -- Valid image @@ -184,7 +185,7 @@ INSERT INTO customers (document) VALUES ('https://storage.example.com/favicon.ic | `hostname` | `text` | Domain name without protocol | `example.com` | | `image` | `json` | Image metadata with URL and MIME | `{"url": "...", "mime": "image/jpeg"}` | | `attachment` | `json` | File attachment URL or metadata | `{"url": "...", "mime": "application/pdf"}` or `https://example.com/favicon.ico` | -| `upload` | `text` | File upload identifier | Various formats | +| `upload` | `json` | File upload metadata (URL + MIME) | `{"url": "...", "mime": "application/pdf"}` | | `single_select` | `text` | Single selection value | Text value | | `multiple_select` | `text[]` | Multiple selection values | Array of text values | @@ -212,7 +213,7 @@ The test suite validates: - Email format validation (valid and invalid cases) - URL format validation with extensive test cases - Hostname format validation -- Image and attachment JSON structure validation +- Image, upload, and attachment JSON structure validation ## Related Tooling diff --git a/packages/data-types/types/__tests__/domains.pgutils.test.ts b/packages/data-types/types/__tests__/domains.pgutils.test.ts index 6be024231..7e01d98f1 100644 --- a/packages/data-types/types/__tests__/domains.pgutils.test.ts +++ b/packages/data-types/types/__tests__/domains.pgutils.test.ts @@ -64,6 +64,17 @@ const invalidImages = [ { url: 'https://foo.bar/some.png' } ]; +const validUploads = [ + { url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' }, + { url: 'https://foo.bar/some.PNG', mime: 'image/png' } +]; + +const invalidUploads = [ + { url: 'hi there' }, + { url: 'https://foo.bar/some.png' }, + { url: 'ftp://foo.bar/some.png', mime: 'image/png' } +]; + let pg: PgTestClient; let teardown: () => Promise; @@ -79,7 +90,8 @@ CREATE TABLE customers ( image image, attachment attachment, domain hostname, - email email + email email, + upload upload ); `); }); @@ -129,6 +141,24 @@ describe('types', () => { } }); + it('valid upload', async () => { + for (const upload of validUploads) { + await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]); + } + }); + + it('invalid upload', async () => { + for (const upload of invalidUploads) { + let failed = false; + try { + await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]); + } catch (e) { + failed = true; + } + expect(failed).toBe(true); + } + }); + it('valid url', async () => { for (const value of validUrls) { await pg.any(`INSERT INTO customers (url) VALUES ($1);`, [value]); diff --git a/packages/data-types/types/__tests__/domains.test.ts b/packages/data-types/types/__tests__/domains.test.ts index 6be024231..7e01d98f1 100644 --- a/packages/data-types/types/__tests__/domains.test.ts +++ b/packages/data-types/types/__tests__/domains.test.ts @@ -64,6 +64,17 @@ const invalidImages = [ { url: 'https://foo.bar/some.png' } ]; +const validUploads = [ + { url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' }, + { url: 'https://foo.bar/some.PNG', mime: 'image/png' } +]; + +const invalidUploads = [ + { url: 'hi there' }, + { url: 'https://foo.bar/some.png' }, + { url: 'ftp://foo.bar/some.png', mime: 'image/png' } +]; + let pg: PgTestClient; let teardown: () => Promise; @@ -79,7 +90,8 @@ CREATE TABLE customers ( image image, attachment attachment, domain hostname, - email email + email email, + upload upload ); `); }); @@ -129,6 +141,24 @@ describe('types', () => { } }); + it('valid upload', async () => { + for (const upload of validUploads) { + await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]); + } + }); + + it('invalid upload', async () => { + for (const upload of invalidUploads) { + let failed = false; + try { + await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]); + } catch (e) { + failed = true; + } + expect(failed).toBe(true); + } + }); + it('valid url', async () => { for (const value of validUrls) { await pg.any(`INSERT INTO customers (url) VALUES ($1);`, [value]); diff --git a/packages/data-types/types/deploy/schemas/public/domains/upload.sql b/packages/data-types/types/deploy/schemas/public/domains/upload.sql index 09a94337b..0af945bc8 100644 --- a/packages/data-types/types/deploy/schemas/public/domains/upload.sql +++ b/packages/data-types/types/deploy/schemas/public/domains/upload.sql @@ -4,7 +4,11 @@ BEGIN; -CREATE DOMAIN upload AS text CHECK (VALUE ~ '^(https?)://[^\s/$.?#].[^\s]*$'); +CREATE DOMAIN upload AS jsonb CHECK ( + value ?& ARRAY['url', 'mime'] + AND + value->>'url' ~ '^(https?)://[^\s/$.?#].[^\s]*$' +); COMMENT ON DOMAIN upload IS E'@name launchqlInternalTypeUpload'; COMMIT; diff --git a/packages/data-types/types/sql/launchql-types--0.9.0.sql b/packages/data-types/types/sql/launchql-types--0.9.0.sql index 6eecade07..ce5cef441 100644 --- a/packages/data-types/types/sql/launchql-types--0.9.0.sql +++ b/packages/data-types/types/sql/launchql-types--0.9.0.sql @@ -35,12 +35,15 @@ CREATE DOMAIN single_select AS jsonb COMMENT ON DOMAIN single_select IS '@name launchqlInternalTypeSingleSelect'; -CREATE DOMAIN upload AS text - CHECK (value ~ E'^(https?)://[^\\s/$.?#].[^\\s]*$'); +CREATE DOMAIN upload AS jsonb + CHECK ( + value ?& ARRAY['url', 'mime'] + AND (value ->> 'url') ~ E'^(https?)://[^\\s/$.?#].[^\\s]*$' +); COMMENT ON DOMAIN upload IS '@name launchqlInternalTypeUpload'; CREATE DOMAIN url AS text CHECK (value ~ E'^(https?)://[^\\s/$.?#].[^\\s]*$'); -COMMENT ON DOMAIN url IS '@name launchqlInternalTypeUrl'; \ No newline at end of file +COMMENT ON DOMAIN url IS '@name launchqlInternalTypeUrl'; From 0ae8a90c3615db82c41e7a2635c5223a8fb03eed Mon Sep 17 00:00:00 2001 From: Zhi Zhen Date: Wed, 10 Dec 2025 11:13:23 +0800 Subject: [PATCH 2/3] fix max worker count for achievements test --- packages/metrics/achievements/jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/metrics/achievements/jest.config.js b/packages/metrics/achievements/jest.config.js index e20e7efb5..db566b12b 100644 --- a/packages/metrics/achievements/jest.config.js +++ b/packages/metrics/achievements/jest.config.js @@ -2,6 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', + maxWorkers: 1, // Match both __tests__ and colocated test files testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], From 6d3cb2030327ad95830dc0fb81143fbec4525a45 Mon Sep 17 00:00:00 2001 From: Zhi Zhen Date: Wed, 10 Dec 2025 11:27:54 +0800 Subject: [PATCH 3/3] re-run --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 78d5994c1..b2b326b39 100644 --- a/Makefile +++ b/Makefile @@ -9,4 +9,4 @@ ssh: docker exec -it postgres /bin/bash install: - docker exec postgres /sql-bin/install.sh + docker exec postgres /sql-bin/install.sh \ No newline at end of file