Merge branch 'main' into feat-combobox-2

This commit is contained in:
Oleg Isonen authored and GitHub committed 2022-07-22 22:21:01 +02:00
commit 0eaf8350b5
14 files changed
+200 -26

No files matched your search

+11 -1
View File
@@ -6,4 +6,14 @@ DEV_LOGIN=true
# relative to the public directory
FILE_UPLOAD_PATH="uploads"
FILE_UPLOAD_PATH="uploads"
# S3 envs
S3_BUCKET=
S3_REGION=
S3_ENDPOINT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
# if using r2 this your worker url
WORKER_URL=
@@ -2,8 +2,8 @@ import { ImageIcon } from "~/shared/icons";
import { Button, Flex, Grid, Heading } from "~/shared/design-system";
import { useRef } from "react";
import { Form, useSubmit } from "@remix-run/react";
import { type Asset } from "@webstudio-is/prisma-client";
import { Image } from "~/shared/design-system/components/image";
import { Asset } from "@webstudio-is/prisma-client";
export const TabContent = ({ assets }: { assets: Array<Asset> }) => {
const inputRef = useRef<HTMLInputElement | null>(null);
+9 -4
View File
@@ -1,16 +1,21 @@
import { prisma, Project } from "@webstudio-is/prisma-client";
import { type Location, prisma, Project } from "@webstudio-is/prisma-client";
import sharp from "sharp";
export const create = async (
projectId: Project["id"],
values: { name: string; path: string; size: number; metadata: sharp.Metadata }
values: {
name: string;
size: number;
metadata: sharp.Metadata;
location: Location;
}
) => {
const size = values.size || values.metadata.size || 0;
const { metadata, name, path } = values;
const { metadata, name, location } = values;
const newAsset = await prisma.asset.create({
data: {
location,
name,
path,
size,
format: metadata.format,
...(metadata.width ? { width: metadata.width } : {}),
+9 -3
View File
@@ -1,6 +1,9 @@
import { prisma, Project } from "@webstudio-is/prisma-client";
import { prisma, Project, Asset } from "@webstudio-is/prisma-client";
import { getAssetPath } from "../helpers/get-asset-path";
export const loadByProject = async (projectId?: Project["id"]) => {
export const loadByProject = async (
projectId?: Project["id"]
): Promise<Asset[]> => {
if (typeof projectId !== "string") {
throw new Error("Tree ID required");
}
@@ -12,5 +15,8 @@ export const loadByProject = async (projectId?: Project["id"]) => {
},
});
return assets;
return assets.map((asset) => ({
...asset,
path: getAssetPath(asset),
}));
};
@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Decimal } from "@prisma/client/runtime";
import { Location } from "@webstudio-is/prisma-client";
const commonAsset = {
id: "sa-546",
width: new Decimal(200),
height: new Decimal(200),
projectId: "id",
size: 2135,
format: "png",
createdAt: new Date(),
alt: "",
};
describe("getAssetPath", () => {
const OLD_ENV = process.env;
beforeEach(() => {
jest.resetModules();
process.env = {
...OLD_ENV,
S3_ACCESS_KEY_ID: "testid",
S3_SECRET_ACCESS_KEY: "testkey",
WORKER_URL: "",
};
});
afterAll(() => {
process.env = OLD_ENV;
});
describe("local", () => {
test("returns local path with no custom path", () => {
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
...commonAsset,
name: "test.png",
location: Location.FS,
})
).toBe("/uploads/test.png");
});
test("return local path with custom path", () => {
process.env.FILE_UPLOAD_PATH = "assets";
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
...commonAsset,
name: "test.png",
location: Location.FS,
})
).toBe("/assets/test.png");
});
});
describe("s3", () => {
test("returns s3 url", () => {
process.env.S3_ENDPOINT = "https://fr1.s3.com";
process.env.S3_BUCKET = "bucket";
process.env.S3_REGION = "fr1";
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
...commonAsset,
name: "test.png",
location: Location.REMOTE,
})
).toBe("https://bucket.fr1.s3.com/test.png");
});
test("returns s3 url and fixes extra slashes", () => {
process.env.S3_ENDPOINT = "https://fr1.s3.com//";
process.env.S3_BUCKET = "bucket";
process.env.S3_REGION = "fr1";
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
...commonAsset,
name: "test.png",
location: Location.REMOTE,
})
).toBe("https://bucket.fr1.s3.com/test.png");
});
});
describe("R2", () => {
test("returns r2 url", () => {
process.env.S3_ENDPOINT = "https://userid.r2.cloudflarestorage.com";
process.env.S3_BUCKET = "bucket";
process.env.S3_REGION = "auto";
process.env.WORKER_URL = "https://worker-test.workers.dev";
const { getAssetPath } = require("./get-asset-path");
expect(
getAssetPath({
...commonAsset,
name: "test.png",
location: Location.REMOTE,
})
).toBe("https://worker-test.workers.dev/test.png");
});
});
});
@@ -0,0 +1,27 @@
import { DbAsset, Location } from "@webstudio-is/prisma-client";
import { fsEnvVariables, s3EnvVariables } from "../schema";
import path from "path";
const s3Envs = s3EnvVariables.safeParse(process.env);
const fsEnvs = fsEnvVariables.parse(process.env);
export const getAssetPath = (asset: DbAsset) => {
if (asset.location === Location.FS) {
return path.join("/", fsEnvs.FILE_UPLOAD_PATH, asset.name);
}
if (asset.location === Location.REMOTE && s3Envs.success) {
const s3Url = new URL(s3Envs.data.S3_ENDPOINT);
if (s3Envs.data.WORKER_URL) {
const r2Url = new URL(s3Envs.data.WORKER_URL);
r2Url.pathname = asset.name;
return r2Url.toString();
}
s3Url.hostname = `${s3Envs.data.S3_BUCKET}.${s3Url.hostname}`;
s3Url.pathname = asset.name;
return s3Url.toString();
}
return "";
};
+1
View File
@@ -21,6 +21,7 @@ export const s3EnvVariables = z.object({
S3_SECRET_ACCESS_KEY: z.string(),
S3_BUCKET: z.string(),
S3_ACL: z.string().optional().default("public-read"),
WORKER_URL: z.string().optional(),
});
export const fsEnvVariables = z.object({
@@ -1,4 +1,4 @@
import path from "path";
import { Location } from "@webstudio-is/prisma-client";
import sharp from "sharp";
import { create } from "../../";
import { ImagesUpload } from "../../schema";
@@ -6,11 +6,9 @@ import { ImagesUpload } from "../../schema";
export const uploadToDisk = async ({
formData,
projectId,
folderInPublic,
}: {
formData: FormData;
projectId: string;
folderInPublic: string;
}) => {
const imagesInfo = ImagesUpload.parse(formData.getAll("image"));
const allInfo = imagesInfo.map(async (image) => {
@@ -21,9 +19,9 @@ export const uploadToDisk = async ({
const data = {
name: image.name,
path: path.join("/", folderInPublic, image.name),
size: image.size,
metadata,
location: Location.FS,
};
const newAsset = await create(projectId, data);
@@ -13,6 +13,7 @@ import {
getArrayBufferFromIterable,
getFilenameAndExtension,
} from "../../helpers/array-buffer-helpers";
import { Location } from "@webstudio-is/prisma-client";
type S3UploadHandler = ({
file,
@@ -40,11 +41,14 @@ export const s3UploadHandler: S3UploadHandler = async ({
}
const [name, extension] = getFilenameAndExtension(filename);
const key = `${name}_${Date.now()}.${extension}`;
const key = encodeURI(`${name}_${Date.now()}.${extension}`);
// r2 does not have ACL support so in that case we pass nothing there
const ACL = s3Envs.WORKER_URL ? {} : { ACL: s3Envs.S3_ACL };
const params: PutObjectCommandInput = {
Bucket: s3Envs.S3_BUCKET,
ACL: s3Envs.S3_ACL,
...ACL,
Key: key,
Body: uint8Array,
ContentType: contentType,
@@ -64,7 +68,7 @@ export const s3UploadHandler: S3UploadHandler = async ({
const upload = new Upload({ client, params });
const newFile = ImagesUploadedSuccess.parse(await upload.done());
ImagesUploadedSuccess.parse(await upload.done());
const image = sharp(uint8Array);
const metadata = await image.metadata();
@@ -72,6 +76,6 @@ export const s3UploadHandler: S3UploadHandler = async ({
return JSON.stringify({
metadata,
name: key,
path: newFile.Location,
location: Location.REMOTE,
});
};
-1
View File
@@ -49,7 +49,6 @@ export const uploadAssets = async ({
return await uploadToDisk({
projectId,
formData,
folderInPublic: fsUploadVars.FILE_UPLOAD_PATH,
});
}
};
@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `path` on the `Asset` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Asset" DROP COLUMN "path";
@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `location` to the `Asset` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "Location" AS ENUM ('FS', 'REMOTE');
-- AlterTable
ALTER TABLE "Asset" ADD COLUMN "location" "Location" NOT NULL;
+6 -1
View File
@@ -15,6 +15,11 @@ model Team {
users User[]
}
enum Location {
FS
REMOTE
}
model Asset {
id String @id @default(uuid())
project Project @relation(fields: [projectId], references: [id])
@@ -24,8 +29,8 @@ model Asset {
width Decimal?
height Decimal?
name String
path String
alt String?
location Location
createdAt DateTime @default(now())
}
+6 -7
View File
@@ -1,7 +1,6 @@
export type {
InstanceProps,
Project,
User,
Breakpoints,
Asset,
} from "@prisma/client";
import type { Asset as DbAsset } from "@prisma/client";
export { Location } from "@prisma/client";
export type { InstanceProps, Project, User, Breakpoints } from "@prisma/client";
export type Asset = DbAsset & { path: string };
export type { DbAsset };