diff --git a/apps/server/src/config/r2.ts b/apps/server/src/config/r2.ts
index c5c34f6..10d6e96 100644
--- a/apps/server/src/config/r2.ts
+++ b/apps/server/src/config/r2.ts
@@ -1,11 +1,16 @@
-import { S3Client } from "@aws-sdk/client-s3";
+import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
+import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { env } from "@fixr/env/server";
import {
buildObjectPublicUrl as _buildObjectPublicUrl,
isAllowedCompanyPhotoUrl as _isAllowedCompanyPhotoUrl,
} from "../core/lib/r2";
-export { buildUploadObjectKey, sanitizeUploadFileName } from "../core/lib/r2";
+export {
+ buildModelObjectKey,
+ buildUploadObjectKey,
+ sanitizeUploadFileName,
+} from "../core/lib/r2";
function parseR2BucketUrl(bucketUrl: string) {
const parsed = new URL(bucketUrl);
@@ -45,3 +50,17 @@ export function buildObjectPublicUrl(key: string) {
export function isAllowedCompanyPhotoUrl(url: string, companyId: string) {
return _isAllowedCompanyPhotoUrl(r2PublicBaseUrl, url, companyId);
}
+
+/**
+ * Generate a presigned GET URL for reading an object from R2
+ *
+ * @param key - The R2 object key (nullable)
+ * @returns A presigned URL valid for 24 hours, or null if key is empty
+ */
+export async function generatePresignedGetUrl(key: string): Promise {
+ const command = new GetObjectCommand({
+ Bucket: r2Bucket,
+ Key: key,
+ });
+ return await getSignedUrl(r2Client, command, { expiresIn: 86_400 });
+}
diff --git a/apps/server/src/core/docs/categories/categories.docs.ts b/apps/server/src/core/docs/categories/categories.docs.ts
new file mode 100644
index 0000000..8d02610
--- /dev/null
+++ b/apps/server/src/core/docs/categories/categories.docs.ts
@@ -0,0 +1,61 @@
+import { modelCategorySelectSchema } from "@fixr/db/schema";
+import {
+ getModelCategoriesQuerySchema,
+ getModelCategoryParamsSchema,
+} from "@fixr/schemas/models";
+import type { FastifySchema } from "fastify";
+import { z } from "zod";
+import { zodResponseSchema } from "../types";
+
+const listCategoriesSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "List model categories",
+ description: `
+**Retrieves all device categories.**
+Categories are global and not tied to a specific company.
+
+Optional filter (query string):
+- \`query\`: search by category name
+`,
+ querystring: getModelCategoriesQuerySchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Categories retrieved successfully.",
+ code: "list_categories_success",
+ data: z.array(modelCategorySelectSchema),
+ }).describe("Categories retrieved successfully."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const getCategoryBySlugSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Get category by slug",
+ description: "Retrieves a single device category by its slug.",
+ params: getModelCategoryParamsSchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Category retrieved successfully.",
+ code: "get_category_success",
+ data: modelCategorySelectSchema,
+ }).describe("Category retrieved successfully."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "category_not_found",
+ message: "Category not found.",
+ data: null,
+ }).describe("Category not found."),
+ },
+ security: [{ JWT: [] }],
+};
+
+/** @description OpenAPI schemas for the categories module */
+export const categoriesDocs = {
+ listCategoriesSchema,
+ getCategoryBySlugSchema,
+};
diff --git a/apps/server/src/core/docs/makers/makers.docs.ts b/apps/server/src/core/docs/makers/makers.docs.ts
new file mode 100644
index 0000000..225aa62
--- /dev/null
+++ b/apps/server/src/core/docs/makers/makers.docs.ts
@@ -0,0 +1,79 @@
+import { modelMakerSelectSchema } from "@fixr/db/schema";
+import {
+ getModelMakerParamsSchema,
+ getModelMakersQuerySchema,
+} from "@fixr/schemas/models";
+import { paginatedDataSchema } from "@fixr/schemas/utils";
+import type { FastifySchema } from "fastify";
+import { z } from "zod";
+import { zodResponseSchema } from "../types";
+
+const makerListRecordSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ url: z.string(),
+ deviceCount: z.number(),
+ pageCount: z.number().nullable(),
+ createdAt: z.coerce.date(),
+});
+
+const listMakersSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "List model makers",
+ description: `
+**Retrieves device makers (brands) with pagination.**
+
+Optional filters (query string):
+- \`query\`: search by maker name
+- \`page\`, \`perPage\`, \`sort\` (\`newer\` | \`older\` | \`name\` | \`most_devices\`): pagination (see API pagination docs)
+`,
+ querystring: getModelMakersQuerySchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Makers successfully retrieved.",
+ code: "list_makers_success",
+ data: paginatedDataSchema(makerListRecordSchema),
+ }).describe("Makers retrieved successfully."),
+ 416: zodResponseSchema({
+ status: 416,
+ error: "Range Not Satisfiable",
+ code: "page_out_of_bounds",
+ message: "The requested page exceeds the total number of pages.",
+ data: null,
+ }).describe("Requested page exceeds total pages."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const getMakerBySlugSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Get maker by slug",
+ description: "Retrieves a single device maker by its slug.",
+ params: getModelMakerParamsSchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Maker retrieved successfully.",
+ code: "get_maker_success",
+ data: modelMakerSelectSchema,
+ }).describe("Maker retrieved successfully."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "maker_not_found",
+ message: "Maker not found.",
+ data: null,
+ }).describe("Maker not found."),
+ },
+ security: [{ JWT: [] }],
+};
+
+/** @description OpenAPI schemas for the makers module */
+export const makersDocs = {
+ listMakersSchema,
+ getMakerBySlugSchema,
+};
diff --git a/apps/server/src/core/docs/models/models.docs.ts b/apps/server/src/core/docs/models/models.docs.ts
new file mode 100644
index 0000000..cfd4413
--- /dev/null
+++ b/apps/server/src/core/docs/models/models.docs.ts
@@ -0,0 +1,391 @@
+import { getCompanyNestedDataSchema } from "@fixr/schemas/companies";
+import {
+ createModelBodySchema,
+ createModelImageBodySchema,
+ getModelsQuerySchema,
+ patchModelBodySchema,
+} from "@fixr/schemas/models";
+import { paginatedDataSchema } from "@fixr/schemas/utils";
+import type { FastifySchema } from "fastify";
+import { z } from "zod";
+import { zodResponseSchema } from "../types";
+
+const modelListRecordSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ imageUrl: z.string().nullable(),
+ status: z.string().nullable(),
+ price: z.string().nullable(),
+ released: z.string().nullable(),
+ maker: z.object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ }),
+ category: z
+ .object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ })
+ .nullable(),
+});
+
+const modelImageRecordSchema = z.object({
+ id: z.string(),
+ modelId: z.string(),
+ r2Key: z.string().nullable(),
+ presignedUrl: z.string().nullable(),
+ isPrimary: z.boolean(),
+ variant: z.string().nullable(),
+ position: z.number(),
+ createdAt: z.coerce.date(),
+});
+
+const modelDetailRecordSchema = z.object({
+ id: z.string(),
+ makerId: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ url: z.string(),
+ imageUrl: z.string().nullable(),
+ categoryId: z.string().nullable(),
+ announced: z.string().nullable(),
+ status: z.string().nullable(),
+ dimensions: z.string().nullable(),
+ weight: z.string().nullable(),
+ build: z.string().nullable(),
+ sim: z.string().nullable(),
+ displayType: z.string().nullable(),
+ displaySize: z.string().nullable(),
+ displayResolution: z.string().nullable(),
+ displayProtection: z.string().nullable(),
+ os: z.string().nullable(),
+ chipset: z.string().nullable(),
+ cpu: z.string().nullable(),
+ gpu: z.string().nullable(),
+ cardSlot: z.string().nullable(),
+ internalMemory: z.string().nullable(),
+ mainCamera: z.string().nullable(),
+ mainCameraFeatures: z.string().nullable(),
+ mainCameraVideo: z.string().nullable(),
+ selfieCamera: z.string().nullable(),
+ selfieFeatures: z.string().nullable(),
+ selfieVideo: z.string().nullable(),
+ battery: z.string().nullable(),
+ batteryCharging: z.string().nullable(),
+ networkTech: z.string().nullable(),
+ sensors: z.string().nullable(),
+ colors: z.string().nullable(),
+ colorsHex: z.string().nullable(),
+ modelsText: z.string().nullable(),
+ price: z.string().nullable(),
+ dimensionsWidth: z.number().nullable(),
+ dimensionsHeight: z.number().nullable(),
+ dimensionsThickness: z.number().nullable(),
+ weightGrams: z.number().nullable(),
+ displaySizeInches: z.number().nullable(),
+ displaySizeRatio: z.string().nullable(),
+ displayResWidth: z.number().nullable(),
+ displayResHeight: z.number().nullable(),
+ displayResPpi: z.number().nullable(),
+ released: z.string().nullable(),
+ meta: z.string().nullable(),
+ companyId: z.string().nullable(),
+ createdAt: z.coerce.date(),
+ maker: z.object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ url: z.string(),
+ }),
+ category: z
+ .object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ })
+ .nullable(),
+ images: z.array(modelImageRecordSchema),
+});
+
+const modelDetailParamsSchema = z
+ .object({
+ subdomain: z.string(),
+ slug: z.string(),
+ })
+ .describe("Company subdomain and model slug.");
+
+const listModelsSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "List device models",
+ description: `
+**Retrieves device models (paginated minimal list) for mounting a table.**
+
+Returns base (global) models plus company-specific models.
+
+Optional filters (query string):
+- \`query\`: fulltext search on name, model variants, chipset, CPU, internal memory, OS
+- \`makerId\`: filter by brand (cuid2)
+- \`categoryId\`: filter by category (cuid2)
+- \`status\`: release status: Available, Discontinued, Cancelled, Rumored
+- \`page\`, \`perPage\`, \`sort\` (\`newer\` | \`older\` | \`name\`): pagination (see API pagination docs)
+`,
+ params: getCompanyNestedDataSchema,
+ querystring: getModelsQuerySchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Models successfully retrieved.",
+ code: "list_models_success",
+ data: paginatedDataSchema(modelListRecordSchema),
+ }).describe("Models retrieved successfully."),
+ 416: zodResponseSchema({
+ status: 416,
+ error: "Range Not Satisfiable",
+ code: "page_out_of_bounds",
+ message: "The requested page exceeds the total number of pages.",
+ data: null,
+ }).describe("Requested page exceeds total pages."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed to access this company."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const getModelBySlugSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Get device model by slug",
+ description: `
+**Retrieves full device model details by slug.**
+
+Returns all spec fields, related maker and category, and model images with presigned URLs.
+`,
+ params: modelDetailParamsSchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Model retrieved successfully.",
+ code: "get_model_success",
+ data: modelDetailRecordSchema,
+ }).describe("Model retrieved successfully."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "model_not_found",
+ message: "Model not found.",
+ data: null,
+ }).describe("Model not found."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed to access this company."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const createModelSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Create device model",
+ description: `
+**Creates a new device model record.**
+
+Only company-specific models can be created.
+- \`name\` and \`makerId\` are required.
+- \`slug\` is auto-generated from \`name\` if not provided.
+- All spec fields are optional and can be filled later via PATCH.
+`,
+ params: getCompanyNestedDataSchema,
+ body: createModelBodySchema,
+ response: {
+ 201: zodResponseSchema({
+ status: 201,
+ error: null,
+ message: "Model created successfully.",
+ code: "create_model_success",
+ data: z.object({ id: z.string(), name: z.string(), slug: z.string() }),
+ }).describe("Model created successfully."),
+ 409: zodResponseSchema({
+ status: 409,
+ error: "Conflict",
+ code: "model_slug_conflict",
+ message: "A model with this slug already exists.",
+ data: null,
+ }).describe("Slug already exists."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed to access this company."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const patchModelSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Update device model (partial)",
+ description: `
+**Partially updates a device model record.**
+
+All fields are optional: only provided fields will be updated.
+Returns the full model detail with presigned image URLs.
+`,
+ params: z.object({ subdomain: z.string(), modelId: z.string() }),
+ body: patchModelBodySchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Model updated successfully.",
+ code: "patch_model_success",
+ data: modelDetailRecordSchema,
+ }).describe("Model updated successfully."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "model_not_found",
+ message: "Model not found.",
+ data: null,
+ }).describe("Model not found."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed to access this company."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const deleteModelSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Delete device model",
+ description: `
+**Deletes a device model and its associated images.**
+
+Removes all related \`model_images\` records and deletes the uploaded files from storage.
+`,
+ params: z.object({ subdomain: z.string(), modelId: z.string() }),
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Model deleted successfully.",
+ code: "delete_model_success",
+ data: null,
+ }).describe("Model deleted successfully."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "model_not_found",
+ message: "Model not found.",
+ data: null,
+ }).describe("Model not found."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed to access this company."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const createModelImageSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Assign an uploaded image to a model",
+ description: `
+**Creates a model image record, linking an uploaded file to a device model.**
+
+Provide the \`r2Key\` returned from the presign upload endpoint. Returns the created model image with a presigned URL.
+`,
+ params: z.object({ subdomain: z.string(), modelId: z.string() }),
+ body: createModelImageBodySchema,
+ response: {
+ 201: zodResponseSchema({
+ status: 201,
+ error: null,
+ message: "Model image created successfully.",
+ code: "create_model_image_success",
+ data: modelImageRecordSchema,
+ }).describe("Model image created."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "model_not_found",
+ message: "Model not found.",
+ data: null,
+ }).describe("Model not found."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed."),
+ },
+ security: [{ JWT: [] }],
+};
+
+const deleteModelImageSchema: FastifySchema = {
+ tags: ["Devices"],
+ summary: "Delete a model image",
+ description: `
+**Deletes a model image record and removes the uploaded file from storage.**
+`,
+ params: z.object({
+ subdomain: z.string(),
+ modelId: z.string(),
+ imageId: z.string(),
+ }),
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Model image deleted successfully.",
+ code: "delete_model_image_success",
+ data: null,
+ }).describe("Model image deleted."),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "model_image_not_found",
+ message: "Model image not found.",
+ data: null,
+ }).describe("Image not found."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ data: null,
+ }).describe("Not allowed."),
+ },
+ security: [{ JWT: [] }],
+};
+
+/** @description OpenAPI schemas for the models module */
+export const modelsDocs = {
+ listModelsSchema,
+ getModelBySlugSchema,
+ createModelSchema,
+ patchModelSchema,
+ deleteModelSchema,
+ createModelImageSchema,
+ deleteModelImageSchema,
+};
diff --git a/apps/server/src/core/docs/service-orders.docs.ts b/apps/server/src/core/docs/service-orders.docs.ts
index 0ed65a1..1c8f511 100644
--- a/apps/server/src/core/docs/service-orders.docs.ts
+++ b/apps/server/src/core/docs/service-orders.docs.ts
@@ -44,12 +44,12 @@ const getCompanyServiceOrdersSchema: FastifySchema = {
**Retrieves company service orders (paginated)**
Optional filters (query string):
-- \`deviceCategoryId\` — device category (cuid2)
-- \`employeeId\` — responsible employee (cuid2)
-- \`status\` — one of: ${serviceOrderStatuses.options.join(", ")}
-- \`dateFrom\` / \`dateTo\` — filter by \`created_at\` (inclusive; ISO date or datetime)
-- \`query\` — search in device model, reported defect, or client name
-- \`page\`, \`perPage\`, \`sort\` (\`newer\` | \`older\`) — pagination (see API pagination docs)
+- \`deviceCategoryId\`: device category (cuid2)
+- \`employeeId\`: responsible employee (cuid2)
+- \`status\`: one of: ${serviceOrderStatuses.options.join(", ")}
+- \`dateFrom\` / \`dateTo\`: filter by \`created_at\` (inclusive; ISO date or datetime)
+- \`query\`: search in device model, reported defect, or client name
+- \`page\`, \`perPage\`, \`sort\` (\`newer\` | \`older\`): pagination (see API pagination docs)
`,
params: getCompanyNestedDataSchema,
querystring: getServiceOrdersQuerySchema,
diff --git a/apps/server/src/core/docs/uploads.docs.ts b/apps/server/src/core/docs/uploads.docs.ts
index 63b1d9e..1f435ba 100644
--- a/apps/server/src/core/docs/uploads.docs.ts
+++ b/apps/server/src/core/docs/uploads.docs.ts
@@ -1,4 +1,5 @@
import {
+ createModelImageUploadPresignSchema,
createUploadPresignSchema,
uploadPresignResponseSchema,
} from "@fixr/schemas/uploads";
@@ -42,6 +43,42 @@ The request accepts file metadata (\`fileName\`, \`contentType\`, \`size\`) and
security: [{ JWT: [] }],
};
+const createModelImagePresignSchemaDoc: FastifySchema = {
+ tags: ["Uploads"],
+ summary: "Generate pre-signed upload URL for a model image",
+ description: `
+**Generates a pre-signed URL for uploading a model image to Cloudflare R2**
+
+Returns a time-limited presigned PUT URL and an upload ID. Upload the file directly to R2 using the returned URL, then use \`POST /{modelId}/images\` in the devices module to assign it to a model.
+`,
+ body: createModelImageUploadPresignSchema,
+ response: {
+ 200: zodResponseSchema({
+ status: 200,
+ error: null,
+ message: "Upload URL generated successfully.",
+ code: "create_model_image_presign_success",
+ data: uploadPresignResponseSchema,
+ }).describe("Presigned URL generated."),
+ 403: zodResponseSchema({
+ status: 403,
+ error: "Forbidden",
+ code: "not_allowed",
+ message: "You are not authorized to perform this action.",
+ data: null,
+ }),
+ 404: zodResponseSchema({
+ status: 404,
+ error: "Not Found",
+ code: "company_not_found",
+ message: "There's no company associated with this account.",
+ data: null,
+ }),
+ },
+ security: [{ JWT: [] }],
+};
+
export const uploadsDocs = {
createUploadPresignSchema: createUploadPresignSchemaDoc,
+ createModelImagePresignSchema: createModelImagePresignSchemaDoc,
};
diff --git a/apps/server/src/core/errors/index.ts b/apps/server/src/core/errors/index.ts
index 6156b0a..c0d64fc 100644
--- a/apps/server/src/core/errors/index.ts
+++ b/apps/server/src/core/errors/index.ts
@@ -1,8 +1,11 @@
import { accountErrors } from "../../modules/account/errors";
import { authErrors } from "../../modules/auth/errors";
+import { categoriesErrors } from "../../modules/categories/errors";
import { companiesErrors } from "../../modules/companies/errors";
import { credentialsErrors } from "../../modules/credentials/errors";
import { employeesErrors } from "../../modules/employees/errors";
+import { makersErrors } from "../../modules/makers/errors";
+import { modelsErrors } from "../../modules/models/errors";
import { serviceOrdersErrors } from "../../modules/service-orders/errors";
import { tokensErrors } from "../../modules/tokens/errors";
import { uploadsErrors } from "../../modules/uploads/errors";
@@ -14,6 +17,9 @@ export const errors = defineErrors({
...credentialsErrors,
...companiesErrors,
...employeesErrors,
+ ...categoriesErrors,
+ ...makersErrors,
+ ...modelsErrors,
...serviceOrdersErrors,
...tokensErrors,
...uploadsErrors,
diff --git a/apps/server/src/core/lib/r2.ts b/apps/server/src/core/lib/r2.ts
index 6e0af8d..bd518f4 100644
--- a/apps/server/src/core/lib/r2.ts
+++ b/apps/server/src/core/lib/r2.ts
@@ -56,3 +56,19 @@ export function buildUploadObjectKey({
return `companies/${companyId}/service-orders/${uniquePrefix}-${safeName}`;
}
+
+/**
+ * Build an R2 object key for a model image
+ */
+export function buildModelObjectKey({
+ companyId,
+ fileName,
+}: {
+ companyId: string;
+ fileName: string;
+}): string {
+ const safeName = sanitizeUploadFileName(fileName);
+ const uniquePrefix = `${Date.now()}-${randomUUID().slice(0, 8)}`;
+
+ return `companies/${companyId}/models/${uniquePrefix}-${safeName}`;
+}
diff --git a/apps/server/src/core/lib/response.ts b/apps/server/src/core/lib/response.ts
index d7953dc..67bf1a5 100644
--- a/apps/server/src/core/lib/response.ts
+++ b/apps/server/src/core/lib/response.ts
@@ -28,6 +28,7 @@ export const httpStatusCodes: Record = {
405: "Method Not Allowed",
409: "Conflict",
410: "Gone",
+ 416: "Range Not Satisfiable",
418: "I'm a teapot",
429: "Too Many Requests",
500: "Internal Server Error",
diff --git a/apps/server/src/modules/categories/controllers/index.ts b/apps/server/src/modules/categories/controllers/index.ts
new file mode 100644
index 0000000..da13dc5
--- /dev/null
+++ b/apps/server/src/modules/categories/controllers/index.ts
@@ -0,0 +1,32 @@
+import type {
+ getModelCategoriesQuerySchema,
+ getModelCategoryParamsSchema,
+} from "@fixr/schemas/models";
+import type { FastifyReply } from "fastify";
+import type { z } from "zod";
+import { CategoriesService } from "../services";
+
+/** @description Categories request handlers */
+export class CategoriesController {
+ /** @description List all categories */
+ static listCategories({
+ query,
+ response,
+ }: {
+ query?: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return CategoriesService.listCategories({ query, response });
+ }
+
+ /** @description Get a category by slug */
+ static getCategoryBySlug({
+ slug,
+ response,
+ }: {
+ slug: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return CategoriesService.getCategoryBySlug({ slug, response });
+ }
+}
diff --git a/apps/server/src/modules/categories/errors/index.ts b/apps/server/src/modules/categories/errors/index.ts
new file mode 100644
index 0000000..15ccd62
--- /dev/null
+++ b/apps/server/src/modules/categories/errors/index.ts
@@ -0,0 +1,10 @@
+import { defineErrors } from "../../../core/utils/errors";
+
+/** @description Error definitions for the categories module */
+export const categoriesErrors = defineErrors({
+ CATEGORY_NOT_FOUND: {
+ code: "category_not_found",
+ message: "Category not found.",
+ status: 404,
+ },
+});
diff --git a/apps/server/src/modules/categories/repositories/index.ts b/apps/server/src/modules/categories/repositories/index.ts
new file mode 100644
index 0000000..702446d
--- /dev/null
+++ b/apps/server/src/modules/categories/repositories/index.ts
@@ -0,0 +1,33 @@
+import { asc, db, eq, like } from "@fixr/db/connection";
+import { modelCategories } from "@fixr/db/schema";
+
+/** @description Data access layer for device categories */
+export class CategoriesRepository {
+ /**
+ * Query all categories, optionally filtering by name
+ *
+ * @param query - Optional name filter
+ */
+ static async queryAllCategories(query?: string) {
+ const base = db.select().from(modelCategories).$dynamic();
+ if (query) {
+ base.where(like(modelCategories.name, `%${query}%`));
+ }
+ return await base.orderBy(asc(modelCategories.name));
+ }
+
+ /**
+ * Find a category by its slug
+ *
+ * @param slug - The category slug
+ * @returns The category record or null
+ */
+ static async queryCategoryBySlug(slug: string) {
+ const [category] = await db
+ .select()
+ .from(modelCategories)
+ .where(eq(modelCategories.slug, slug))
+ .limit(1);
+ return category ?? null;
+ }
+}
diff --git a/apps/server/src/modules/categories/routes/index.ts b/apps/server/src/modules/categories/routes/index.ts
new file mode 100644
index 0000000..be1c8a8
--- /dev/null
+++ b/apps/server/src/modules/categories/routes/index.ts
@@ -0,0 +1,46 @@
+import { permissions } from "@fixr/permissions";
+import {
+ getModelCategoriesQuerySchema,
+ getModelCategoryParamsSchema,
+} from "@fixr/schemas/models";
+import { categoriesDocs } from "../../../core/docs/categories/categories.docs";
+import type { FastifyTypedInstance } from "../../../core/interfaces/fastify";
+import { authenticateEmployee } from "../../../core/middlewares/authenticate-employee";
+import { requirePermission } from "../../../core/middlewares/rbac";
+import { withErrorHandler } from "../../../core/middlewares/with-error-handler";
+import { CategoriesController } from "../controllers";
+
+/** @description Categories routes plugin */
+export function categoriesRoutes(fastify: FastifyTypedInstance) {
+ fastify.get(
+ "/",
+ {
+ preHandler: [
+ authenticateEmployee,
+ requirePermission(permissions.devices.read),
+ ],
+ schema: categoriesDocs.listCategoriesSchema,
+ },
+ withErrorHandler(async (request, response) => {
+ const { query } = getModelCategoriesQuerySchema.parse(request.query);
+
+ await CategoriesController.listCategories({ query, response });
+ })
+ );
+
+ fastify.get(
+ "/:slug",
+ {
+ preHandler: [
+ authenticateEmployee,
+ requirePermission(permissions.devices.read),
+ ],
+ schema: categoriesDocs.getCategoryBySlugSchema,
+ },
+ withErrorHandler(async (request, response) => {
+ const { slug } = getModelCategoryParamsSchema.parse(request.params);
+
+ await CategoriesController.getCategoryBySlug({ slug, response });
+ })
+ );
+}
diff --git a/apps/server/src/modules/categories/services/index.ts b/apps/server/src/modules/categories/services/index.ts
new file mode 100644
index 0000000..a724fda
--- /dev/null
+++ b/apps/server/src/modules/categories/services/index.ts
@@ -0,0 +1,64 @@
+import { modelCategorySelectSchema } from "@fixr/db/schema";
+import type { FastifyReply } from "fastify";
+import { AppError } from "../../../core/lib/app-error";
+import { apiResponse } from "../../../core/lib/response";
+import { CategoriesRepository } from "../repositories";
+
+/** @description Business logic for device categories */
+export class CategoriesService {
+ /**
+ * List all categories, optionally filtered by query
+ *
+ * @param query - Optional name filter
+ * @param response - Fastify reply
+ */
+ static async listCategories({
+ query,
+ response,
+ }: {
+ query?: string;
+ response: FastifyReply;
+ }) {
+ const categories = await CategoriesRepository.queryAllCategories(query);
+
+ return response.status(200).send(
+ apiResponse({
+ status: 200,
+ error: null,
+ code: "list_categories_success",
+ message: "Categories retrieved successfully.",
+ data: categories.map((c) => modelCategorySelectSchema.parse(c)),
+ })
+ );
+ }
+
+ /**
+ * Get a category by its slug
+ *
+ * @param slug - The category slug
+ * @param response - Fastify reply
+ */
+ static async getCategoryBySlug({
+ slug,
+ response,
+ }: {
+ slug: string;
+ response: FastifyReply;
+ }) {
+ const category = await CategoriesRepository.queryCategoryBySlug(slug);
+
+ if (!category) {
+ throw new AppError("CATEGORY_NOT_FOUND");
+ }
+
+ return response.status(200).send(
+ apiResponse({
+ status: 200,
+ error: null,
+ code: "get_category_success",
+ message: "Category retrieved successfully.",
+ data: modelCategorySelectSchema.parse(category),
+ })
+ );
+ }
+}
diff --git a/apps/server/src/modules/makers/controllers/index.ts b/apps/server/src/modules/makers/controllers/index.ts
new file mode 100644
index 0000000..7843df9
--- /dev/null
+++ b/apps/server/src/modules/makers/controllers/index.ts
@@ -0,0 +1,41 @@
+import type { getModelMakersQuerySchema } from "@fixr/schemas/models";
+import type { FastifyReply } from "fastify";
+import type { z } from "zod";
+import { MakersService } from "../services";
+
+/** @description Makers request handlers */
+export class MakersController {
+ /** @description List makers with pagination */
+ static listMakers({
+ page,
+ perPage,
+ query,
+ sort,
+ response,
+ }: {
+ page: number;
+ perPage?: number;
+ query?: string;
+ sort?: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return MakersService.listMakers({
+ page,
+ perPage,
+ query,
+ sort,
+ response,
+ });
+ }
+
+ /** @description Get a maker by slug */
+ static getMakerBySlug({
+ slug,
+ response,
+ }: {
+ slug: string;
+ response: FastifyReply;
+ }) {
+ return MakersService.getMakerBySlug({ slug, response });
+ }
+}
diff --git a/apps/server/src/modules/makers/errors/index.ts b/apps/server/src/modules/makers/errors/index.ts
new file mode 100644
index 0000000..345015a
--- /dev/null
+++ b/apps/server/src/modules/makers/errors/index.ts
@@ -0,0 +1,15 @@
+import { defineErrors } from "../../../core/utils/errors";
+
+/** @description Error definitions for the makers module */
+export const makersErrors = defineErrors({
+ MAKER_NOT_FOUND: {
+ code: "maker_not_found",
+ message: "Maker not found.",
+ status: 404,
+ },
+ MAKER_PAGE_OUT_OF_BOUNDS: {
+ code: "page_out_of_bounds",
+ message: "The requested page exceeds the total number of pages.",
+ status: 416,
+ },
+});
diff --git a/apps/server/src/modules/makers/repositories/index.ts b/apps/server/src/modules/makers/repositories/index.ts
new file mode 100644
index 0000000..5b7d57d
--- /dev/null
+++ b/apps/server/src/modules/makers/repositories/index.ts
@@ -0,0 +1,62 @@
+import { and, asc, db, desc, eq, like, type SQL } from "@fixr/db/connection";
+import { modelMakers } from "@fixr/db/schema";
+
+/** @description Column selection for paginated makers list */
+export const makersListSelect = {
+ id: modelMakers.id,
+ name: modelMakers.name,
+ slug: modelMakers.slug,
+ url: modelMakers.url,
+ deviceCount: modelMakers.deviceCount,
+ pageCount: modelMakers.pageCount,
+ createdAt: modelMakers.createdAt,
+};
+
+/** @description Data access layer for device makers */
+export class MakersRepository {
+ /**
+ * Build a WHERE clause for filtering makers
+ *
+ * @param query - Optional name filter
+ */
+ static buildListFilter(query?: string) {
+ const conditions: SQL[] = [];
+ if (query) {
+ conditions.push(like(modelMakers.name, `%${query}%`));
+ }
+ return conditions.length > 0 ? and(...conditions) : undefined;
+ }
+
+ /**
+ * Build an ORDER BY clause for makers
+ *
+ * @param sort - Sort key: newer, older, name, most_devices
+ */
+ static buildOrder(sort?: string) {
+ switch (sort) {
+ case "newer":
+ return desc(modelMakers.createdAt);
+ case "older":
+ return asc(modelMakers.createdAt);
+ case "most_devices":
+ return desc(modelMakers.deviceCount);
+ default:
+ return asc(modelMakers.name);
+ }
+ }
+
+ /**
+ * Find a maker by its slug
+ *
+ * @param slug - The maker slug
+ * @returns The maker record or null
+ */
+ static async queryMakerBySlug(slug: string) {
+ const [maker] = await db
+ .select()
+ .from(modelMakers)
+ .where(eq(modelMakers.slug, slug))
+ .limit(1);
+ return maker ?? null;
+ }
+}
diff --git a/apps/server/src/modules/makers/routes/index.ts b/apps/server/src/modules/makers/routes/index.ts
new file mode 100644
index 0000000..28970a3
--- /dev/null
+++ b/apps/server/src/modules/makers/routes/index.ts
@@ -0,0 +1,54 @@
+import { permissions } from "@fixr/permissions";
+import {
+ getModelMakerParamsSchema,
+ getModelMakersQuerySchema,
+} from "@fixr/schemas/models";
+import { makersDocs } from "../../../core/docs/makers/makers.docs";
+import type { FastifyTypedInstance } from "../../../core/interfaces/fastify";
+import { authenticateEmployee } from "../../../core/middlewares/authenticate-employee";
+import { requirePermission } from "../../../core/middlewares/rbac";
+import { withErrorHandler } from "../../../core/middlewares/with-error-handler";
+import { MakersController } from "../controllers";
+
+/** @description Makers routes plugin */
+export function makersRoutes(fastify: FastifyTypedInstance) {
+ fastify.get(
+ "/",
+ {
+ preHandler: [
+ authenticateEmployee,
+ requirePermission(permissions.devices.read),
+ ],
+ schema: makersDocs.listMakersSchema,
+ },
+ withErrorHandler(async (request, response) => {
+ const { query, sort, page, perPage } = getModelMakersQuerySchema.parse(
+ request.query
+ );
+
+ await MakersController.listMakers({
+ page,
+ perPage,
+ query,
+ sort,
+ response,
+ });
+ })
+ );
+
+ fastify.get(
+ "/:slug",
+ {
+ preHandler: [
+ authenticateEmployee,
+ requirePermission(permissions.devices.read),
+ ],
+ schema: makersDocs.getMakerBySlugSchema,
+ },
+ withErrorHandler(async (request, response) => {
+ const { slug } = getModelMakerParamsSchema.parse(request.params);
+
+ await MakersController.getMakerBySlug({ slug, response });
+ })
+ );
+}
diff --git a/apps/server/src/modules/makers/services/index.ts b/apps/server/src/modules/makers/services/index.ts
new file mode 100644
index 0000000..881f54a
--- /dev/null
+++ b/apps/server/src/modules/makers/services/index.ts
@@ -0,0 +1,134 @@
+import { modelMakerSelectSchema } from "@fixr/db/schema";
+import type { FastifyReply } from "fastify";
+import { AppError } from "../../../core/lib/app-error";
+import {
+ getPaginatedCount,
+ getPaginatedRecords,
+} from "../../../core/lib/pagination";
+import { apiResponse, paginatedData } from "../../../core/lib/response";
+import { MakersRepository, makersListSelect } from "../repositories";
+
+/** @description Business logic for device makers */
+export class MakersService {
+ /**
+ * List makers with pagination, filtering, and sorting
+ *
+ * @param page - Current page number
+ * @param perPage - Items per page
+ * @param query - Optional name filter
+ * @param sort - Sort direction
+ * @param response - Fastify reply
+ */
+ static async listMakers({
+ page,
+ perPage,
+ query,
+ sort,
+ response,
+ }: {
+ page: number;
+ perPage?: number;
+ query?: string;
+ sort?: string;
+ response: FastifyReply;
+ }) {
+ const PER_PAGE = perPage ?? 10;
+
+ const filter = MakersRepository.buildListFilter(query);
+ const order = MakersRepository.buildOrder(sort);
+
+ const [records, totalRecords] = await Promise.all([
+ getPaginatedRecords({
+ table: makersListSelect.id.table,
+ select: makersListSelect,
+ skip: (page - 1) * PER_PAGE,
+ take: PER_PAGE,
+ where: filter,
+ order,
+ }),
+ getPaginatedCount({
+ table: makersListSelect.id.table,
+ where: filter,
+ }),
+ ]);
+
+ if (totalRecords === 0) {
+ return response.status(200).send(
+ apiResponse({
+ status: 200,
+ error: null,
+ message: "Makers successfully retrieved.",
+ code: "list_makers_success",
+ data: paginatedData({
+ records: [],
+ pagination: {
+ total_records: 0,
+ total_pages: 0,
+ current_page: 1,
+ next_page: null,
+ prev_page: null,
+ },
+ }),
+ })
+ );
+ }
+
+ const total_pages = Math.ceil(totalRecords / PER_PAGE);
+
+ if (page > total_pages) {
+ throw new AppError("MAKER_PAGE_OUT_OF_BOUNDS");
+ }
+
+ const next_page =
+ PER_PAGE * (page - 1) + records.length < totalRecords ? page + 1 : null;
+
+ return response.status(200).send(
+ apiResponse({
+ status: 200,
+ error: null,
+ message: "Makers successfully retrieved.",
+ code: "list_makers_success",
+ data: paginatedData({
+ records,
+ pagination: {
+ total_records: totalRecords,
+ total_pages,
+ current_page: page,
+ next_page,
+ prev_page: page > 1 ? page - 1 : null,
+ },
+ }),
+ })
+ );
+ }
+
+ /**
+ * Get a maker by its slug
+ *
+ * @param slug - The maker slug
+ * @param response - Fastify reply
+ */
+ static async getMakerBySlug({
+ slug,
+ response,
+ }: {
+ slug: string;
+ response: FastifyReply;
+ }) {
+ const maker = await MakersRepository.queryMakerBySlug(slug);
+
+ if (!maker) {
+ throw new AppError("MAKER_NOT_FOUND");
+ }
+
+ return response.status(200).send(
+ apiResponse({
+ status: 200,
+ error: null,
+ code: "get_maker_success",
+ message: "Maker retrieved successfully.",
+ data: modelMakerSelectSchema.parse(maker),
+ })
+ );
+ }
+}
diff --git a/apps/server/src/modules/models/controllers/index.ts b/apps/server/src/modules/models/controllers/index.ts
new file mode 100644
index 0000000..16134ba
--- /dev/null
+++ b/apps/server/src/modules/models/controllers/index.ts
@@ -0,0 +1,165 @@
+import type { jwtPayload } from "@fixr/schemas/auth";
+import type {
+ createModelBodySchema,
+ createModelImageBodySchema,
+ getModelBySlugParamsSchema,
+ getModelsQuerySchema,
+ modelIdParamsSchema,
+ patchModelBodySchema,
+} from "@fixr/schemas/models";
+import type { FastifyReply } from "fastify";
+import type { z } from "zod";
+import { ModelsService } from "../services";
+
+/** @description Models request handlers */
+export class ModelsController {
+ /** @description List models with pagination and filtering */
+ static listModels({
+ userJwt,
+ subdomain,
+ page,
+ perPage,
+ query,
+ makerId,
+ categoryId,
+ status,
+ sort,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return ModelsService.listModels({
+ userJwt,
+ subdomain,
+ page,
+ perPage,
+ query,
+ makerId,
+ categoryId,
+ status,
+ sort,
+ response,
+ });
+ }
+
+ /** @description Get a model by slug with full details */
+ static getModelBySlug({
+ userJwt,
+ subdomain,
+ slug,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ slug: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return ModelsService.getModelBySlug({
+ userJwt,
+ subdomain,
+ slug,
+ response,
+ });
+ }
+
+ /** @description Create a new model */
+ static createModel({
+ userJwt,
+ subdomain,
+ data,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ data: z.infer;
+ response: FastifyReply;
+ }) {
+ return ModelsService.createModel({ userJwt, subdomain, data, response });
+ }
+
+ /** @description Partially update a model */
+ static patchModel({
+ userJwt,
+ subdomain,
+ modelId,
+ data,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ modelId: string;
+ data: Record;
+ response: FastifyReply;
+ } & z.infer) {
+ return ModelsService.patchModel({
+ userJwt,
+ subdomain,
+ modelId,
+ data,
+ response,
+ });
+ }
+
+ /** @description Delete a model */
+ static deleteModel({
+ userJwt,
+ subdomain,
+ modelId,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ modelId: string;
+ response: FastifyReply;
+ } & z.infer) {
+ return ModelsService.deleteModel({ userJwt, subdomain, modelId, response });
+ }
+
+ /** @description Assign an uploaded image to a model */
+ static createModelImage({
+ userJwt,
+ subdomain,
+ modelId,
+ data,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ modelId: string;
+ data: z.infer;
+ response: FastifyReply;
+ }) {
+ return ModelsService.createModelImage({
+ userJwt,
+ subdomain,
+ modelId,
+ data,
+ response,
+ });
+ }
+
+ /** @description Delete a model image */
+ static deleteModelImage({
+ userJwt,
+ subdomain,
+ modelId,
+ imageId,
+ response,
+ }: {
+ userJwt: z.infer;
+ subdomain: string;
+ modelId: string;
+ imageId: string;
+ response: FastifyReply;
+ }) {
+ return ModelsService.deleteModelImage({
+ userJwt,
+ subdomain,
+ modelId,
+ imageId,
+ response,
+ });
+ }
+}
diff --git a/apps/server/src/modules/models/errors/index.ts b/apps/server/src/modules/models/errors/index.ts
new file mode 100644
index 0000000..57f3feb
--- /dev/null
+++ b/apps/server/src/modules/models/errors/index.ts
@@ -0,0 +1,45 @@
+import { defineErrors } from "../../../core/utils/errors";
+
+/** @description Error definitions for the models module */
+export const modelsErrors = defineErrors({
+ MODEL_NOT_FOUND: {
+ code: "model_not_found",
+ message: "Model not found.",
+ status: 404,
+ },
+ MODEL_PAGE_OUT_OF_BOUNDS: {
+ code: "page_out_of_bounds",
+ message: "The requested page exceeds the total number of pages.",
+ status: 416,
+ },
+ MODEL_COMPANY_NOT_FOUND: {
+ code: "company_not_found",
+ message: "There's no companies bound to your account",
+ status: 404,
+ },
+ MODEL_NOT_ALLOWED: {
+ code: "not_allowed",
+ message: "You are not authorized to access this company.",
+ status: 403,
+ },
+ MODEL_SLUG_CONFLICT: {
+ code: "model_slug_conflict",
+ message: "A model with this slug already exists.",
+ status: 409,
+ },
+ MODEL_MAKER_NOT_FOUND: {
+ code: "maker_not_found",
+ message: "The specified maker does not exist.",
+ status: 404,
+ },
+ MODEL_IMAGE_NOT_FOUND: {
+ code: "model_image_not_found",
+ message: "Model image not found.",
+ status: 404,
+ },
+ MODEL_IMAGE_KEY_MISMATCH: {
+ code: "model_image_key_mismatch",
+ message: "The provided image key does not belong to your company.",
+ status: 403,
+ },
+});
diff --git a/apps/server/src/modules/models/repositories/index.ts b/apps/server/src/modules/models/repositories/index.ts
new file mode 100644
index 0000000..a5ee04c
--- /dev/null
+++ b/apps/server/src/modules/models/repositories/index.ts
@@ -0,0 +1,511 @@
+import { DeleteObjectCommand } from "@aws-sdk/client-s3";
+import {
+ and,
+ asc,
+ db,
+ desc,
+ eq,
+ inArray,
+ or,
+ type SQL,
+ sql,
+} from "@fixr/db/connection";
+import {
+ type ModelImageSelect,
+ modelCategories,
+ modelImages,
+ modelMakers,
+ models,
+} from "@fixr/db/schema";
+import {
+ generatePresignedGetUrl,
+ r2Bucket,
+ r2Client,
+} from "../../../config/r2";
+
+const FTS_OPERATOR_REGEX = /[+\-*~()<>@]/;
+const WHITESPACE_REGEX = /\s+/;
+
+/** @description Column selection for paginated models minimal list */
+export const modelMinimalListSelect = {
+ id: models.id,
+ name: models.name,
+ slug: models.slug,
+ status: models.status,
+ price: models.price,
+ released: models.released,
+ makerId: modelMakers.id,
+ makerName: modelMakers.name,
+ makerSlug: modelMakers.slug,
+ categoryId: modelCategories.id,
+ categoryName: modelCategories.name,
+ categorySlug: modelCategories.slug,
+};
+
+/** @description Join definitions for models list queries */
+export const modelListJoins = [
+ {
+ type: "inner" as const,
+ table: modelMakers,
+ on: eq(modelMakers.id, models.makerId),
+ },
+ {
+ type: "left" as const,
+ table: modelCategories,
+ on: eq(modelCategories.id, models.categoryId),
+ },
+];
+
+export interface ModelFlatRecord {
+ id: string;
+ name: string;
+ slug: string;
+ status: string | null;
+ price: string | null;
+ released: string | null;
+ makerId: string;
+ makerName: string;
+ makerSlug: string;
+ categoryId: string | null;
+ categoryName: string | null;
+ categorySlug: string | null;
+}
+
+export interface ModelListRecord {
+ id: string;
+ name: string;
+ slug: string;
+ status: string;
+ price: string | null;
+ released: string | null;
+ maker: { id: string; name: string; slug: string };
+ category: { id: string; name: string; slug: string } | null;
+ imageUrl: string | null;
+}
+
+/** @description Data access layer for device models */
+export class ModelsRepository {
+ /**
+ * Build a WHERE clause for filtering models with fulltext search support
+ *
+ * @param companyId - Company ID for scoping
+ * @param filters - Query, maker, category, and status filters
+ */
+ static buildListFilter(
+ companyId: string,
+ filters: {
+ query?: string;
+ makerId?: string;
+ categoryId?: string;
+ status?: string;
+ }
+ ) {
+ const conditions: SQL[] = [
+ or(eq(models.companyId, companyId), sql`${models.companyId} IS NULL`)!,
+ ];
+
+ if (filters.makerId) {
+ conditions.push(eq(models.makerId, filters.makerId));
+ }
+ if (filters.categoryId) {
+ conditions.push(eq(models.categoryId, filters.categoryId));
+ }
+ if (filters.status) {
+ conditions.push(eq(models.status, filters.status));
+ }
+ if (filters.query) {
+ const hasOperators = FTS_OPERATOR_REGEX.test(filters.query);
+ const ftsQuery = hasOperators
+ ? filters.query
+ : filters.query
+ .trim()
+ .split(WHITESPACE_REGEX)
+ .map((w) => `${w}*`)
+ .join(" ");
+ conditions.push(
+ sql`MATCH(${models.name}, ${models.modelsText}, ${models.chipset}, ${models.cpu}, ${models.internalMemory}, ${models.os}) AGAINST(${ftsQuery} IN BOOLEAN MODE)`
+ );
+ }
+
+ return and(...conditions)!;
+ }
+
+ /**
+ * Build an ORDER BY clause for models
+ *
+ * @param sort - Sort key: newer, older, name
+ */
+ static buildOrder(sort?: string) {
+ switch (sort) {
+ case "newer":
+ return desc(models.createdAt);
+ case "older":
+ return asc(models.createdAt);
+ case "name":
+ return asc(models.name);
+ default:
+ return desc(models.createdAt);
+ }
+ }
+
+ /**
+ * Find a model by its slug with full maker and category relations
+ *
+ * @param slug - The model slug
+ * @param companyId - Optional company ID for scoping
+ * @returns The model record with relations or null
+ */
+ static async queryModelBySlug(slug: string, companyId?: string) {
+ const conditions: SQL[] = [eq(models.slug, slug)];
+ if (companyId) {
+ conditions.push(
+ or(eq(models.companyId, companyId), sql`${models.companyId} IS NULL`)!
+ );
+ }
+
+ const result = await db
+ .select({
+ id: models.id,
+ makerId: models.makerId,
+ name: models.name,
+ slug: models.slug,
+ url: models.url,
+ categoryId: models.categoryId,
+ announced: models.announced,
+ status: models.status,
+ dimensions: models.dimensions,
+ weight: models.weight,
+ build: models.build,
+ sim: models.sim,
+ displayType: models.displayType,
+ displaySize: models.displaySize,
+ displayResolution: models.displayResolution,
+ displayProtection: models.displayProtection,
+ os: models.os,
+ chipset: models.chipset,
+ cpu: models.cpu,
+ gpu: models.gpu,
+ cardSlot: models.cardSlot,
+ internalMemory: models.internalMemory,
+ mainCamera: models.mainCamera,
+ mainCameraFeatures: models.mainCameraFeatures,
+ mainCameraVideo: models.mainCameraVideo,
+ selfieCamera: models.selfieCamera,
+ selfieFeatures: models.selfieFeatures,
+ selfieVideo: models.selfieVideo,
+ battery: models.battery,
+ batteryCharging: models.batteryCharging,
+ networkTech: models.networkTech,
+ sensors: models.sensors,
+ colors: models.colors,
+ colorsHex: models.colorsHex,
+ modelsText: models.modelsText,
+ price: models.price,
+ dimensionsWidth: models.dimensionsWidth,
+ dimensionsHeight: models.dimensionsHeight,
+ dimensionsThickness: models.dimensionsThickness,
+ weightGrams: models.weightGrams,
+ displaySizeInches: models.displaySizeInches,
+ displaySizeRatio: models.displaySizeRatio,
+ displayResWidth: models.displayResWidth,
+ displayResHeight: models.displayResHeight,
+ displayResPpi: models.displayResPpi,
+ released: models.released,
+ meta: models.meta,
+ companyId: models.companyId,
+ createdAt: models.createdAt,
+ maker: {
+ id: modelMakers.id,
+ name: modelMakers.name,
+ slug: modelMakers.slug,
+ url: modelMakers.url,
+ },
+ category: {
+ id: modelCategories.id,
+ name: modelCategories.name,
+ slug: modelCategories.slug,
+ },
+ })
+ .from(models)
+ .innerJoin(modelMakers, eq(modelMakers.id, models.makerId))
+ .leftJoin(modelCategories, eq(modelCategories.id, models.categoryId))
+ .where(and(...conditions))
+ .limit(1);
+
+ return result[0] ?? null;
+ }
+
+ /**
+ * Query all images for a model, ordered by position
+ *
+ * @param modelId - The model ID
+ * @returns Array of model images
+ */
+ static async queryModelImages(modelId: string) {
+ return await db
+ .select()
+ .from(modelImages)
+ .where(eq(modelImages.modelId, modelId))
+ .orderBy(asc(modelImages.position));
+ }
+
+ /**
+ * Batch fetch primary model images for a set of model IDs
+ *
+ * @param modelIds - Array of model IDs
+ * @returns Map of modelId -> r2Key
+ */
+ static async queryPrimaryImages(
+ modelIds: string[]
+ ): Promise
diff --git a/package.json b/package.json
index 017ae81..e9bbae4 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"db:studio": "bun run --elide-lines 0 --filter @fixr/db db:studio",
"db:generate": "bun run --elide-lines 0 --filter @fixr/db db:generate",
"db:migrate": "bun run --elide-lines 0 --filter @fixr/db db:migrate",
+ "db:seed": "bun run --elide-lines 0 --filter @fixr/db db:seed",
"db:start": "bun run --elide-lines 0 --filter @fixr/db db:start",
"db:watch": "bun run --elide-lines 0 --filter @fixr/db db:watch",
"db:stop": "bun run --elide-lines 0 --filter @fixr/db db:stop",
diff --git a/packages/constants/package.json b/packages/constants/package.json
index 3645f15..31731f9 100644
--- a/packages/constants/package.json
+++ b/packages/constants/package.json
@@ -33,6 +33,10 @@
"./enforcements": {
"types": "./src/enforcements.ts",
"default": "./src/enforcements.ts"
+ },
+ "./slug": {
+ "types": "./src/slug.ts",
+ "default": "./src/slug.ts"
}
},
"devDependencies": {
diff --git a/packages/constants/src/slug.ts b/packages/constants/src/slug.ts
new file mode 100644
index 0000000..0240373
--- /dev/null
+++ b/packages/constants/src/slug.ts
@@ -0,0 +1,13 @@
+const SLUGIFY_SPACE_REGEX = /\s+/g;
+const SLUGIFY_SPECIAL_REGEX = /[^\w-]/g;
+const SLUGIFY_DUPLICATE_DASHES = /-+/g;
+const SLUGIFY_TRIM_DASHES = /^-|-$/g;
+
+export function slugify(text: string): string {
+ return text
+ .toLowerCase()
+ .replace(SLUGIFY_SPACE_REGEX, "-")
+ .replace(SLUGIFY_SPECIAL_REGEX, "")
+ .replace(SLUGIFY_DUPLICATE_DASHES, "-")
+ .replace(SLUGIFY_TRIM_DASHES, "");
+}
diff --git a/packages/db/drizzle/0006_awesome_cloak.sql b/packages/db/drizzle/0006_awesome_cloak.sql
new file mode 100644
index 0000000..d05a057
--- /dev/null
+++ b/packages/db/drizzle/0006_awesome_cloak.sql
@@ -0,0 +1,76 @@
+CREATE TABLE `model_categories` (
+ `id` varchar(25) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `slug` varchar(100) NOT NULL,
+ CONSTRAINT `model_categories_id` PRIMARY KEY(`id`),
+ CONSTRAINT `model_categories_slug_unique` UNIQUE(`slug`)
+);
+--> statement-breakpoint
+CREATE TABLE `model_makers` (
+ `id` varchar(25) NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `slug` varchar(100) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `device_count` int NOT NULL DEFAULT 0,
+ `page_count` int,
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `model_makers_id` PRIMARY KEY(`id`),
+ CONSTRAINT `model_makers_slug_unique` UNIQUE(`slug`)
+);
+--> statement-breakpoint
+CREATE TABLE `service_order_images` (
+ `id` varchar(25) NOT NULL,
+ `service_order_id` varchar(25) NOT NULL,
+ `employee_id` varchar(25) NOT NULL,
+ `upload_id` varchar(25) NOT NULL,
+ `image_url` varchar(255) NOT NULL,
+ `file_name` varchar(255) NOT NULL,
+ `size_in_bytes` int NOT NULL,
+ `content_type` varchar(50) NOT NULL,
+ `description` varchar(255),
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `service_order_images_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `service_orders` (
+ `id` varchar(25) NOT NULL,
+ `company_id` varchar(25) NOT NULL,
+ `client_id` varchar(25) NOT NULL,
+ `employee_id` varchar(25) NOT NULL,
+ `device_brand_id` varchar(25) NOT NULL,
+ `device_category_id` varchar(25) NOT NULL,
+ `device_model` varchar(100) NOT NULL,
+ `imei` varchar(50),
+ `reported_defect` text NOT NULL,
+ `observations` text,
+ `status` enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered') NOT NULL DEFAULT 'pending',
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ `updated_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `service_orders_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `uploads` (
+ `id` varchar(25) NOT NULL,
+ `company_id` varchar(25) NOT NULL,
+ `employee_id` varchar(25) NOT NULL,
+ `key` varchar(512) NOT NULL,
+ `url` varchar(512) NOT NULL,
+ `file_name` varchar(255) NOT NULL,
+ `content_type` varchar(50) NOT NULL,
+ `size_in_bytes` int NOT NULL,
+ `status` varchar(20) NOT NULL DEFAULT 'pending',
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `uploads_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+ALTER TABLE `employees` MODIFY COLUMN `roles` enum('guest','technician','warehouse','financial','manager','admin') NOT NULL;--> statement-breakpoint
+ALTER TABLE `service_order_images` ADD CONSTRAINT `service_order_images_service_order_id_service_orders_id_fk` FOREIGN KEY (`service_order_id`) REFERENCES `service_orders`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_order_images` ADD CONSTRAINT `service_order_images_employee_id_employees_id_fk` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_order_images` ADD CONSTRAINT `service_order_images_upload_id_uploads_id_fk` FOREIGN KEY (`upload_id`) REFERENCES `uploads`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_orders` ADD CONSTRAINT `service_orders_company_id_companies_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_orders` ADD CONSTRAINT `service_orders_client_id_clients_id_fk` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_orders` ADD CONSTRAINT `service_orders_employee_id_employees_id_fk` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_orders` ADD CONSTRAINT `service_orders_device_brand_id_model_makers_id_fk` FOREIGN KEY (`device_brand_id`) REFERENCES `model_makers`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `service_orders` ADD CONSTRAINT `service_orders_device_category_id_model_categories_id_fk` FOREIGN KEY (`device_category_id`) REFERENCES `model_categories`(`id`) ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `uploads` ADD CONSTRAINT `uploads_company_id_companies_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `uploads` ADD CONSTRAINT `uploads_employee_id_employees_id_fk` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`) ON DELETE restrict ON UPDATE no action;
\ No newline at end of file
diff --git a/packages/db/drizzle/0007_romantic_union_jack.sql b/packages/db/drizzle/0007_romantic_union_jack.sql
new file mode 100644
index 0000000..0c62a17
--- /dev/null
+++ b/packages/db/drizzle/0007_romantic_union_jack.sql
@@ -0,0 +1,74 @@
+CREATE TABLE `model_images` (
+ `id` varchar(25) NOT NULL,
+ `model_id` varchar(25) NOT NULL,
+ `original_url` varchar(255),
+ `r2_key` varchar(255),
+ `is_primary` boolean NOT NULL DEFAULT false,
+ `variant` varchar(50),
+ `position` int NOT NULL DEFAULT 0,
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `model_images_id` PRIMARY KEY(`id`)
+);
+--> statement-breakpoint
+CREATE TABLE `models` (
+ `id` varchar(25) NOT NULL,
+ `maker_id` varchar(25) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `slug` varchar(100) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `image_url` varchar(255),
+ `image_local_path` varchar(255),
+ `category_id` varchar(25),
+ `announced` text,
+ `status` text,
+ `dimensions` text,
+ `weight` text,
+ `build` text,
+ `sim` text,
+ `display_type` text,
+ `display_size` text,
+ `display_resolution` text,
+ `display_protection` text,
+ `os` text,
+ `chipset` text,
+ `cpu` text,
+ `gpu` text,
+ `card_slot` text,
+ `internal_memory` text,
+ `main_camera` text,
+ `main_camera_features` text,
+ `main_camera_video` text,
+ `selfie_camera` text,
+ `selfie_features` text,
+ `selfie_video` text,
+ `battery` text,
+ `battery_charging` text,
+ `network_tech` text,
+ `sensors` text,
+ `colors` text,
+ `colors_hex` text,
+ `models_text` text,
+ `price` text,
+ `dimensions_width` float,
+ `dimensions_height` float,
+ `dimensions_thickness` float,
+ `weight_grams` float,
+ `display_size_inches` float,
+ `display_size_ratio` text,
+ `display_res_width` int,
+ `display_res_height` int,
+ `display_res_ppi` int,
+ `released` text,
+ `meta` text,
+ `company_id` varchar(25),
+ `created_at` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `models_id` PRIMARY KEY(`id`),
+ CONSTRAINT `slug_company_unique` UNIQUE(`slug`,`company_id`)
+);
+--> statement-breakpoint
+ALTER TABLE `model_categories` DROP INDEX `model_categories_slug_unique`;--> statement-breakpoint
+ALTER TABLE `model_makers` DROP INDEX `model_makers_slug_unique`;--> statement-breakpoint
+ALTER TABLE `model_images` ADD CONSTRAINT `model_images_model_id_models_id_fk` FOREIGN KEY (`model_id`) REFERENCES `models`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `models` ADD CONSTRAINT `models_maker_id_model_makers_id_fk` FOREIGN KEY (`maker_id`) REFERENCES `model_makers`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `models` ADD CONSTRAINT `models_category_id_model_categories_id_fk` FOREIGN KEY (`category_id`) REFERENCES `model_categories`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE `models` ADD CONSTRAINT `models_company_id_companies_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE no action ON UPDATE no action;
\ No newline at end of file
diff --git a/packages/db/drizzle/0008_slimy_abomination.sql b/packages/db/drizzle/0008_slimy_abomination.sql
new file mode 100644
index 0000000..5008d6b
--- /dev/null
+++ b/packages/db/drizzle/0008_slimy_abomination.sql
@@ -0,0 +1 @@
+ALTER TABLE `models` DROP COLUMN `image_local_path`;
\ No newline at end of file
diff --git a/packages/db/drizzle/0009_ambiguous_hammerhead.sql b/packages/db/drizzle/0009_ambiguous_hammerhead.sql
new file mode 100644
index 0000000..c1374d7
--- /dev/null
+++ b/packages/db/drizzle/0009_ambiguous_hammerhead.sql
@@ -0,0 +1 @@
+ALTER TABLE `models` DROP COLUMN `image_url`;
\ No newline at end of file
diff --git a/packages/db/drizzle/0010_sturdy_fulltext.sql b/packages/db/drizzle/0010_sturdy_fulltext.sql
new file mode 100644
index 0000000..d2c3875
--- /dev/null
+++ b/packages/db/drizzle/0010_sturdy_fulltext.sql
@@ -0,0 +1 @@
+ALTER TABLE `models` ADD FULLTEXT INDEX `models_fulltext_idx` (`name`, `models_text`, `chipset`, `cpu`, `internal_memory`, `os`);
diff --git a/packages/db/drizzle/0011_crazy_slapstick.sql b/packages/db/drizzle/0011_crazy_slapstick.sql
new file mode 100644
index 0000000..0243498
--- /dev/null
+++ b/packages/db/drizzle/0011_crazy_slapstick.sql
@@ -0,0 +1 @@
+ALTER TABLE `model_images` DROP COLUMN `original_url`;
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/0006_snapshot.json b/packages/db/drizzle/meta/0006_snapshot.json
new file mode 100644
index 0000000..0249b7c
--- /dev/null
+++ b/packages/db/drizzle/meta/0006_snapshot.json
@@ -0,0 +1,962 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "ab909380-0bce-443b-b2a8-5ebb6488b252",
+ "prevId": "96fef0d8-3c1e-4d2f-990d-625797fa1861",
+ "tables": {
+ "clients": {
+ "name": "clients",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "clients_user_id_users_id_fk": {
+ "name": "clients_user_id_users_id_fk",
+ "tableFrom": "clients",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "clients_id": {
+ "name": "clients_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "clients_cpf_unique": {
+ "name": "clients_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "companies": {
+ "name": "companies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cnpj": {
+ "name": "cnpj",
+ "type": "varchar(14)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "companies_id": {
+ "name": "companies_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "companies_cnpj_unique": {
+ "name": "companies_cnpj_unique",
+ "columns": ["cnpj"]
+ },
+ "companies_subdomain_unique": {
+ "name": "companies_subdomain_unique",
+ "columns": ["subdomain"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "employees": {
+ "name": "employees",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "roles": {
+ "name": "roles",
+ "type": "enum('guest','technician','warehouse','financial','manager','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "employees_user_id_users_id_fk": {
+ "name": "employees_user_id_users_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "employees_company_id_companies_id_fk": {
+ "name": "employees_company_id_companies_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "employees_id": {
+ "name": "employees_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "employees_cpf_unique": {
+ "name": "employees_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_categories": {
+ "name": "model_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_categories_id": {
+ "name": "model_categories_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "model_categories_slug_unique": {
+ "name": "model_categories_slug_unique",
+ "columns": ["slug"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_makers": {
+ "name": "model_makers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_count": {
+ "name": "device_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "page_count": {
+ "name": "page_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_makers_id": {
+ "name": "model_makers_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "model_makers_slug_unique": {
+ "name": "model_makers_slug_unique",
+ "columns": ["slug"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "one_time_tokens": {
+ "name": "one_time_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ott_type": {
+ "name": "ott_type",
+ "type": "enum('confirmation','password_reset','account_deletion')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "relates_to": {
+ "name": "relates_to",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "one_time_tokens_user_id_users_id_fk": {
+ "name": "one_time_tokens_user_id_users_id_fk",
+ "tableFrom": "one_time_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "one_time_tokens_id": {
+ "name": "one_time_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "one_time_tokens_token_unique": {
+ "name": "one_time_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "refresh_tokens": {
+ "name": "refresh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "refresh_tokens_id": {
+ "name": "refresh_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "refresh_tokens_token_unique": {
+ "name": "refresh_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "service_order_images": {
+ "name": "service_order_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_order_id": {
+ "name": "service_order_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_order_images_service_order_id_service_orders_id_fk": {
+ "name": "service_order_images_service_order_id_service_orders_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "service_orders",
+ "columnsFrom": ["service_order_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_order_images_employee_id_employees_id_fk": {
+ "name": "service_order_images_employee_id_employees_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_order_images_upload_id_uploads_id_fk": {
+ "name": "service_order_images_upload_id_uploads_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "uploads",
+ "columnsFrom": ["upload_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_order_images_id": {
+ "name": "service_order_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "service_orders": {
+ "name": "service_orders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_brand_id": {
+ "name": "device_brand_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_category_id": {
+ "name": "device_category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_model": {
+ "name": "device_model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imei": {
+ "name": "imei",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reported_defect": {
+ "name": "reported_defect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "observations": {
+ "name": "observations",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_orders_company_id_companies_id_fk": {
+ "name": "service_orders_company_id_companies_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_orders_client_id_clients_id_fk": {
+ "name": "service_orders_client_id_clients_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "clients",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_employee_id_employees_id_fk": {
+ "name": "service_orders_employee_id_employees_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_brand_id_model_makers_id_fk": {
+ "name": "service_orders_device_brand_id_model_makers_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_makers",
+ "columnsFrom": ["device_brand_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_category_id_model_categories_id_fk": {
+ "name": "service_orders_device_category_id_model_categories_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_categories",
+ "columnsFrom": ["device_category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_orders_id": {
+ "name": "service_orders_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "uploads": {
+ "name": "uploads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploads_company_id_companies_id_fk": {
+ "name": "uploads_company_id_companies_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "uploads_employee_id_employees_id_fk": {
+ "name": "uploads_employee_id_employees_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "uploads_id": {
+ "name": "uploads_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_id": {
+ "name": "google_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": ["email"]
+ },
+ "users_google_id_unique": {
+ "name": "users_google_id_unique",
+ "columns": ["google_id"]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/0007_snapshot.json b/packages/db/drizzle/meta/0007_snapshot.json
new file mode 100644
index 0000000..ba12de1
--- /dev/null
+++ b/packages/db/drizzle/meta/0007_snapshot.json
@@ -0,0 +1,1442 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "f8e272be-5fd8-4902-aa53-8ee19994cce6",
+ "prevId": "ab909380-0bce-443b-b2a8-5ebb6488b252",
+ "tables": {
+ "clients": {
+ "name": "clients",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "clients_user_id_users_id_fk": {
+ "name": "clients_user_id_users_id_fk",
+ "tableFrom": "clients",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "clients_id": {
+ "name": "clients_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "clients_cpf_unique": {
+ "name": "clients_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "companies": {
+ "name": "companies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cnpj": {
+ "name": "cnpj",
+ "type": "varchar(14)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "companies_id": {
+ "name": "companies_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "companies_cnpj_unique": {
+ "name": "companies_cnpj_unique",
+ "columns": ["cnpj"]
+ },
+ "companies_subdomain_unique": {
+ "name": "companies_subdomain_unique",
+ "columns": ["subdomain"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_makers": {
+ "name": "model_makers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_count": {
+ "name": "device_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "page_count": {
+ "name": "page_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_makers_id": {
+ "name": "model_makers_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "employees": {
+ "name": "employees",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "roles": {
+ "name": "roles",
+ "type": "enum('guest','technician','warehouse','financial','manager','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "employees_user_id_users_id_fk": {
+ "name": "employees_user_id_users_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "employees_company_id_companies_id_fk": {
+ "name": "employees_company_id_companies_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "employees_id": {
+ "name": "employees_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "employees_cpf_unique": {
+ "name": "employees_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_categories": {
+ "name": "model_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_categories_id": {
+ "name": "model_categories_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "model_images": {
+ "name": "model_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "original_url": {
+ "name": "original_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "r2_key": {
+ "name": "r2_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_primary": {
+ "name": "is_primary",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "variant": {
+ "name": "variant",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "model_images_model_id_models_id_fk": {
+ "name": "model_images_model_id_models_id_fk",
+ "tableFrom": "model_images",
+ "tableTo": "models",
+ "columnsFrom": ["model_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "model_images_id": {
+ "name": "model_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "models": {
+ "name": "models",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maker_id": {
+ "name": "maker_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image_local_path": {
+ "name": "image_local_path",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "announced": {
+ "name": "announced",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions": {
+ "name": "dimensions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "build": {
+ "name": "build",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sim": {
+ "name": "sim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_type": {
+ "name": "display_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size": {
+ "name": "display_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_resolution": {
+ "name": "display_resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_protection": {
+ "name": "display_protection",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "os": {
+ "name": "os",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "chipset": {
+ "name": "chipset",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu": {
+ "name": "cpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "card_slot": {
+ "name": "card_slot",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "internal_memory": {
+ "name": "internal_memory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera": {
+ "name": "main_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_features": {
+ "name": "main_camera_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_video": {
+ "name": "main_camera_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_camera": {
+ "name": "selfie_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_features": {
+ "name": "selfie_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_video": {
+ "name": "selfie_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery": {
+ "name": "battery",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery_charging": {
+ "name": "battery_charging",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_tech": {
+ "name": "network_tech",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sensors": {
+ "name": "sensors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors": {
+ "name": "colors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors_hex": {
+ "name": "colors_hex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "models_text": {
+ "name": "models_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "price": {
+ "name": "price",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_width": {
+ "name": "dimensions_width",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_height": {
+ "name": "dimensions_height",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_thickness": {
+ "name": "dimensions_thickness",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight_grams": {
+ "name": "weight_grams",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_inches": {
+ "name": "display_size_inches",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_ratio": {
+ "name": "display_size_ratio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_width": {
+ "name": "display_res_width",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_height": {
+ "name": "display_res_height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_ppi": {
+ "name": "display_res_ppi",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "released": {
+ "name": "released",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta": {
+ "name": "meta",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "models_maker_id_model_makers_id_fk": {
+ "name": "models_maker_id_model_makers_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_makers",
+ "columnsFrom": ["maker_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_category_id_model_categories_id_fk": {
+ "name": "models_category_id_model_categories_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_categories",
+ "columnsFrom": ["category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_company_id_companies_id_fk": {
+ "name": "models_company_id_companies_id_fk",
+ "tableFrom": "models",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "models_id": {
+ "name": "models_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "slug_company_unique": {
+ "name": "slug_company_unique",
+ "columns": ["slug", "company_id"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "one_time_tokens": {
+ "name": "one_time_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ott_type": {
+ "name": "ott_type",
+ "type": "enum('confirmation','password_reset','account_deletion')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "relates_to": {
+ "name": "relates_to",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "one_time_tokens_user_id_users_id_fk": {
+ "name": "one_time_tokens_user_id_users_id_fk",
+ "tableFrom": "one_time_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "one_time_tokens_id": {
+ "name": "one_time_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "one_time_tokens_token_unique": {
+ "name": "one_time_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "refresh_tokens": {
+ "name": "refresh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "refresh_tokens_id": {
+ "name": "refresh_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "refresh_tokens_token_unique": {
+ "name": "refresh_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "service_order_images": {
+ "name": "service_order_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_order_id": {
+ "name": "service_order_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_order_images_service_order_id_service_orders_id_fk": {
+ "name": "service_order_images_service_order_id_service_orders_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "service_orders",
+ "columnsFrom": ["service_order_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_order_images_employee_id_employees_id_fk": {
+ "name": "service_order_images_employee_id_employees_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_order_images_upload_id_uploads_id_fk": {
+ "name": "service_order_images_upload_id_uploads_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "uploads",
+ "columnsFrom": ["upload_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_order_images_id": {
+ "name": "service_order_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "service_orders": {
+ "name": "service_orders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_brand_id": {
+ "name": "device_brand_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_category_id": {
+ "name": "device_category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_model": {
+ "name": "device_model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imei": {
+ "name": "imei",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reported_defect": {
+ "name": "reported_defect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "observations": {
+ "name": "observations",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_orders_company_id_companies_id_fk": {
+ "name": "service_orders_company_id_companies_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_orders_client_id_clients_id_fk": {
+ "name": "service_orders_client_id_clients_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "clients",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_employee_id_employees_id_fk": {
+ "name": "service_orders_employee_id_employees_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_brand_id_model_makers_id_fk": {
+ "name": "service_orders_device_brand_id_model_makers_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_makers",
+ "columnsFrom": ["device_brand_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_category_id_model_categories_id_fk": {
+ "name": "service_orders_device_category_id_model_categories_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_categories",
+ "columnsFrom": ["device_category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_orders_id": {
+ "name": "service_orders_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "uploads": {
+ "name": "uploads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploads_company_id_companies_id_fk": {
+ "name": "uploads_company_id_companies_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "uploads_employee_id_employees_id_fk": {
+ "name": "uploads_employee_id_employees_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "uploads_id": {
+ "name": "uploads_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_id": {
+ "name": "google_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": ["email"]
+ },
+ "users_google_id_unique": {
+ "name": "users_google_id_unique",
+ "columns": ["google_id"]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/0008_snapshot.json b/packages/db/drizzle/meta/0008_snapshot.json
new file mode 100644
index 0000000..3f21437
--- /dev/null
+++ b/packages/db/drizzle/meta/0008_snapshot.json
@@ -0,0 +1,1435 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "7a33fdf9-9138-4db6-96e0-c729b47e5f0a",
+ "prevId": "f8e272be-5fd8-4902-aa53-8ee19994cce6",
+ "tables": {
+ "clients": {
+ "name": "clients",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "clients_user_id_users_id_fk": {
+ "name": "clients_user_id_users_id_fk",
+ "tableFrom": "clients",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "clients_id": {
+ "name": "clients_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "clients_cpf_unique": {
+ "name": "clients_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "companies": {
+ "name": "companies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cnpj": {
+ "name": "cnpj",
+ "type": "varchar(14)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "companies_id": {
+ "name": "companies_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "companies_cnpj_unique": {
+ "name": "companies_cnpj_unique",
+ "columns": ["cnpj"]
+ },
+ "companies_subdomain_unique": {
+ "name": "companies_subdomain_unique",
+ "columns": ["subdomain"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_makers": {
+ "name": "model_makers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_count": {
+ "name": "device_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "page_count": {
+ "name": "page_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_makers_id": {
+ "name": "model_makers_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "employees": {
+ "name": "employees",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "roles": {
+ "name": "roles",
+ "type": "enum('guest','technician','warehouse','financial','manager','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "employees_user_id_users_id_fk": {
+ "name": "employees_user_id_users_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "employees_company_id_companies_id_fk": {
+ "name": "employees_company_id_companies_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "employees_id": {
+ "name": "employees_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "employees_cpf_unique": {
+ "name": "employees_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_categories": {
+ "name": "model_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_categories_id": {
+ "name": "model_categories_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "model_images": {
+ "name": "model_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "original_url": {
+ "name": "original_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "r2_key": {
+ "name": "r2_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_primary": {
+ "name": "is_primary",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "variant": {
+ "name": "variant",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "model_images_model_id_models_id_fk": {
+ "name": "model_images_model_id_models_id_fk",
+ "tableFrom": "model_images",
+ "tableTo": "models",
+ "columnsFrom": ["model_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "model_images_id": {
+ "name": "model_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "models": {
+ "name": "models",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maker_id": {
+ "name": "maker_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "announced": {
+ "name": "announced",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions": {
+ "name": "dimensions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "build": {
+ "name": "build",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sim": {
+ "name": "sim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_type": {
+ "name": "display_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size": {
+ "name": "display_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_resolution": {
+ "name": "display_resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_protection": {
+ "name": "display_protection",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "os": {
+ "name": "os",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "chipset": {
+ "name": "chipset",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu": {
+ "name": "cpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "card_slot": {
+ "name": "card_slot",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "internal_memory": {
+ "name": "internal_memory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera": {
+ "name": "main_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_features": {
+ "name": "main_camera_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_video": {
+ "name": "main_camera_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_camera": {
+ "name": "selfie_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_features": {
+ "name": "selfie_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_video": {
+ "name": "selfie_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery": {
+ "name": "battery",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery_charging": {
+ "name": "battery_charging",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_tech": {
+ "name": "network_tech",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sensors": {
+ "name": "sensors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors": {
+ "name": "colors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors_hex": {
+ "name": "colors_hex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "models_text": {
+ "name": "models_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "price": {
+ "name": "price",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_width": {
+ "name": "dimensions_width",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_height": {
+ "name": "dimensions_height",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_thickness": {
+ "name": "dimensions_thickness",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight_grams": {
+ "name": "weight_grams",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_inches": {
+ "name": "display_size_inches",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_ratio": {
+ "name": "display_size_ratio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_width": {
+ "name": "display_res_width",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_height": {
+ "name": "display_res_height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_ppi": {
+ "name": "display_res_ppi",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "released": {
+ "name": "released",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta": {
+ "name": "meta",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "models_maker_id_model_makers_id_fk": {
+ "name": "models_maker_id_model_makers_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_makers",
+ "columnsFrom": ["maker_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_category_id_model_categories_id_fk": {
+ "name": "models_category_id_model_categories_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_categories",
+ "columnsFrom": ["category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_company_id_companies_id_fk": {
+ "name": "models_company_id_companies_id_fk",
+ "tableFrom": "models",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "models_id": {
+ "name": "models_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "slug_company_unique": {
+ "name": "slug_company_unique",
+ "columns": ["slug", "company_id"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "one_time_tokens": {
+ "name": "one_time_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ott_type": {
+ "name": "ott_type",
+ "type": "enum('confirmation','password_reset','account_deletion')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "relates_to": {
+ "name": "relates_to",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "one_time_tokens_user_id_users_id_fk": {
+ "name": "one_time_tokens_user_id_users_id_fk",
+ "tableFrom": "one_time_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "one_time_tokens_id": {
+ "name": "one_time_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "one_time_tokens_token_unique": {
+ "name": "one_time_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "refresh_tokens": {
+ "name": "refresh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "refresh_tokens_id": {
+ "name": "refresh_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "refresh_tokens_token_unique": {
+ "name": "refresh_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "service_order_images": {
+ "name": "service_order_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_order_id": {
+ "name": "service_order_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_order_images_service_order_id_service_orders_id_fk": {
+ "name": "service_order_images_service_order_id_service_orders_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "service_orders",
+ "columnsFrom": ["service_order_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_order_images_employee_id_employees_id_fk": {
+ "name": "service_order_images_employee_id_employees_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_order_images_upload_id_uploads_id_fk": {
+ "name": "service_order_images_upload_id_uploads_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "uploads",
+ "columnsFrom": ["upload_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_order_images_id": {
+ "name": "service_order_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "service_orders": {
+ "name": "service_orders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_brand_id": {
+ "name": "device_brand_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_category_id": {
+ "name": "device_category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_model": {
+ "name": "device_model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imei": {
+ "name": "imei",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reported_defect": {
+ "name": "reported_defect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "observations": {
+ "name": "observations",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_orders_company_id_companies_id_fk": {
+ "name": "service_orders_company_id_companies_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_orders_client_id_clients_id_fk": {
+ "name": "service_orders_client_id_clients_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "clients",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_employee_id_employees_id_fk": {
+ "name": "service_orders_employee_id_employees_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_brand_id_model_makers_id_fk": {
+ "name": "service_orders_device_brand_id_model_makers_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_makers",
+ "columnsFrom": ["device_brand_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_category_id_model_categories_id_fk": {
+ "name": "service_orders_device_category_id_model_categories_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_categories",
+ "columnsFrom": ["device_category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_orders_id": {
+ "name": "service_orders_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "uploads": {
+ "name": "uploads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploads_company_id_companies_id_fk": {
+ "name": "uploads_company_id_companies_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "uploads_employee_id_employees_id_fk": {
+ "name": "uploads_employee_id_employees_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "uploads_id": {
+ "name": "uploads_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_id": {
+ "name": "google_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": ["email"]
+ },
+ "users_google_id_unique": {
+ "name": "users_google_id_unique",
+ "columns": ["google_id"]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/0009_snapshot.json b/packages/db/drizzle/meta/0009_snapshot.json
new file mode 100644
index 0000000..3ac8f5c
--- /dev/null
+++ b/packages/db/drizzle/meta/0009_snapshot.json
@@ -0,0 +1,1428 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "0f499b87-ef09-4b34-a384-2a6f28f37c2d",
+ "prevId": "7a33fdf9-9138-4db6-96e0-c729b47e5f0a",
+ "tables": {
+ "clients": {
+ "name": "clients",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "clients_user_id_users_id_fk": {
+ "name": "clients_user_id_users_id_fk",
+ "tableFrom": "clients",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "clients_id": {
+ "name": "clients_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "clients_cpf_unique": {
+ "name": "clients_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "companies": {
+ "name": "companies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cnpj": {
+ "name": "cnpj",
+ "type": "varchar(14)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "companies_id": {
+ "name": "companies_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "companies_cnpj_unique": {
+ "name": "companies_cnpj_unique",
+ "columns": ["cnpj"]
+ },
+ "companies_subdomain_unique": {
+ "name": "companies_subdomain_unique",
+ "columns": ["subdomain"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_makers": {
+ "name": "model_makers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_count": {
+ "name": "device_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "page_count": {
+ "name": "page_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_makers_id": {
+ "name": "model_makers_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "employees": {
+ "name": "employees",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "roles": {
+ "name": "roles",
+ "type": "enum('guest','technician','warehouse','financial','manager','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "employees_user_id_users_id_fk": {
+ "name": "employees_user_id_users_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "employees_company_id_companies_id_fk": {
+ "name": "employees_company_id_companies_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "employees_id": {
+ "name": "employees_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "employees_cpf_unique": {
+ "name": "employees_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_categories": {
+ "name": "model_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_categories_id": {
+ "name": "model_categories_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "model_images": {
+ "name": "model_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "original_url": {
+ "name": "original_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "r2_key": {
+ "name": "r2_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_primary": {
+ "name": "is_primary",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "variant": {
+ "name": "variant",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "model_images_model_id_models_id_fk": {
+ "name": "model_images_model_id_models_id_fk",
+ "tableFrom": "model_images",
+ "tableTo": "models",
+ "columnsFrom": ["model_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "model_images_id": {
+ "name": "model_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "models": {
+ "name": "models",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maker_id": {
+ "name": "maker_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "announced": {
+ "name": "announced",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions": {
+ "name": "dimensions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "build": {
+ "name": "build",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sim": {
+ "name": "sim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_type": {
+ "name": "display_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size": {
+ "name": "display_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_resolution": {
+ "name": "display_resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_protection": {
+ "name": "display_protection",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "os": {
+ "name": "os",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "chipset": {
+ "name": "chipset",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu": {
+ "name": "cpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "card_slot": {
+ "name": "card_slot",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "internal_memory": {
+ "name": "internal_memory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera": {
+ "name": "main_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_features": {
+ "name": "main_camera_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_video": {
+ "name": "main_camera_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_camera": {
+ "name": "selfie_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_features": {
+ "name": "selfie_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_video": {
+ "name": "selfie_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery": {
+ "name": "battery",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery_charging": {
+ "name": "battery_charging",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_tech": {
+ "name": "network_tech",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sensors": {
+ "name": "sensors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors": {
+ "name": "colors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors_hex": {
+ "name": "colors_hex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "models_text": {
+ "name": "models_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "price": {
+ "name": "price",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_width": {
+ "name": "dimensions_width",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_height": {
+ "name": "dimensions_height",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_thickness": {
+ "name": "dimensions_thickness",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight_grams": {
+ "name": "weight_grams",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_inches": {
+ "name": "display_size_inches",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_ratio": {
+ "name": "display_size_ratio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_width": {
+ "name": "display_res_width",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_height": {
+ "name": "display_res_height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_ppi": {
+ "name": "display_res_ppi",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "released": {
+ "name": "released",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta": {
+ "name": "meta",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "models_maker_id_model_makers_id_fk": {
+ "name": "models_maker_id_model_makers_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_makers",
+ "columnsFrom": ["maker_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_category_id_model_categories_id_fk": {
+ "name": "models_category_id_model_categories_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_categories",
+ "columnsFrom": ["category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_company_id_companies_id_fk": {
+ "name": "models_company_id_companies_id_fk",
+ "tableFrom": "models",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "models_id": {
+ "name": "models_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "slug_company_unique": {
+ "name": "slug_company_unique",
+ "columns": ["slug", "company_id"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "one_time_tokens": {
+ "name": "one_time_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ott_type": {
+ "name": "ott_type",
+ "type": "enum('confirmation','password_reset','account_deletion')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "relates_to": {
+ "name": "relates_to",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "one_time_tokens_user_id_users_id_fk": {
+ "name": "one_time_tokens_user_id_users_id_fk",
+ "tableFrom": "one_time_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "one_time_tokens_id": {
+ "name": "one_time_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "one_time_tokens_token_unique": {
+ "name": "one_time_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "refresh_tokens": {
+ "name": "refresh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "refresh_tokens_id": {
+ "name": "refresh_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "refresh_tokens_token_unique": {
+ "name": "refresh_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "service_order_images": {
+ "name": "service_order_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_order_id": {
+ "name": "service_order_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_order_images_service_order_id_service_orders_id_fk": {
+ "name": "service_order_images_service_order_id_service_orders_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "service_orders",
+ "columnsFrom": ["service_order_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_order_images_employee_id_employees_id_fk": {
+ "name": "service_order_images_employee_id_employees_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_order_images_upload_id_uploads_id_fk": {
+ "name": "service_order_images_upload_id_uploads_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "uploads",
+ "columnsFrom": ["upload_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_order_images_id": {
+ "name": "service_order_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "service_orders": {
+ "name": "service_orders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_brand_id": {
+ "name": "device_brand_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_category_id": {
+ "name": "device_category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_model": {
+ "name": "device_model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imei": {
+ "name": "imei",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reported_defect": {
+ "name": "reported_defect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "observations": {
+ "name": "observations",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_orders_company_id_companies_id_fk": {
+ "name": "service_orders_company_id_companies_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_orders_client_id_clients_id_fk": {
+ "name": "service_orders_client_id_clients_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "clients",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_employee_id_employees_id_fk": {
+ "name": "service_orders_employee_id_employees_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_brand_id_model_makers_id_fk": {
+ "name": "service_orders_device_brand_id_model_makers_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_makers",
+ "columnsFrom": ["device_brand_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_category_id_model_categories_id_fk": {
+ "name": "service_orders_device_category_id_model_categories_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_categories",
+ "columnsFrom": ["device_category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_orders_id": {
+ "name": "service_orders_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "uploads": {
+ "name": "uploads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploads_company_id_companies_id_fk": {
+ "name": "uploads_company_id_companies_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "uploads_employee_id_employees_id_fk": {
+ "name": "uploads_employee_id_employees_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "uploads_id": {
+ "name": "uploads_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_id": {
+ "name": "google_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": ["email"]
+ },
+ "users_google_id_unique": {
+ "name": "users_google_id_unique",
+ "columns": ["google_id"]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/0011_snapshot.json b/packages/db/drizzle/meta/0011_snapshot.json
new file mode 100644
index 0000000..4ecf4c3
--- /dev/null
+++ b/packages/db/drizzle/meta/0011_snapshot.json
@@ -0,0 +1,1421 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "0b530de8-4672-48e9-872d-56bc108a654e",
+ "prevId": "0f499b87-ef09-4b34-a384-2a6f28f37c2d",
+ "tables": {
+ "clients": {
+ "name": "clients",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "clients_user_id_users_id_fk": {
+ "name": "clients_user_id_users_id_fk",
+ "tableFrom": "clients",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "clients_id": {
+ "name": "clients_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "clients_cpf_unique": {
+ "name": "clients_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "companies": {
+ "name": "companies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cnpj": {
+ "name": "cnpj",
+ "type": "varchar(14)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "companies_id": {
+ "name": "companies_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "companies_cnpj_unique": {
+ "name": "companies_cnpj_unique",
+ "columns": ["cnpj"]
+ },
+ "companies_subdomain_unique": {
+ "name": "companies_subdomain_unique",
+ "columns": ["subdomain"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_makers": {
+ "name": "model_makers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_count": {
+ "name": "device_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "page_count": {
+ "name": "page_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_makers_id": {
+ "name": "model_makers_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "employees": {
+ "name": "employees",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cpf": {
+ "name": "cpf",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "varchar(11)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "roles": {
+ "name": "roles",
+ "type": "enum('guest','technician','warehouse','financial','manager','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "employees_user_id_users_id_fk": {
+ "name": "employees_user_id_users_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "employees_company_id_companies_id_fk": {
+ "name": "employees_company_id_companies_id_fk",
+ "tableFrom": "employees",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "employees_id": {
+ "name": "employees_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "employees_cpf_unique": {
+ "name": "employees_cpf_unique",
+ "columns": ["cpf"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "model_categories": {
+ "name": "model_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_categories_id": {
+ "name": "model_categories_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "model_images": {
+ "name": "model_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "r2_key": {
+ "name": "r2_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_primary": {
+ "name": "is_primary",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "variant": {
+ "name": "variant",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "model_images_model_id_models_id_fk": {
+ "name": "model_images_model_id_models_id_fk",
+ "tableFrom": "model_images",
+ "tableTo": "models",
+ "columnsFrom": ["model_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "model_images_id": {
+ "name": "model_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "models": {
+ "name": "models",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "maker_id": {
+ "name": "maker_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "announced": {
+ "name": "announced",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions": {
+ "name": "dimensions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight": {
+ "name": "weight",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "build": {
+ "name": "build",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sim": {
+ "name": "sim",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_type": {
+ "name": "display_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size": {
+ "name": "display_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_resolution": {
+ "name": "display_resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_protection": {
+ "name": "display_protection",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "os": {
+ "name": "os",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "chipset": {
+ "name": "chipset",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu": {
+ "name": "cpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "card_slot": {
+ "name": "card_slot",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "internal_memory": {
+ "name": "internal_memory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera": {
+ "name": "main_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_features": {
+ "name": "main_camera_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "main_camera_video": {
+ "name": "main_camera_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_camera": {
+ "name": "selfie_camera",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_features": {
+ "name": "selfie_features",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "selfie_video": {
+ "name": "selfie_video",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery": {
+ "name": "battery",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "battery_charging": {
+ "name": "battery_charging",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_tech": {
+ "name": "network_tech",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sensors": {
+ "name": "sensors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors": {
+ "name": "colors",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colors_hex": {
+ "name": "colors_hex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "models_text": {
+ "name": "models_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "price": {
+ "name": "price",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_width": {
+ "name": "dimensions_width",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_height": {
+ "name": "dimensions_height",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dimensions_thickness": {
+ "name": "dimensions_thickness",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "weight_grams": {
+ "name": "weight_grams",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_inches": {
+ "name": "display_size_inches",
+ "type": "float",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_size_ratio": {
+ "name": "display_size_ratio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_width": {
+ "name": "display_res_width",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_height": {
+ "name": "display_res_height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_res_ppi": {
+ "name": "display_res_ppi",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "released": {
+ "name": "released",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta": {
+ "name": "meta",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "models_maker_id_model_makers_id_fk": {
+ "name": "models_maker_id_model_makers_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_makers",
+ "columnsFrom": ["maker_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_category_id_model_categories_id_fk": {
+ "name": "models_category_id_model_categories_id_fk",
+ "tableFrom": "models",
+ "tableTo": "model_categories",
+ "columnsFrom": ["category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "models_company_id_companies_id_fk": {
+ "name": "models_company_id_companies_id_fk",
+ "tableFrom": "models",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "models_id": {
+ "name": "models_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "slug_company_unique": {
+ "name": "slug_company_unique",
+ "columns": ["slug", "company_id"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "one_time_tokens": {
+ "name": "one_time_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ott_type": {
+ "name": "ott_type",
+ "type": "enum('confirmation','password_reset','account_deletion')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "relates_to": {
+ "name": "relates_to",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "one_time_tokens_user_id_users_id_fk": {
+ "name": "one_time_tokens_user_id_users_id_fk",
+ "tableFrom": "one_time_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "one_time_tokens_id": {
+ "name": "one_time_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "one_time_tokens_token_unique": {
+ "name": "one_time_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "refresh_tokens": {
+ "name": "refresh_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "refresh_tokens_id": {
+ "name": "refresh_tokens_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "refresh_tokens_token_unique": {
+ "name": "refresh_tokens_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "service_order_images": {
+ "name": "service_order_images",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_order_id": {
+ "name": "service_order_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_order_images_service_order_id_service_orders_id_fk": {
+ "name": "service_order_images_service_order_id_service_orders_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "service_orders",
+ "columnsFrom": ["service_order_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_order_images_employee_id_employees_id_fk": {
+ "name": "service_order_images_employee_id_employees_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_order_images_upload_id_uploads_id_fk": {
+ "name": "service_order_images_upload_id_uploads_id_fk",
+ "tableFrom": "service_order_images",
+ "tableTo": "uploads",
+ "columnsFrom": ["upload_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_order_images_id": {
+ "name": "service_order_images_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "service_orders": {
+ "name": "service_orders",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_brand_id": {
+ "name": "device_brand_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_category_id": {
+ "name": "device_category_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "device_model": {
+ "name": "device_model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imei": {
+ "name": "imei",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reported_defect": {
+ "name": "reported_defect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "observations": {
+ "name": "observations",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('pending','diagnosing','waiting_approval','approved','fixing','ready','delivered')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "service_orders_company_id_companies_id_fk": {
+ "name": "service_orders_company_id_companies_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "service_orders_client_id_clients_id_fk": {
+ "name": "service_orders_client_id_clients_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "clients",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_employee_id_employees_id_fk": {
+ "name": "service_orders_employee_id_employees_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_brand_id_model_makers_id_fk": {
+ "name": "service_orders_device_brand_id_model_makers_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_makers",
+ "columnsFrom": ["device_brand_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "service_orders_device_category_id_model_categories_id_fk": {
+ "name": "service_orders_device_category_id_model_categories_id_fk",
+ "tableFrom": "service_orders",
+ "tableTo": "model_categories",
+ "columnsFrom": ["device_category_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "service_orders_id": {
+ "name": "service_orders_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "uploads": {
+ "name": "uploads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "employee_id": {
+ "name": "employee_id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_in_bytes": {
+ "name": "size_in_bytes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploads_company_id_companies_id_fk": {
+ "name": "uploads_company_id_companies_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "companies",
+ "columnsFrom": ["company_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "uploads_employee_id_employees_id_fk": {
+ "name": "uploads_employee_id_employees_id_fk",
+ "tableFrom": "uploads",
+ "tableTo": "employees",
+ "columnsFrom": ["employee_id"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "uploads_id": {
+ "name": "uploads_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(25)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_id": {
+ "name": "google_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": ["email"]
+ },
+ "users_google_id_unique": {
+ "name": "users_google_id_unique",
+ "columns": ["google_id"]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 6e81e16..6f2e713 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -43,6 +43,48 @@
"when": 1760755210443,
"tag": "0005_faulty_mandarin",
"breakpoints": true
+ },
+ {
+ "idx": 6,
+ "version": "5",
+ "when": 1779902286658,
+ "tag": "0006_awesome_cloak",
+ "breakpoints": true
+ },
+ {
+ "idx": 7,
+ "version": "5",
+ "when": 1780260069002,
+ "tag": "0007_romantic_union_jack",
+ "breakpoints": true
+ },
+ {
+ "idx": 8,
+ "version": "5",
+ "when": 1780260191632,
+ "tag": "0008_slimy_abomination",
+ "breakpoints": true
+ },
+ {
+ "idx": 9,
+ "version": "5",
+ "when": 1780262325715,
+ "tag": "0009_ambiguous_hammerhead",
+ "breakpoints": true
+ },
+ {
+ "idx": 10,
+ "version": "5",
+ "when": 1780263000000,
+ "tag": "0010_sturdy_fulltext",
+ "breakpoints": true
+ },
+ {
+ "idx": 11,
+ "version": "5",
+ "when": 1780264072893,
+ "tag": "0011_crazy_slapstick",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/package.json b/packages/db/package.json
index abdebb7..3eb2c53 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -4,10 +4,10 @@
"type": "module",
"private": true,
"scripts": {
- "db:push": "dotenv -- drizzle-kit push",
- "db:generate": "dotenv -- drizzle-kit generate",
- "db:studio": "dotenv -- drizzle-kit studio",
- "db:migrate": "dotenv -- tsx src/migrate.ts",
+ "db:push": "bunx --bun drizzle-kit push",
+ "db:generate": "bunx --bun drizzle-kit generate",
+ "db:studio": "bunx --bun drizzle-kit studio",
+ "db:migrate": "tsx src/migrate.ts",
"db:start": "docker compose up -d",
"db:watch": "docker compose up",
"db:stop": "docker compose stop",
diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts
index 4cbaa08..4d8e44a 100644
--- a/packages/db/src/schema/index.ts
+++ b/packages/db/src/schema/index.ts
@@ -2,7 +2,9 @@ export * from "./clients";
export * from "./companies";
export * from "./employees";
export * from "./model-categories";
+export * from "./model-images";
export * from "./model-makers";
+export * from "./models";
export * from "./one-time-tokens";
export * from "./refresh-tokens";
export * from "./service-order-photos";
diff --git a/packages/db/src/schema/model-categories.ts b/packages/db/src/schema/model-categories.ts
index 9037fa4..dfbdde9 100644
--- a/packages/db/src/schema/model-categories.ts
+++ b/packages/db/src/schema/model-categories.ts
@@ -2,16 +2,16 @@ import { createId } from "@paralleldrive/cuid2";
import { mysqlTable, varchar } from "drizzle-orm/mysql-core";
import { createSelectSchema } from "drizzle-zod";
+/** @description Device categories table: groups models by type (e.g. smartphone, tablet) */
export const modelCategories = mysqlTable("model_categories", {
id: varchar("id", { length: 25 })
.$defaultFn(() => createId())
.primaryKey(),
name: varchar("name", { length: 100 }).notNull(),
+ slug: varchar("slug", { length: 100 }).notNull(),
});
+/** @description Zod schema for selecting a category record */
export const modelCategorySelectSchema = createSelectSchema(modelCategories);
-
-/** @deprecated Renamed to {@link modelCategories} */
-export const deviceCategories = modelCategories;
-/** @deprecated Renamed to {@link modelCategorySelectSchema} */
-export const deviceCategorySelectSchema = modelCategorySelectSchema;
+export type CategoryInsert = typeof modelCategories.$inferInsert;
+export type CategorySelect = typeof modelCategories.$inferSelect;
diff --git a/packages/db/src/schema/model-images.ts b/packages/db/src/schema/model-images.ts
new file mode 100644
index 0000000..081e067
--- /dev/null
+++ b/packages/db/src/schema/model-images.ts
@@ -0,0 +1,31 @@
+import { createId } from "@paralleldrive/cuid2";
+import {
+ boolean,
+ int,
+ mysqlTable,
+ timestamp,
+ varchar,
+} from "drizzle-orm/mysql-core";
+import { createSelectSchema } from "drizzle-zod";
+import { z } from "zod";
+import { models } from "./models";
+
+export const modelImages = mysqlTable("model_images", {
+ id: varchar("id", { length: 25 })
+ .$defaultFn(() => createId())
+ .primaryKey(),
+ modelId: varchar("model_id", { length: 25 })
+ .notNull()
+ .references(() => models.id),
+ r2Key: varchar("r2_key", { length: 255 }),
+ isPrimary: boolean("is_primary").notNull().default(false),
+ variant: varchar("variant", { length: 50 }),
+ position: int("position").notNull().default(0),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+});
+
+export const modelImageSelectSchema = createSelectSchema(modelImages, {
+ createdAt: z.coerce.date(),
+});
+export type ModelImageInsert = typeof modelImages.$inferInsert;
+export type ModelImageSelect = typeof modelImages.$inferSelect;
diff --git a/packages/db/src/schema/model-makers.ts b/packages/db/src/schema/model-makers.ts
index eb30ca0..3ac0249 100644
--- a/packages/db/src/schema/model-makers.ts
+++ b/packages/db/src/schema/model-makers.ts
@@ -1,14 +1,21 @@
import { createId } from "@paralleldrive/cuid2";
-import { mysqlTable, varchar } from "drizzle-orm/mysql-core";
+import { int, mysqlTable, timestamp, varchar } from "drizzle-orm/mysql-core";
import { createSelectSchema } from "drizzle-zod";
+/** @description Device makers (brands) table: stores manufacturer/brand info */
export const modelMakers = mysqlTable("model_makers", {
id: varchar("id", { length: 25 })
.$defaultFn(() => createId())
.primaryKey(),
name: varchar("name", { length: 100 }).notNull(),
+ slug: varchar("slug", { length: 100 }).notNull(),
+ url: varchar("url", { length: 255 }).notNull(),
+ deviceCount: int("device_count").notNull().default(0),
+ pageCount: int("page_count"),
+ createdAt: timestamp("created_at").notNull().defaultNow(),
});
+/** @description Zod schema for selecting a maker record */
export const modelMakerSelectSchema = createSelectSchema(modelMakers);
/** @deprecated Renamed to {@link modelMakers} */
diff --git a/packages/db/src/schema/models.ts b/packages/db/src/schema/models.ts
new file mode 100644
index 0000000..ca5860c
--- /dev/null
+++ b/packages/db/src/schema/models.ts
@@ -0,0 +1,105 @@
+import { createId } from "@paralleldrive/cuid2";
+import {
+ float,
+ index,
+ int,
+ mysqlTable,
+ text,
+ timestamp,
+ unique,
+ varchar,
+} from "drizzle-orm/mysql-core";
+import { createSelectSchema } from "drizzle-zod";
+import { z } from "zod";
+import { companies } from "./companies";
+import { modelCategories } from "./model-categories";
+import { modelMakers } from "./model-makers";
+
+export const models = mysqlTable(
+ "models",
+ {
+ id: varchar("id", { length: 25 })
+ .$defaultFn(() => createId())
+ .primaryKey(),
+ makerId: varchar("maker_id", { length: 25 })
+ .notNull()
+ .references(() => modelMakers.id),
+ name: varchar("name", { length: 255 }).notNull(),
+ slug: varchar("slug", { length: 100 }).notNull(),
+ url: varchar("url", { length: 255 }).notNull(),
+
+ categoryId: varchar("category_id", { length: 25 }).references(
+ () => modelCategories.id
+ ),
+
+ announced: text("announced"),
+ status: text("status"),
+ dimensions: text("dimensions"),
+ weight: text("weight"),
+ build: text("build"),
+ sim: text("sim"),
+ displayType: text("display_type"),
+ displaySize: text("display_size"),
+ displayResolution: text("display_resolution"),
+ displayProtection: text("display_protection"),
+ os: text("os"),
+ chipset: text("chipset"),
+ cpu: text("cpu"),
+ gpu: text("gpu"),
+ cardSlot: text("card_slot"),
+ internalMemory: text("internal_memory"),
+ mainCamera: text("main_camera"),
+ mainCameraFeatures: text("main_camera_features"),
+ mainCameraVideo: text("main_camera_video"),
+ selfieCamera: text("selfie_camera"),
+ selfieFeatures: text("selfie_features"),
+ selfieVideo: text("selfie_video"),
+ battery: text("battery"),
+ batteryCharging: text("battery_charging"),
+ networkTech: text("network_tech"),
+ sensors: text("sensors"),
+ colors: text("colors"),
+ colorsHex: text("colors_hex"),
+ modelsText: text("models_text"),
+ price: text("price"),
+ dimensionsWidth: float("dimensions_width"),
+ dimensionsHeight: float("dimensions_height"),
+ dimensionsThickness: float("dimensions_thickness"),
+ weightGrams: float("weight_grams"),
+ displaySizeInches: float("display_size_inches"),
+ displaySizeRatio: text("display_size_ratio"),
+ displayResWidth: int("display_res_width"),
+ displayResHeight: int("display_res_height"),
+ displayResPpi: int("display_res_ppi"),
+ released: text("released"),
+
+ meta: text("meta"),
+
+ companyId: varchar("company_id", { length: 25 }).references(
+ () => companies.id
+ ),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ },
+ (table) => ({
+ slugCompanyUnique: unique("slug_company_unique").on(
+ table.slug,
+ table.companyId
+ ),
+ modelsFulltextIdx: index("models_fulltext_idx")
+ .on(
+ table.name,
+ table.modelsText,
+ table.chipset,
+ table.cpu,
+ table.internalMemory,
+ table.os
+ )
+ .using("fulltext" as "btree"),
+ })
+);
+
+export const modelSelectSchema = createSelectSchema(models, {
+ createdAt: z.coerce.date(),
+});
+export type ModelInsert = typeof models.$inferInsert;
+export type ModelSelect = typeof models.$inferSelect;
diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json
index 314d12f..8ee2d72 100644
--- a/packages/db/tsconfig.json
+++ b/packages/db/tsconfig.json
@@ -10,5 +10,5 @@
"esModuleInterop": true
},
"include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
+ "exclude": ["node_modules", "dist", "src/sqlites/sqlite-db"]
}
diff --git a/packages/permissions/src/abilities.ts b/packages/permissions/src/abilities.ts
index ab0e4dc..6388c07 100644
--- a/packages/permissions/src/abilities.ts
+++ b/packages/permissions/src/abilities.ts
@@ -21,11 +21,13 @@ const roleAbilities: Record = {
permissions.serviceOrders.read,
permissions.serviceOrders.update,
permissions.serviceOrders.changeStatus,
+ permissions.devices.read,
],
warehouse: [
...baseEmployee,
permissions.inventory.read,
permissions.inventory.update,
+ permissions.devices.read,
],
financial: [
...baseEmployee,
@@ -34,6 +36,7 @@ const roleAbilities: Record = {
permissions.estimates.update,
permissions.estimates.delete,
permissions.estimates.sendToCustomer,
+ permissions.devices.read,
],
manager: [
...baseEmployee,
@@ -52,6 +55,10 @@ const roleAbilities: Record = {
permissions.estimates.sendToCustomer,
permissions.employees.read,
permissions.employees.create,
+ permissions.devices.read,
+ permissions.devices.create,
+ permissions.devices.update,
+ permissions.devices.delete,
],
admin: [
...baseEmployee,
diff --git a/packages/schemas/package.json b/packages/schemas/package.json
index b96c951..907449d 100644
--- a/packages/schemas/package.json
+++ b/packages/schemas/package.json
@@ -57,6 +57,10 @@
"./uploads": {
"types": "./src/uploads.ts",
"default": "./src/uploads.ts"
+ },
+ "./models": {
+ "types": "./src/models.ts",
+ "default": "./src/models.ts"
}
},
"devDependencies": {
diff --git a/packages/schemas/src/models.ts b/packages/schemas/src/models.ts
new file mode 100644
index 0000000..559f172
--- /dev/null
+++ b/packages/schemas/src/models.ts
@@ -0,0 +1,113 @@
+import { z } from "zod";
+import { getPaginatedDataSchema } from "./utils";
+
+/** @description Query schema for listing categories: optional name filter */
+export const getModelCategoriesQuerySchema = z.object({
+ query: z.string().optional(),
+});
+
+/** @description Params schema for getting a category by slug */
+export const getModelCategoryParamsSchema = z.object({
+ slug: z.string().min(1),
+});
+
+/** @description Query schema for listing makers: pagination, sorting, and optional name filter */
+export const getModelMakersQuerySchema = getPaginatedDataSchema.extend({
+ sort: z.enum(["newer", "older", "name", "most_devices"]).optional(),
+});
+
+/** @description Params schema for getting a maker by slug */
+export const getModelMakerParamsSchema = z.object({
+ slug: z.string().min(1),
+});
+
+/** @description Enum of valid device model release statuses */
+export const modelStatuses = z.enum([
+ "Available",
+ "Discontinued",
+ "Cancelled",
+ "Rumored",
+]);
+
+/** @description Query schema for listing models: pagination, fulltext search, filters, and sorting */
+export const getModelsQuerySchema = getPaginatedDataSchema.extend({
+ makerId: z.string().cuid2().optional(),
+ categoryId: z.string().cuid2().optional(),
+ status: modelStatuses.optional(),
+ sort: z.enum(["newer", "older", "name"]).optional(),
+});
+
+/** @description Params schema for getting a model by slug */
+export const getModelBySlugParamsSchema = z.object({
+ slug: z.string().min(1),
+});
+
+/** @description Body schema for creating a model */
+export const createModelBodySchema = z.object({
+ name: z.string().min(1).max(255),
+ makerId: z.string().min(1),
+ categoryId: z.string().optional(),
+ status: modelStatuses.optional(),
+ announced: z.string().optional(),
+ dimensions: z.string().optional(),
+ weight: z.string().optional(),
+ build: z.string().optional(),
+ sim: z.string().optional(),
+ displayType: z.string().optional(),
+ displaySize: z.string().optional(),
+ displayResolution: z.string().optional(),
+ displayProtection: z.string().optional(),
+ os: z.string().optional(),
+ chipset: z.string().optional(),
+ cpu: z.string().optional(),
+ gpu: z.string().optional(),
+ cardSlot: z.string().optional(),
+ internalMemory: z.string().optional(),
+ mainCamera: z.string().optional(),
+ mainCameraFeatures: z.string().optional(),
+ mainCameraVideo: z.string().optional(),
+ selfieCamera: z.string().optional(),
+ selfieFeatures: z.string().optional(),
+ selfieVideo: z.string().optional(),
+ battery: z.string().optional(),
+ batteryCharging: z.string().optional(),
+ networkTech: z.string().optional(),
+ sensors: z.string().optional(),
+ colors: z.string().optional(),
+ colorsHex: z.string().optional(),
+ modelsText: z.string().optional(),
+ price: z.string().optional(),
+ dimensionsWidth: z.number().optional(),
+ dimensionsHeight: z.number().optional(),
+ dimensionsThickness: z.number().optional(),
+ weightGrams: z.number().optional(),
+ displaySizeInches: z.number().optional(),
+ displaySizeRatio: z.string().optional(),
+ displayResWidth: z.number().optional(),
+ displayResHeight: z.number().optional(),
+ displayResPpi: z.number().optional(),
+ released: z.string().optional(),
+ meta: z.string().optional(),
+});
+
+/** @description Body schema for partially updating a model (all fields optional) */
+export const patchModelBodySchema = createModelBodySchema.partial();
+
+/** @description Params schema for operating on a model by its ID */
+export const modelIdParamsSchema = z.object({
+ modelId: z.string().min(1),
+});
+
+/** @description Params schema for model image operations */
+export const modelImageParamsSchema = z.object({
+ modelId: z.string().min(1),
+ imageId: z.string().min(1),
+});
+
+/** @description Body schema for assigning an uploaded image to a model */
+export const createModelImageBodySchema = z.object({
+ r2Key: z.string().min(1),
+ isPrimary: z.boolean().optional(),
+ variant: z.string().optional(),
+ position: z.number().int().optional(),
+});
diff --git a/packages/schemas/src/uploads.ts b/packages/schemas/src/uploads.ts
index a78343c..b73ddd2 100644
--- a/packages/schemas/src/uploads.ts
+++ b/packages/schemas/src/uploads.ts
@@ -30,3 +30,13 @@ export const uploadPresignResponseSchema = z.object({
url: z.string().url(),
expiresIn: z.number().int().positive(),
});
+
+export const createModelImageUploadPresignSchema = z.object({
+ fileName: z.string().min(1).max(255),
+ contentType: z
+ .string()
+ .min(1)
+ .max(255)
+ .regex(/^[^/]+\/[^/]+$/),
+ size: z.number().int().positive().max(MAX_UPLOAD_SIZE_BYTES),
+});