feat: rename properties to projects
- Rename all DB tables: properties → projects, properties_addresses → projects_addresses, properties_models → projects_models, property_likes → projects_likes - Add projects_likes as proper Drizzle schema table (was SQL-only before) - Rename ID prefixes: property_xxx → project_xxx - Rename auth_sessions column: active_property_id → active_project_id - Simplify project creation: address is now optional (no longer required to get started) - Update all actions, stores, types, components, and routes - Rename route: /editor/[propertyId] → /editor/[projectId] - Add SQL migration for table/column/index/policy/function renames - Update RPC functions: increment_project_views, get_project_like_count - Update storage bucket reference: project-thumbnails - All types/exports follow existing Drizzle patterns (pgTable, relations, zod schemas)
This commit is contained in:
@@ -13,8 +13,8 @@ export const sessions = pgTable('auth_sessions', (t) => ({
|
||||
token: t.text('token').notNull(),
|
||||
ipAddress: t.text('ip_address'),
|
||||
userAgent: t.text('user_agent'),
|
||||
// Custom: active property for the session context
|
||||
activePropertyId: t.text('active_property_id'),
|
||||
// Custom: active project for the session context
|
||||
activeProjectId: t.text('active_project_id'),
|
||||
// Admin plugin support: tracks who is impersonating this session
|
||||
impersonatedBy: t
|
||||
.text('impersonated_by')
|
||||
|
||||
@@ -5,7 +5,8 @@ export * from './auth/sessions'
|
||||
export * from './auth/users'
|
||||
export * from './auth/verifications'
|
||||
|
||||
// Property tables
|
||||
export * from './properties/addresses'
|
||||
export * from './properties/models'
|
||||
export * from './properties/properties'
|
||||
// Project tables
|
||||
export * from './projects/addresses'
|
||||
export * from './projects/likes'
|
||||
export * from './projects/models'
|
||||
export * from './projects/projects'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { z } from 'zod'
|
||||
import { id, timestampsColumns } from '../../helpers'
|
||||
|
||||
export const addresses = pgTable(
|
||||
'properties_addresses',
|
||||
'projects_addresses',
|
||||
(t) => ({
|
||||
id: id('address'),
|
||||
streetNumber: t.text('street_number'),
|
||||
@@ -0,0 +1,36 @@
|
||||
import { relations } from 'drizzle-orm'
|
||||
import { pgTable, unique } from 'drizzle-orm/pg-core'
|
||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||
import { id, createdAt } from '../../helpers'
|
||||
import { projects } from './projects'
|
||||
import { users } from '../auth/users'
|
||||
|
||||
export const projectsLikes = pgTable(
|
||||
'projects_likes',
|
||||
(t) => ({
|
||||
id: id('like'),
|
||||
projectId: t
|
||||
.text('project_id')
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: 'cascade' }),
|
||||
userId: t
|
||||
.text('user_id')
|
||||
.notNull(),
|
||||
createdAt,
|
||||
}),
|
||||
(t) => [
|
||||
unique('projects_likes_project_user_unique').on(t.projectId, t.userId),
|
||||
],
|
||||
).enableRLS()
|
||||
|
||||
export const projectsLikesRelations = relations(projectsLikes, ({ one }) => ({
|
||||
project: one(projects, {
|
||||
fields: [projectsLikes.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export type ProjectLike = typeof projectsLikes.$inferSelect
|
||||
export type NewProjectLike = typeof projectsLikes.$inferInsert
|
||||
export const insertProjectLikeSchema = createInsertSchema(projectsLikes)
|
||||
export const selectProjectLikeSchema = createSelectSchema(projectsLikes)
|
||||
+8
-8
@@ -2,26 +2,26 @@ import { relations } from 'drizzle-orm'
|
||||
import { pgTable } from 'drizzle-orm/pg-core'
|
||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||
import { id, timestampsColumnsSoftDelete } from '../../helpers'
|
||||
import { properties } from './properties'
|
||||
import { projects } from './projects'
|
||||
|
||||
export const models = pgTable('properties_models', (t) => ({
|
||||
export const models = pgTable('projects_models', (t) => ({
|
||||
id: id('model'),
|
||||
name: t.text('name'),
|
||||
version: t.integer('version').default(1),
|
||||
description: t.text('description'),
|
||||
draft: t.boolean('draft').default(true),
|
||||
propertyId: t
|
||||
.text('property_id')
|
||||
.references(() => properties.id, { onDelete: 'set null' }),
|
||||
projectId: t
|
||||
.text('project_id')
|
||||
.references(() => projects.id, { onDelete: 'set null' }),
|
||||
sceneGraph: t.jsonb('scene_graph'),
|
||||
metadata: t.jsonb('metadata'),
|
||||
...timestampsColumnsSoftDelete,
|
||||
})).enableRLS()
|
||||
|
||||
export const modelsRelations = relations(models, ({ one }) => ({
|
||||
property: one(properties, {
|
||||
fields: [models.propertyId],
|
||||
references: [properties.id],
|
||||
project: one(projects, {
|
||||
fields: [models.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
+16
-17
@@ -5,15 +5,14 @@ import { id, timestampsColumns } from '../../helpers'
|
||||
import { users } from '../auth/users'
|
||||
import { addresses } from './addresses'
|
||||
|
||||
export const properties = pgTable(
|
||||
'properties',
|
||||
export const projects = pgTable(
|
||||
'projects',
|
||||
(t) => ({
|
||||
id: id('property'),
|
||||
id: id('project'),
|
||||
name: t.text('name'),
|
||||
addressId: t
|
||||
.text('address_id')
|
||||
.references(() => addresses.id, { onDelete: 'set null' })
|
||||
.unique(),
|
||||
.references(() => addresses.id, { onDelete: 'set null' }),
|
||||
ownerId: t
|
||||
.text('owner_id')
|
||||
.references(() => users.id, { onDelete: 'set null' }),
|
||||
@@ -27,26 +26,26 @@ export const properties = pgTable(
|
||||
...timestampsColumns,
|
||||
}),
|
||||
(t) => [
|
||||
index('property_address_idx').on(t.addressId),
|
||||
index('property_owner_idx').on(t.ownerId),
|
||||
index('property_is_private_idx').on(t.isPrivate),
|
||||
index('property_views_idx').on(t.views),
|
||||
index('property_likes_idx').on(t.likes),
|
||||
index('project_address_idx').on(t.addressId),
|
||||
index('project_owner_idx').on(t.ownerId),
|
||||
index('project_is_private_idx').on(t.isPrivate),
|
||||
index('project_views_idx').on(t.views),
|
||||
index('project_likes_idx').on(t.likes),
|
||||
],
|
||||
).enableRLS()
|
||||
|
||||
export const propertiesRelations = relations(properties, ({ one }) => ({
|
||||
export const projectsRelations = relations(projects, ({ one }) => ({
|
||||
address: one(addresses, {
|
||||
fields: [properties.addressId],
|
||||
fields: [projects.addressId],
|
||||
references: [addresses.id],
|
||||
}),
|
||||
owner: one(users, {
|
||||
fields: [properties.ownerId],
|
||||
fields: [projects.ownerId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export type Property = typeof properties.$inferSelect
|
||||
export type NewProperty = typeof properties.$inferInsert
|
||||
export const insertPropertySchema = createInsertSchema(properties)
|
||||
export const selectPropertySchema = createSelectSchema(properties)
|
||||
export type Project = typeof projects.$inferSelect
|
||||
export type NewProject = typeof projects.$inferInsert
|
||||
export const insertProjectSchema = createInsertSchema(projects)
|
||||
export const selectProjectSchema = createSelectSchema(projects)
|
||||
@@ -8,7 +8,7 @@ export type Json = string | number | boolean | null | { [key: string]: Json | un
|
||||
export interface Database {
|
||||
public: {
|
||||
Tables: {
|
||||
properties: {
|
||||
projects: {
|
||||
Row: {
|
||||
id: string
|
||||
name: string
|
||||
@@ -31,10 +31,10 @@ export interface Database {
|
||||
updated_at?: string
|
||||
}
|
||||
}
|
||||
properties_addresses: {
|
||||
projects_addresses: {
|
||||
Row: {
|
||||
id: string
|
||||
property_id: string
|
||||
project_id: string
|
||||
formatted_address: string
|
||||
street_number: string | null
|
||||
route: string | null
|
||||
@@ -51,7 +51,7 @@ export interface Database {
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
property_id: string
|
||||
project_id: string
|
||||
formatted_address: string
|
||||
street_number?: string | null
|
||||
route?: string | null
|
||||
@@ -68,7 +68,7 @@ export interface Database {
|
||||
}
|
||||
Update: {
|
||||
id?: string
|
||||
property_id?: string
|
||||
project_id?: string
|
||||
formatted_address?: string
|
||||
street_number?: string | null
|
||||
route?: string | null
|
||||
@@ -84,10 +84,10 @@ export interface Database {
|
||||
updated_at?: string
|
||||
}
|
||||
}
|
||||
properties_models: {
|
||||
projects_models: {
|
||||
Row: {
|
||||
id: string
|
||||
property_id: string
|
||||
project_id: string
|
||||
name: string
|
||||
version: number
|
||||
draft: boolean
|
||||
@@ -97,7 +97,7 @@ export interface Database {
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
property_id: string
|
||||
project_id: string
|
||||
name: string
|
||||
version?: number
|
||||
draft?: boolean
|
||||
@@ -107,7 +107,7 @@ export interface Database {
|
||||
}
|
||||
Update: {
|
||||
id?: string
|
||||
property_id?: string
|
||||
project_id?: string
|
||||
name?: string
|
||||
version?: number
|
||||
draft?: boolean
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
-- Migration: Rename properties → projects
|
||||
-- This renames all property-related tables, columns, indexes, policies, and functions.
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Drop existing RLS policies (they reference old table names)
|
||||
-- ============================================================
|
||||
|
||||
-- properties policies
|
||||
DROP POLICY IF EXISTS "Users can view own or public properties" ON properties;
|
||||
DROP POLICY IF EXISTS "Users can insert their own properties" ON properties;
|
||||
DROP POLICY IF EXISTS "Users can update their own properties" ON properties;
|
||||
DROP POLICY IF EXISTS "Users can delete their own properties" ON properties;
|
||||
|
||||
-- properties_addresses policies
|
||||
DROP POLICY IF EXISTS "Authenticated users can view all addresses" ON properties_addresses;
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert addresses" ON properties_addresses;
|
||||
DROP POLICY IF EXISTS "Authenticated users can update addresses" ON properties_addresses;
|
||||
DROP POLICY IF EXISTS "Authenticated users can delete addresses" ON properties_addresses;
|
||||
|
||||
-- properties_models policies
|
||||
DROP POLICY IF EXISTS "Users can view models of own or public properties" ON properties_models;
|
||||
DROP POLICY IF EXISTS "Users can insert models for their own properties" ON properties_models;
|
||||
DROP POLICY IF EXISTS "Users can update models of their own properties" ON properties_models;
|
||||
DROP POLICY IF EXISTS "Users can delete models of their own properties" ON properties_models;
|
||||
|
||||
-- property_likes policies
|
||||
DROP POLICY IF EXISTS "Anyone can view likes" ON property_likes;
|
||||
DROP POLICY IF EXISTS "Users can create their own likes" ON property_likes;
|
||||
DROP POLICY IF EXISTS "Users can delete their own likes" ON property_likes;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. Drop old indexes (before renaming tables)
|
||||
-- ============================================================
|
||||
|
||||
DROP INDEX IF EXISTS idx_properties_owner_id;
|
||||
DROP INDEX IF EXISTS idx_properties_address_id;
|
||||
DROP INDEX IF EXISTS idx_properties_is_private;
|
||||
DROP INDEX IF EXISTS idx_properties_views;
|
||||
DROP INDEX IF EXISTS idx_properties_likes;
|
||||
DROP INDEX IF EXISTS idx_properties_addresses_city_state;
|
||||
DROP INDEX IF EXISTS idx_properties_models_property_id;
|
||||
DROP INDEX IF EXISTS idx_properties_models_version;
|
||||
DROP INDEX IF EXISTS idx_property_likes_property_id;
|
||||
DROP INDEX IF EXISTS idx_property_likes_user_id;
|
||||
DROP INDEX IF EXISTS property_address_idx;
|
||||
DROP INDEX IF EXISTS property_owner_idx;
|
||||
DROP INDEX IF EXISTS property_is_private_idx;
|
||||
DROP INDEX IF EXISTS property_views_idx;
|
||||
DROP INDEX IF EXISTS property_likes_idx;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. Drop old triggers (before renaming tables)
|
||||
-- ============================================================
|
||||
|
||||
DROP TRIGGER IF EXISTS update_properties_updated_at ON properties;
|
||||
DROP TRIGGER IF EXISTS update_properties_addresses_updated_at ON properties_addresses;
|
||||
DROP TRIGGER IF EXISTS update_properties_models_updated_at ON properties_models;
|
||||
|
||||
-- ============================================================
|
||||
-- 4. Rename tables
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE properties_addresses RENAME TO projects_addresses;
|
||||
ALTER TABLE properties_models RENAME TO projects_models;
|
||||
ALTER TABLE property_likes RENAME TO projects_likes;
|
||||
ALTER TABLE properties RENAME TO projects;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. Rename columns (property_id → project_id)
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE projects_models RENAME COLUMN property_id TO project_id;
|
||||
ALTER TABLE projects_likes RENAME COLUMN property_id TO project_id;
|
||||
|
||||
-- Rename active_property_id in auth_sessions
|
||||
ALTER TABLE auth_sessions RENAME COLUMN active_property_id TO active_project_id;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. Rename constraints
|
||||
-- ============================================================
|
||||
|
||||
-- Rename the unique constraint on projects_likes
|
||||
ALTER TABLE projects_likes RENAME CONSTRAINT "property_likes_pkey" TO "projects_likes_pkey";
|
||||
ALTER TABLE projects_likes RENAME CONSTRAINT "property_likes_property_id_fkey" TO "projects_likes_project_id_fkey";
|
||||
ALTER TABLE projects_likes RENAME CONSTRAINT "property_likes_property_id_user_id_key" TO "projects_likes_project_id_user_id_key";
|
||||
|
||||
-- ============================================================
|
||||
-- 7. Recreate indexes with new names
|
||||
-- ============================================================
|
||||
|
||||
CREATE INDEX project_address_idx ON projects(address_id);
|
||||
CREATE INDEX project_owner_idx ON projects(owner_id);
|
||||
CREATE INDEX project_is_private_idx ON projects(is_private) WHERE is_private = false;
|
||||
CREATE INDEX project_views_idx ON projects(views DESC);
|
||||
CREATE INDEX project_likes_idx ON projects(likes DESC);
|
||||
CREATE INDEX idx_projects_addresses_city_state ON projects_addresses(city, state);
|
||||
CREATE INDEX idx_projects_models_project_id ON projects_models(project_id);
|
||||
CREATE INDEX idx_projects_models_version ON projects_models(project_id, version DESC);
|
||||
CREATE INDEX idx_projects_likes_project_id ON projects_likes(project_id);
|
||||
CREATE INDEX idx_projects_likes_user_id ON projects_likes(user_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 8. Recreate triggers on renamed tables
|
||||
-- ============================================================
|
||||
|
||||
CREATE TRIGGER update_projects_updated_at
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_addresses_updated_at
|
||||
BEFORE UPDATE ON projects_addresses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_models_updated_at
|
||||
BEFORE UPDATE ON projects_models
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================
|
||||
-- 9. Recreate RLS policies with new names and table references
|
||||
-- ============================================================
|
||||
|
||||
-- projects policies
|
||||
CREATE POLICY "Users can view own or public projects"
|
||||
ON projects FOR SELECT
|
||||
USING (
|
||||
owner_id = current_setting('app.user_id', true)::TEXT
|
||||
OR is_private = false
|
||||
OR owner_id IS NULL
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can insert their own projects"
|
||||
ON projects FOR INSERT
|
||||
WITH CHECK (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL);
|
||||
|
||||
CREATE POLICY "Users can update their own projects"
|
||||
ON projects FOR UPDATE
|
||||
USING (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL);
|
||||
|
||||
CREATE POLICY "Users can delete their own projects"
|
||||
ON projects FOR DELETE
|
||||
USING (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL);
|
||||
|
||||
-- projects_addresses policies
|
||||
CREATE POLICY "Authenticated users can view all addresses"
|
||||
ON projects_addresses FOR SELECT
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can insert addresses"
|
||||
ON projects_addresses FOR INSERT
|
||||
WITH CHECK (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can update addresses"
|
||||
ON projects_addresses FOR UPDATE
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Authenticated users can delete addresses"
|
||||
ON projects_addresses FOR DELETE
|
||||
USING (true);
|
||||
|
||||
-- projects_models policies
|
||||
CREATE POLICY "Users can view models of own or public projects"
|
||||
ON projects_models FOR SELECT
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM projects
|
||||
WHERE projects.id = projects_models.project_id
|
||||
AND (
|
||||
projects.owner_id = current_setting('app.user_id', true)::TEXT
|
||||
OR projects.is_private = false
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can insert models for their own projects"
|
||||
ON projects_models FOR INSERT
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM projects
|
||||
WHERE projects.id = projects_models.project_id
|
||||
AND projects.owner_id = current_setting('app.user_id', true)::TEXT
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can update models of their own projects"
|
||||
ON projects_models FOR UPDATE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM projects
|
||||
WHERE projects.id = projects_models.project_id
|
||||
AND projects.owner_id = current_setting('app.user_id', true)::TEXT
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can delete models of their own projects"
|
||||
ON projects_models FOR DELETE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM projects
|
||||
WHERE projects.id = projects_models.project_id
|
||||
AND projects.owner_id = current_setting('app.user_id', true)::TEXT
|
||||
)
|
||||
);
|
||||
|
||||
-- projects_likes policies
|
||||
CREATE POLICY "Anyone can view likes"
|
||||
ON projects_likes FOR SELECT
|
||||
USING (true);
|
||||
|
||||
CREATE POLICY "Users can create their own likes"
|
||||
ON projects_likes FOR INSERT
|
||||
WITH CHECK (user_id = current_setting('app.user_id', true)::TEXT);
|
||||
|
||||
CREATE POLICY "Users can delete their own likes"
|
||||
ON projects_likes FOR DELETE
|
||||
USING (user_id = current_setting('app.user_id', true)::TEXT);
|
||||
|
||||
-- ============================================================
|
||||
-- 10. Replace functions with renamed versions
|
||||
-- ============================================================
|
||||
|
||||
-- Drop old functions
|
||||
DROP FUNCTION IF EXISTS increment_property_views(TEXT);
|
||||
DROP FUNCTION IF EXISTS get_property_like_count(TEXT);
|
||||
|
||||
-- Recreate with new names
|
||||
CREATE OR REPLACE FUNCTION increment_project_views(project_id TEXT)
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
UPDATE projects
|
||||
SET views = views + 1
|
||||
WHERE id = project_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_project_like_count(project_id TEXT)
|
||||
RETURNS INTEGER AS $$
|
||||
BEGIN
|
||||
RETURN (SELECT COUNT(*)::INTEGER FROM projects_likes WHERE projects_likes.project_id = $1);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER STABLE;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user