diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7ce3345f..e25b8baa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,6 +6,7 @@
"exif",
"hgetall",
"hset",
+ "nanoids",
"nextjs",
"qaub",
"skippable",
diff --git a/package.json b/package.json
index 45805db0..8a9320dc 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"eslint": "8.49.0",
"eslint-config-next": "13.4.19",
"framer-motion": "^10.16.4",
+ "nanoid": "^5.0.1",
"next": "^13.4.19",
"next-auth": "0.0.0-manual.ffd05533",
"next-themes": "^0.2.1",
@@ -30,7 +31,6 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.11.0",
- "short-uuid": "^4.2.2",
"sonner": "^0.7.4",
"tailwindcss": "3.3.3",
"ts-exif-parser": "^0.2.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dcfe3104..a0f37f45 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,7 @@ specifiers:
eslint: 8.49.0
eslint-config-next: 13.4.19
framer-motion: ^10.16.4
+ nanoid: ^5.0.1
next: ^13.4.19
next-auth: 0.0.0-manual.ffd05533
next-themes: ^0.2.1
@@ -24,7 +25,6 @@ specifiers:
react: 18.2.0
react-dom: 18.2.0
react-icons: ^4.11.0
- short-uuid: ^4.2.2
sonner: ^0.7.4
tailwindcss: 3.3.3
ts-exif-parser: ^0.2.2
@@ -47,6 +47,7 @@ dependencies:
eslint: 8.49.0
eslint-config-next: 13.4.19_rngtr6f3b25lvetpihwplgecf4
framer-motion: 10.16.4_biqbaboplfbrettd7655fr4n2y
+ nanoid: 5.0.1
next: 13.4.19_biqbaboplfbrettd7655fr4n2y
next-auth: 0.0.0-manual.ffd05533_next@13.4.19+react@18.2.0
next-themes: 0.2.1_m2l4knchnqa6bzxbbtfxlzoxdy
@@ -54,7 +55,6 @@ dependencies:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-icons: 4.11.0_react@18.2.0
- short-uuid: 4.2.2
sonner: 0.7.4_biqbaboplfbrettd7655fr4n2y
tailwindcss: 3.3.3
ts-exif-parser: 0.2.2
@@ -761,10 +761,6 @@ packages:
engines: {node: '>=10'}
dev: false
- /any-base/1.1.0:
- resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==}
- dev: false
-
/any-promise/1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
dev: false
@@ -2524,6 +2520,12 @@ packages:
hasBin: true
dev: false
+ /nanoid/5.0.1:
+ resolution: {integrity: sha512-vWeVtV5Cw68aML/QaZvqN/3QQXc6fBfIieAlu05m7FZW2Dgb+3f0xc0TTxuJW+7u30t7iSDTV/j3kVI0oJqIfQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+ dev: false
+
/natural-compare/1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: false
@@ -3184,14 +3186,6 @@ packages:
engines: {node: '>=8'}
dev: false
- /short-uuid/4.2.2:
- resolution: {integrity: sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==}
- engines: {node: '>=8'}
- dependencies:
- any-base: 1.1.0
- uuid: 8.3.2
- dev: false
-
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@@ -3596,11 +3590,6 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
- /uuid/8.3.2:
- resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
- hasBin: true
- dev: false
-
/w3c-xmlserializer/4.0.0:
resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
engines: {node: '>=14'}
diff --git a/src/photo/PhotoOGTile.tsx b/src/photo/PhotoOGTile.tsx
index a1b68ecc..20c39525 100644
--- a/src/photo/PhotoOGTile.tsx
+++ b/src/photo/PhotoOGTile.tsx
@@ -79,7 +79,7 @@ export default function PhotoOGTile({
}
{(loadingState === 'loading' || loadingState === 'loaded') &&
;
@@ -19,21 +20,10 @@ type FormMeta = {
hideTemporarily?: boolean
};
-const FORM_METADATA: Record<
- // Display short ids as a convenience
- // even though they're not part of form data
- keyof PhotoFormData & { idShort?: string },
- FormMeta
-> = {
+const FORM_METADATA: Record = {
title: { label: 'title' },
tags: { label: 'tags', note: 'comma-separated values' },
id: { label: 'id', readOnly: true, hideIfEmpty: true },
- idShort: {
- label: 'short id',
- readOnly: true,
- hideIfEmpty: true,
- note: 'Auto-generated by id',
- },
url: { label: 'url', readOnly: true },
extension: { label: 'extension', readOnly: true },
aspectRatio: { label: 'aspect ratio', readOnly: true },
@@ -122,7 +112,7 @@ export const convertFormDataToPhoto = (
return {
...photoForm,
- ...(generateId && !photoForm.id) && { id: crypto.randomUUID() },
+ ...(generateId && !photoForm.id) && { id: generateNanoid() },
// convert form strings to arrays
tags: convertStringToArray(photoForm.tags),
// Convert form strings to numbers
diff --git a/src/photo/index.ts b/src/photo/index.ts
index 2798ba1e..e1d86e49 100644
--- a/src/photo/index.ts
+++ b/src/photo/index.ts
@@ -12,9 +12,6 @@ import {
} from '@/utility/exif';
import camelcaseKeys from 'camelcase-keys';
import { Metadata } from 'next';
-import short from 'short-uuid';
-
-const translator = short();
export const GRID_THUMBNAILS_TO_SHOW_MAX = 12;
@@ -58,7 +55,6 @@ export interface PhotoDb extends Omit {
// Parsed db response
export interface Photo extends PhotoDb {
- idShort?: string
focalLengthFormatted?: string
focalLengthIn35MmFormatFormatted?: string
fNumberFormatted?: string
@@ -75,8 +71,6 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
) as unknown as PhotoDb;
return {
...photoDb,
- idShort:
- translator.fromUUID(photoDb.id),
tags: photoDb.tags ?? [],
focalLengthFormatted:
formatFocalLength(photoDb.focalLength),
@@ -173,10 +167,8 @@ const PHOTO_ID_FORWARDING_TABLE: Record = JSON.parse(
process.env.PHOTO_ID_FORWARDING_TABLE || '{}'
);
-export const translatePhotoId = (shortId: string) => {
- const id = PHOTO_ID_FORWARDING_TABLE[shortId] || shortId;
- return id.length === 22 ? translator.toUUID(id) : id;
-};
+export const translatePhotoId = (id: string) =>
+ PHOTO_ID_FORWARDING_TABLE[id] || id;
export const titleForPhoto = (photo: Photo) =>
photo.title || 'Untitled';
diff --git a/src/services/postgres.ts b/src/services/postgres.ts
index 09f19f7b..edb0117c 100644
--- a/src/services/postgres.ts
+++ b/src/services/postgres.ts
@@ -6,7 +6,6 @@ import {
parsePhotoFromDb,
Photo,
} from '@/photo';
-import { isValidUUID } from '@/utility/string';
const PHOTO_DEFAULT_LIMIT = 100;
@@ -17,7 +16,7 @@ export const convertArrayToPostgresString = (array?: string[]) => array
const sqlCreatePhotosTable = () =>
sql`
CREATE TABLE IF NOT EXISTS photos (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ id VARCHAR(8) PRIMARY KEY,
url VARCHAR(255) NOT NULL,
extension VARCHAR(255) NOT NULL,
aspect_ratio REAL DEFAULT 1.5,
@@ -44,6 +43,7 @@ const sqlCreatePhotosTable = () =>
)
`;
+// Must provide id as 8-character nanoid
export const sqlInsertPhotoIntoDb = (photo: PhotoDbInsert) => {
return sql`
INSERT INTO photos (
@@ -256,8 +256,6 @@ export const getPhoto = async (id: string): Promise => {
// Check for photo id forwarding
// and convert short ids to uuids
const photoId = translatePhotoId(id);
- return isValidUUID(photoId)
- ? sqlGetPhotoFromDb(photoId)
- .then(photos => photos.length > 0 ? photos[0] : undefined)
- : Promise.resolve(undefined);
+ return sqlGetPhotoFromDb(photoId)
+ .then(photos => photos.length > 0 ? photos[0] : undefined);
};
diff --git a/src/site/paths.ts b/src/site/paths.ts
index a264aa65..9d5a69ee 100644
--- a/src/site/paths.ts
+++ b/src/site/paths.ts
@@ -13,14 +13,14 @@ export const ABSOLUTE_PATH_FOR_HOME_IMAGE = `${BASE_URL}/home-image`;
export const pathForPhoto = (photo: Photo, tag?: string) =>
tag
- ? `${pathForTag(tag)}/${photo.idShort}`
- : `${PREFIX_PHOTO}/${photo.idShort}`;
+ ? `${pathForTag(tag)}/${photo.id}`
+ : `${PREFIX_PHOTO}/${photo.id}`;
export const pathForPhotoShare = (photo: Photo, tag?: string) =>
`${pathForPhoto(photo, tag)}/share`;
export const pathForPhotoEdit = (photo: Photo) =>
- `${PATH_ADMIN_PHOTOS}/${photo.idShort}/edit`;
+ `${PATH_ADMIN_PHOTOS}/${photo.id}/edit`;
export const pathForTag = (tag: string) => `${PREFIX_TAG}/${tag}`;
diff --git a/src/utility/nanoid.ts b/src/utility/nanoid.ts
new file mode 100644
index 00000000..cb4e73cc
--- /dev/null
+++ b/src/utility/nanoid.ts
@@ -0,0 +1,9 @@
+import { customAlphabet } from 'nanoid';
+
+const NANOID_LENGTH = 8;
+
+const NANOID_ALPHABET =
+ '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+export const generateNanoid =
+ customAlphabet(NANOID_ALPHABET, NANOID_LENGTH);
diff --git a/src/utility/string.ts b/src/utility/string.ts
index e5fbfb91..ed8e8ab3 100644
--- a/src/utility/string.ts
+++ b/src/utility/string.ts
@@ -1,6 +1,3 @@
-export const isValidUUID = (id: string): boolean =>
- /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i.test(id);
-
export const convertStringToArray = (
string?: string,
parameterize = true,