Skip to content

Commit

Permalink
Migrate styles from Tree to Build (#904)
Browse files Browse the repository at this point in the history
Ref #807

We are gonna store styles on Build table to be able to share them via
tokens within project. Here I migrated styles data from Tree table to
Build and combined all trees styles on a project into single array.

Rest interface did not changed, the logic is hidden in endpoints for
now. Next step will be to move the logic on client side and make rest
api thin again.
  • Loading branch information
TrySound authored and kof committed Jan 31, 2023
1 parent 0adafd1 commit 3a11560
Show file tree
Hide file tree
Showing 6 changed files with 540 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import ObjectId from "bson-objectid";
import { PrismaClient } from "./client";

type InstanceId = string;
type StyleSourceId = string;
type BuildId = string;
type TreeId = string;

type TreeStyleDecl = {
breakpointId: string;
instanceId: InstanceId;
property: string;
value: unknown;
};

type TreeStyles = TreeStyleDecl[];

type StyleDecl = {
styleSourceId: StyleSourceId;
breakpointId: string;
property: string;
value: unknown;
};

type Styles = StyleDecl[];

type StyleSource = {
type: "local";
id: StyleSourceId;
treeId: TreeId;
};

type StyleSources = StyleSource[];

type StyleSourceSelection = {
instanceId: InstanceId;
values: StyleSourceId[];
};

type StyleSourceSelections = StyleSourceSelection[];

export default async () => {
const client = new PrismaClient({
// Uncomment to see the queries in console as the migration runs
// log: ["query", "info", "warn", "error"],
});

await client.$transaction(
async (prisma) => {
let stylesPerBuild = new Map<BuildId, Styles>();
let styleSourcesPerBuild = new Map<BuildId, StyleSources>();

const chunkSize = 1000;
let cursor: undefined | string = undefined;
let hasNext = true;
while (hasNext) {
const trees = await prisma.tree.findMany({
take: chunkSize,
orderBy: {
id: "asc",
},
...(cursor
? {
skip: 1, // Skip the cursor
cursor: { id: cursor },
}
: null),
});
cursor = trees.at(-1)?.id;
hasNext = trees.length === chunkSize;

for (const tree of trees) {
const treeId = tree.id;
try {
const treeStyles: TreeStyles = JSON.parse(tree.styles);
const styles: Styles = [];
const styleSources: StyleSources = [];
const styleSelections: StyleSourceSelections = [];
const styleSourceIdPerInstanceId = new Map<
InstanceId,
StyleSourceId
>();

for (const styleDecl of treeStyles) {
const { instanceId, breakpointId, property, value } = styleDecl;
let styleSourceId = styleSourceIdPerInstanceId.get(instanceId);
if (styleSourceId === undefined) {
styleSourceId = ObjectId().toString();
styleSourceIdPerInstanceId.set(instanceId, styleSourceId);
}

styles.push({
styleSourceId,
breakpointId,
property,
value,
});
styleSources.push({
id: styleSourceId,
treeId,
type: "local",
});
styleSelections.push({
instanceId,
values: [styleSourceId],
});
}

stylesPerBuild.set(tree.buildId, styles);
styleSourcesPerBuild.set(tree.buildId, styleSources);
tree.styleSelections = JSON.stringify(styleSelections);
} catch {
console.info(`Tree ${treeId} cannot be converted`);
}
}
await Promise.all(
trees.map(({ id, styleSelections }) =>
prisma.tree.update({ where: { id }, data: { styleSelections } })
)
);
}

cursor = undefined;
hasNext = true;
while (hasNext) {
const builds = await prisma.build.findMany({
take: chunkSize,
orderBy: {
id: "asc",
},
...(cursor
? {
skip: 1, // Skip the cursor
cursor: { id: cursor },
}
: null),
});
cursor = builds.at(-1)?.id;
hasNext = builds.length === chunkSize;

for (const build of builds) {
const buildId = build.id;
const styles = stylesPerBuild.get(buildId) ?? [];
const styleSources = styleSourcesPerBuild.get(buildId) ?? [];
build.styles = JSON.stringify(styles);
build.styleSources = JSON.stringify(styleSources);
}
await Promise.all(
builds.map(({ id, styles, styleSources }) =>
prisma.build.update({
where: { id },
data: { styles, styleSources },
})
)
);
}
},
{ timeout: 1000 * 60 * 8 }
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// DO NOT EDIT THIS FILE!
// This is a copy of your schema.prisma that corresponds to the state of the database
// when all migrations up until this one are applied.
// It's used to generate a Prisma Client for the migration.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
previewFeatures = ["views", "clientExtensions"]
output = "client"
}

datasource db {
provider = "postgres"
url = env("DATABASE_URL")
}

model Team {
id String @id @default(uuid())
users User[]
}

enum Location {
FS
REMOTE
}

enum UploadStatus {
UPLOADING
UPLOADED
}

model Asset {
id String @id @default(uuid())
projectId String
format String
size Int
name String
description String?
location Location
createdAt DateTime @default(now())
meta String @default("{}")
status UploadStatus @default(UPLOADED)
}

model User {
id String @id @default(uuid())
email String? @unique
provider String?
image String?
username String?
createdAt DateTime @default(now())
team Team? @relation(fields: [teamId], references: [id])
teamId String?
projects Project[]
}

model Project {
id String @id @default(uuid())
title String
domain String @unique
user User? @relation(fields: [userId], references: [id])
userId String?
build Build[]
trees Tree[]
isDeleted Boolean @default(false)
}

model Build {
id String @id @default(uuid())
createdAt DateTime @default(now())
pages String
project Project @relation(fields: [projectId], references: [id])
projectId String
trees Tree[]
isDev Boolean // exctly one is true per project
isProd Boolean // at most one is true per project (none if not published)
breakpoints Breakpoints?
styles String @default("[]")
styleSources String @default("[]")
}

model Tree {
id String @id @default(uuid())
project Project @relation(fields: [projectId], references: [id])
projectId String
build Build @relation(fields: [buildId], references: [id])
buildId String
root String
instances String @default("[]")
props String @default("[]")
styles String @default("[]")
styleSelections String @default("[]")
}

model Breakpoints {
build Build @relation(fields: [buildId], references: [id])
buildId String @id
values String @default("[]")
}

enum AuthorizationPermit {
VIEW
EDIT
}

model AuthorizationTokens {
id String @id @default(uuid())
// No relation to Project, as the Authorization system is not tied to a project
projectId String
token String
permit AuthorizationPermit
createdAt DateTime @default(now())
}

// Dashboard
view DashboardProject {
id String @id @default(uuid())
title String
domain String
userId String?
isDeleted Boolean @default(false)
isPublished Boolean
}
1 change: 0 additions & 1 deletion packages/project-build/src/schema/style-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const StyleSourceLocal = z.object({
type: z.literal("local"),
id: StyleSourceId,
treeId: z.string(),
name: z.string(),
});

export const StyleSource = z.union([StyleSourceToken, StyleSourceLocal]);
Expand Down
2 changes: 1 addition & 1 deletion packages/project-build/src/schema/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const StoredImageValue = z.object({
});

export const StoredStylesItem = z.object({
styleSourceId: z.string(),
breakpointId: z.string(),
instanceId: z.string(),
// @todo can't figure out how to make property to be enum
property: z.string() as z.ZodType<StyleProperty>,
value: z.union([StoredImageValue, SharedStyleValue]),
Expand Down
Loading

0 comments on commit 3a11560

Please sign in to comment.