* Make /db top-level module * Create Album type * Pin pnpm version * Generalize query modules * Finalize album postgres data type * Remove temp albums prop * Create basic album primitives * Fix temporary album bugs * Add albums to sidebar * Disambiguate string date utilities * Localize album language * Add album join option to core photo queries * Tweak album icon placement * Add album photo detail page * Refine Album data model * Display album subhead when available * Generate album og images * Finalize album share modal * Add albums to sitemap * Statically pre-render albums * Display tags on albums * Add albums to cmd-k menu * Handle album tag overflow * Stop truncating album subheads * Create core admin album views * Make albums editable * Create/edit albums on photo save, add delete album
125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import { safelyQuery } from '@/db/query';
|
|
import { sql } from '@/platforms/postgres';
|
|
import { Album, Albums, parseAlbumFromDb } from '.';
|
|
|
|
export const createAlbumsTable = () =>
|
|
sql`
|
|
CREATE TABLE IF NOT EXISTS albums (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
title VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(255) UNIQUE NOT NULL,
|
|
subhead TEXT,
|
|
description TEXT,
|
|
location_name VARCHAR(255),
|
|
latitude DOUBLE PRECISION,
|
|
longitude DOUBLE PRECISION,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`;
|
|
|
|
export const createAlbumPhotoTable = () =>
|
|
sql`
|
|
CREATE TABLE IF NOT EXISTS album_photo (
|
|
album_id uuid NOT NULL REFERENCES albums(id) ON DELETE CASCADE,
|
|
photo_id VARCHAR(8) NOT NULL REFERENCES photos(id) ON DELETE CASCADE,
|
|
sort_order SMALLINT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (album_id, photo_id)
|
|
)
|
|
`;
|
|
|
|
export const insertAlbum = (album: Omit<Album, 'id'>) =>
|
|
safelyQuery(() => sql`
|
|
INSERT INTO albums (
|
|
title,
|
|
slug,
|
|
subhead,
|
|
description,
|
|
location_name,
|
|
latitude,
|
|
longitude
|
|
) VALUES (
|
|
${album.title},
|
|
${album.slug},
|
|
${album.subhead},
|
|
${album.description},
|
|
${album.locationName},
|
|
${album.latitude},
|
|
${album.longitude}
|
|
)
|
|
RETURNING id
|
|
`.then(({ rows }) => rows[0]?.id as string)
|
|
, 'insertAlbum');
|
|
|
|
export const updateAlbum = (album: Album) =>
|
|
safelyQuery(() => sql`
|
|
UPDATE albums SET
|
|
title=${album.title},
|
|
slug=${album.slug},
|
|
subhead=${album.subhead},
|
|
description=${album.description},
|
|
location_name=${album.locationName},
|
|
latitude=${album.latitude},
|
|
longitude=${album.longitude},
|
|
updated_at=${(new Date()).toISOString()}
|
|
WHERE id=${album.id}
|
|
`, 'updateAlbum');
|
|
|
|
export const getAlbumFromSlug = (slug: string) =>
|
|
safelyQuery(() => sql<Album>`
|
|
SELECT * FROM albums WHERE slug=${slug}
|
|
`.then(({ rows }) => rows[0] ? parseAlbumFromDb(rows[0]) : undefined)
|
|
, 'getAlbum');
|
|
|
|
export const deleteAlbum = (id: string) =>
|
|
safelyQuery(() => sql`
|
|
DELETE FROM albums WHERE id=${id}
|
|
`, 'deleteAlbum');
|
|
|
|
export const getAlbumsWithMeta = () =>
|
|
safelyQuery(() => sql`
|
|
SELECT
|
|
a.*,
|
|
COALESCE(COUNT(ap.photo_id), 0) as count
|
|
FROM albums a
|
|
LEFT JOIN album_photo ap ON a.id = ap.album_id
|
|
GROUP BY a.id
|
|
ORDER BY a.created_at DESC
|
|
`.then(({ rows }): Albums => rows.map(({
|
|
count,
|
|
...album
|
|
}) => ({
|
|
album: parseAlbumFromDb(album),
|
|
count: parseInt(count, 10),
|
|
lastModified: album.updated_at as Date,
|
|
})))
|
|
, 'getAlbumsWithPhotoCounts');
|
|
|
|
export const clearPhotoAlbumIds = (photoId: string) =>
|
|
safelyQuery(() => sql`
|
|
DELETE FROM album_photo WHERE photo_id=${photoId}
|
|
`, 'clearPhotoAlbumIds');
|
|
|
|
export const addPhotoAlbumId = (photoId: string, albumId: string) =>
|
|
safelyQuery(() => sql`
|
|
INSERT INTO album_photo (album_id, photo_id) VALUES (${albumId}, ${photoId})
|
|
ON CONFLICT (album_id, photo_id) DO NOTHING
|
|
`, 'updateAlbumPhoto');
|
|
|
|
export const getAlbumTitlesForPhoto = (photoId: string) =>
|
|
safelyQuery(() => sql<{ title: string }>`
|
|
SELECT a.title FROM albums a
|
|
JOIN album_photo ap ON a.id = ap.album_id
|
|
WHERE ap.photo_id=${photoId}
|
|
`.then(({ rows }) => rows.map(({ title }) => title))
|
|
, 'getAlbumTitlesForPhoto');
|
|
|
|
export const getTagsForAlbum = (albumId: string) =>
|
|
safelyQuery(() => sql`
|
|
SELECT DISTINCT unnest(p.tags) as tag
|
|
FROM photos p
|
|
LEFT JOIN album_photo ap ON p.id = ap.photo_id
|
|
WHERE album_id=${albumId}
|
|
`.then(({ rows }) => rows.map(({ tag }) => tag))
|
|
, 'getTagsForAlbum');
|