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:
Anton Pascal
2026-02-17 20:40:52 +00:00
parent 77d64824e5
commit ee30f17129
35 changed files with 1376 additions and 1025 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ export interface CameraControlEvent {
}
export interface ThumbnailGenerateEvent {
propertyId: string
projectId: string
}
type CameraControlEvents = {
+2 -2
View File
@@ -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 -4
View File
@@ -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'
@@ -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'),
+36
View File
@@ -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)
@@ -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],
}),
}))
@@ -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)
+9 -9
View File
@@ -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;