Skip to content

Commit a4470c0

Browse files
committed
Share save/lookup
1 parent d9ce6dd commit a4470c0

10 files changed

+3785
-3608
lines changed

app/db/schema.ts

+33
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,36 @@ export const regex_link = pgTable(
3636
rxl_url_idx: uniqueIndex("rxl_url_idx").on(table.rxl_url),
3737
})
3838
);
39+
40+
export const regex_share = pgTable(
41+
"regex_share",
42+
{
43+
rxs_id: text("rxs_id")
44+
.notNull()
45+
.primaryKey()
46+
.default(sql`CONCAT('rxs_', gen_random_uuid()::VARCHAR)`),
47+
rxs_created_at: timestamp("rxs_created_at", { withTimezone: true })
48+
.notNull()
49+
.default(sql`now()`),
50+
rxs_updated_at: timestamp("rxs_updated_at", { withTimezone: true })
51+
.notNull()
52+
.default(sql`now()`),
53+
rxs_share_code: text("rxs_share_code").notNull(),
54+
rxs_title: text("rxs_title"),
55+
rxs_regex: text("rxs_regex").notNull(),
56+
rxs_replacement: text("rxs_replacement"),
57+
rxs_inputs: text("rxs_inputs")
58+
.array()
59+
.notNull()
60+
.default(sql`ARRAY[]::text[]`)
61+
.$type<string[]>(),
62+
rxs_options: text("rxs_options")
63+
.array()
64+
.notNull()
65+
.default(sql`ARRAY[]::text[]`)
66+
.$type<string[]>(),
67+
},
68+
(table) => ({
69+
rxs_share_code_idx: index("rxs_share_code_idx").on(table.rxs_share_code),
70+
})
71+
);

app/routes/sharing.lookup[.]json.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { LoaderFunctionArgs } from "@remix-run/node";
2+
import { eq } from "drizzle-orm";
3+
4+
import { dborm } from "~/db/connection.server";
5+
import { regex_share } from "~/db/schema";
6+
import { handleJsonp } from "~/util/handleJsonp";
7+
8+
export async function loader({ request }: LoaderFunctionArgs) {
9+
const { searchParams } = new URL(request.url);
10+
11+
const share_code = searchParams.get("share");
12+
if (!share_code) {
13+
return handleJsonp(request, {
14+
success: false,
15+
message: "No share code provided",
16+
});
17+
}
18+
19+
const theShares = await dborm.select().from(regex_share).where(eq(regex_share.rxs_share_code, share_code));
20+
21+
if (!theShares || theShares.length != 1) {
22+
return handleJsonp(request, {
23+
success: false,
24+
message: `Share code "${share_code}" not found`,
25+
share: share_code,
26+
});
27+
}
28+
29+
return handleJsonp(request, {
30+
success: true,
31+
share: share_code,
32+
regex: {
33+
title: theShares[0].rxs_title,
34+
regex: theShares[0].rxs_regex,
35+
replacement: theShares[0].rxs_replacement,
36+
inputs: theShares[0].rxs_inputs,
37+
options: theShares[0].rxs_options,
38+
//LATER: engines
39+
},
40+
});
41+
}

app/routes/sharing.new[.]json.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
2+
import { desc } from "drizzle-orm";
3+
import Haikunator from "haikunator";
4+
5+
import { dborm } from "~/db/connection.server";
6+
import { regex_share } from "~/db/schema";
7+
import { authenticator } from "~/services/auth.server";
8+
import { getFormString } from "~/util/getFormString";
9+
import { handleJsonp } from "~/util/handleJsonp";
10+
11+
export async function loader({ request }: LoaderFunctionArgs) {
12+
13+
if (request.method !== "POST") {
14+
return handleJsonp(request, { success: false, message: "Invalid request method" });
15+
}
16+
}
17+
18+
export async function action({ request }: ActionFunctionArgs) {
19+
20+
const user = await authenticator.isAuthenticated(request);
21+
22+
const formData = await request.formData();
23+
24+
const regex = getFormString(formData.get("regex"));
25+
if (!regex) {
26+
return handleJsonp(request, { success: false, message: "No regex provided" });
27+
}
28+
const haikunator = new Haikunator({ defaults: { tokenLength: 3 }});
29+
const share_code = haikunator.haikunate();
30+
31+
32+
await dborm.insert(regex_share).values({
33+
rxs_regex: regex,
34+
rxs_share_code: share_code,
35+
rxs_title: getFormString(formData.get("title")),
36+
rxs_replacement: getFormString(formData.get("replacement")),
37+
rxs_inputs: (formData.getAll("input") as string[]) || [],
38+
rxs_options: (formData.getAll("options") as string[]) || [],
39+
});
40+
41+
return handleJsonp(request, {
42+
success: true,
43+
message: `Regex saved with share code: ${share_code}`,
44+
share: share_code
45+
});
46+
}

app/routes/status[.]json.tsx

+2-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { LoaderFunctionArgs } from "@remix-run/node";
2+
import { handleJsonp } from "~/util/handleJsonp";
23

34
export async function loader({
45
request,
@@ -13,22 +14,5 @@ export async function loader({
1314
commit: process.env.COMMIT || '(not set)',
1415
});
1516

16-
const u = new URL(request.url);
17-
const callback = u.searchParams.get('callback');
18-
if (callback && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(callback)) {
19-
return new Response(`${callback}(${jsonStr});`, {
20-
headers: {
21-
"Content-Type": "text/javascript; charset=utf-8",
22-
},
23-
});
24-
} else {
25-
return new Response(jsonStr, {
26-
headers: {
27-
'Access-Control-Allow-Origin': '*',
28-
'Access-Control-Allow-Methods': 'POST, GET',
29-
'Access-Control-Max-Age': '604800',
30-
'Content-Type': 'application/json; charset=utf-8',
31-
},
32-
});
33-
}
17+
return handleJsonp(request, jsonStr);
3418
}

app/util/handleJsonp.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function handleJsonp(request: Request, data: NonNullable<unknown>) {
2+
3+
const jsonStr = JSON.stringify(data);
4+
5+
const u = new URL(request.url);
6+
const callback = u.searchParams.get("callback");
7+
if (callback && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(callback)) {
8+
return new Response(`${callback}(${jsonStr});`, {
9+
headers: {
10+
"Content-Type": "text/javascript; charset=utf-8",
11+
},
12+
});
13+
} else {
14+
return new Response(jsonStr, {
15+
headers: {
16+
"Access-Control-Allow-Origin": "*",
17+
"Access-Control-Allow-Methods": "POST, GET",
18+
"Access-Control-Max-Age": "604800",
19+
"Content-Type": "application/json; charset=utf-8",
20+
},
21+
});
22+
}
23+
}

migrations/0001_charming_callisto.sql

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CREATE TABLE IF NOT EXISTS "regex_share" (
2+
"rxs_id" text PRIMARY KEY DEFAULT CONCAT('rxs_', gen_random_uuid()::VARCHAR) NOT NULL,
3+
"rxs_created_at" timestamp with time zone DEFAULT now() NOT NULL,
4+
"rxs_updated_at" timestamp with time zone DEFAULT now() NOT NULL,
5+
"rxs_share_code" text NOT NULL,
6+
"rxs_title" text,
7+
"rxs_regex" text NOT NULL,
8+
"rxs_replacement" text,
9+
"rxs_inputs" text[] DEFAULT ARRAY[]::text[] NOT NULL,
10+
"rxs_options" text[] DEFAULT ARRAY[]::text[] NOT NULL
11+
);
12+
--> statement-breakpoint
13+
CREATE INDEX IF NOT EXISTS "rxs_share_code_idx" ON "regex_share" USING btree ("rxs_share_code");

migrations/meta/0001_snapshot.json

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
{
2+
"id": "0fcc63ae-b7cc-4479-b040-c0255ded54ec",
3+
"prevId": "bc0f03a2-434a-4e5a-b750-79955b309eeb",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.regex_link": {
8+
"name": "regex_link",
9+
"schema": "",
10+
"columns": {
11+
"rxl_id": {
12+
"name": "rxl_id",
13+
"type": "text",
14+
"primaryKey": true,
15+
"notNull": true,
16+
"default": "CONCAT('rxl_', gen_random_uuid()::VARCHAR)"
17+
},
18+
"rxl_created_at": {
19+
"name": "rxl_created_at",
20+
"type": "timestamp with time zone",
21+
"primaryKey": false,
22+
"notNull": true,
23+
"default": "now()"
24+
},
25+
"rxl_updated_at": {
26+
"name": "rxl_updated_at",
27+
"type": "timestamp with time zone",
28+
"primaryKey": false,
29+
"notNull": true,
30+
"default": "now()"
31+
},
32+
"rxl_title": {
33+
"name": "rxl_title",
34+
"type": "text",
35+
"primaryKey": false,
36+
"notNull": true
37+
},
38+
"rxl_url": {
39+
"name": "rxl_url",
40+
"type": "text",
41+
"primaryKey": false,
42+
"notNull": true
43+
},
44+
"rxl_cached": {
45+
"name": "rxl_cached",
46+
"type": "jsonb",
47+
"primaryKey": false,
48+
"notNull": true,
49+
"default": "'{}'::jsonb"
50+
},
51+
"rxl_tags": {
52+
"name": "rxl_tags",
53+
"type": "text[]",
54+
"primaryKey": false,
55+
"notNull": true,
56+
"default": "ARRAY[]::text[]"
57+
}
58+
},
59+
"indexes": {
60+
"created_at_idx": {
61+
"name": "created_at_idx",
62+
"columns": [
63+
{
64+
"expression": "rxl_created_at",
65+
"isExpression": false,
66+
"asc": true,
67+
"nulls": "last"
68+
}
69+
],
70+
"isUnique": false,
71+
"concurrently": false,
72+
"method": "btree",
73+
"with": {}
74+
},
75+
"rxl_url_idx": {
76+
"name": "rxl_url_idx",
77+
"columns": [
78+
{
79+
"expression": "rxl_url",
80+
"isExpression": false,
81+
"asc": true,
82+
"nulls": "last"
83+
}
84+
],
85+
"isUnique": true,
86+
"concurrently": false,
87+
"method": "btree",
88+
"with": {}
89+
}
90+
},
91+
"foreignKeys": {},
92+
"compositePrimaryKeys": {},
93+
"uniqueConstraints": {}
94+
},
95+
"public.regex_share": {
96+
"name": "regex_share",
97+
"schema": "",
98+
"columns": {
99+
"rxs_id": {
100+
"name": "rxs_id",
101+
"type": "text",
102+
"primaryKey": true,
103+
"notNull": true,
104+
"default": "CONCAT('rxs_', gen_random_uuid()::VARCHAR)"
105+
},
106+
"rxs_created_at": {
107+
"name": "rxs_created_at",
108+
"type": "timestamp with time zone",
109+
"primaryKey": false,
110+
"notNull": true,
111+
"default": "now()"
112+
},
113+
"rxs_updated_at": {
114+
"name": "rxs_updated_at",
115+
"type": "timestamp with time zone",
116+
"primaryKey": false,
117+
"notNull": true,
118+
"default": "now()"
119+
},
120+
"rxs_share_code": {
121+
"name": "rxs_share_code",
122+
"type": "text",
123+
"primaryKey": false,
124+
"notNull": true
125+
},
126+
"rxs_title": {
127+
"name": "rxs_title",
128+
"type": "text",
129+
"primaryKey": false,
130+
"notNull": false
131+
},
132+
"rxs_regex": {
133+
"name": "rxs_regex",
134+
"type": "text",
135+
"primaryKey": false,
136+
"notNull": true
137+
},
138+
"rxs_replacement": {
139+
"name": "rxs_replacement",
140+
"type": "text",
141+
"primaryKey": false,
142+
"notNull": false
143+
},
144+
"rxs_inputs": {
145+
"name": "rxs_inputs",
146+
"type": "text[]",
147+
"primaryKey": false,
148+
"notNull": true,
149+
"default": "ARRAY[]::text[]"
150+
},
151+
"rxs_options": {
152+
"name": "rxs_options",
153+
"type": "text[]",
154+
"primaryKey": false,
155+
"notNull": true,
156+
"default": "ARRAY[]::text[]"
157+
}
158+
},
159+
"indexes": {
160+
"rxs_share_code_idx": {
161+
"name": "rxs_share_code_idx",
162+
"columns": [
163+
{
164+
"expression": "rxs_share_code",
165+
"isExpression": false,
166+
"asc": true,
167+
"nulls": "last"
168+
}
169+
],
170+
"isUnique": false,
171+
"concurrently": false,
172+
"method": "btree",
173+
"with": {}
174+
}
175+
},
176+
"foreignKeys": {},
177+
"compositePrimaryKeys": {},
178+
"uniqueConstraints": {}
179+
}
180+
},
181+
"enums": {},
182+
"schemas": {},
183+
"sequences": {},
184+
"_meta": {
185+
"columns": {},
186+
"schemas": {},
187+
"tables": {}
188+
}
189+
}

0 commit comments

Comments
 (0)