diff --git a/.env.example b/.env.example index 912f716..35de7ff 100644 --- a/.env.example +++ b/.env.example @@ -10,8 +10,10 @@ FLATFILE_API_KEY='sk_...' FLATFILE_ENVIRONMENT_ID='us_env_...' FLATFILE_NAMESPACE='plmproject' NEXT_PUBLIC_FLATFILE_NAMESPACE='plmproject' -FLATFILE_SERVICES_NAMESPACE='servicesproject' NEXT_PUBLIC_FLATFILE_PUBLISHABLE_KEY='pk_...' NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID='us_env_...' NEXT_PUBLIC_APP_ID='products-show' -LISTENER_AUTH_TOKEN='token' \ No newline at end of file +LISTENER_AUTH_TOKEN='token' +NEXT_PUBLIC_GOOGLE_DRIVE_FILE_ID='1mUeqmEUvYzqHPxOo0OUS_My4oJTzo-RX' +GOOGLE_DRIVE_FILE_ID='1mUeqmEUvYzqHPxOo0OUS_My4oJTzo-RX' +GOOGLE_DRIVE_API_KEY='...' \ No newline at end of file diff --git a/app/(authenticated)/file-feed/[spaceId]/workspace.tsx b/app/(authenticated)/file-feed/[spaceId]/workspace.tsx index 9dae5c9..74150a8 100644 --- a/app/(authenticated)/file-feed/[spaceId]/workspace.tsx +++ b/app/(authenticated)/file-feed/[spaceId]/workspace.tsx @@ -66,7 +66,7 @@ export default function Workspace({ here. diff --git a/app/api/create-space/route.ts b/app/api/create-space/route.ts index a688d86..317158f 100644 --- a/app/api/create-space/route.ts +++ b/app/api/create-space/route.ts @@ -1,5 +1,8 @@ import { authenticatedRoute } from "@/lib/api-helpers"; +import { fetchFileFromDrive } from "@/lib/google-drive"; +import { FlatfileService } from "@/lib/services/flatfile"; import { SpaceService } from "@/lib/services/space"; +import { WorkflowType } from "@/lib/workflow-type"; import { NextRequest, NextResponse } from "next/server"; import invariant from "ts-invariant"; @@ -11,16 +14,27 @@ export const POST = async (request: NextRequest, context: { params: any }) => { const userId = context.user.id; + let space; + try { - const space = await SpaceService.createSpace({ + space = await SpaceService.createSpace({ workflowType: json.workflowType, userId, spaceName: json.spaceName, }); - return NextResponse.json({ spaceId: space.id }, { status: 201 }); } catch (e) { console.error(`Error creating space for ${userId}`, e); return new NextResponse("Error creating space", { status: 500 }); } + + if (space.workflowType === WorkflowType.FileFeed) { + const file = await fetchFileFromDrive(); + await FlatfileService.postFileToSpace({ + flatfileSpaceId: space.flatfileSpaceId, + file, + }); + } + + return NextResponse.json({ spaceId: space.id }, { status: 201 }); }); }; diff --git a/global.d.ts b/global.d.ts index 0e39e20..ca4a7e1 100644 --- a/global.d.ts +++ b/global.d.ts @@ -3,7 +3,7 @@ namespace NodeJS { interface ProcessEnv { NEXT_PUBLIC_FLATFILE_PUBLISHABLE_KEY: string; NEXT_PUBLIC_FLATFILE_ENVIRONMENT_ID: string; + FLATFILE_ENVIRONMENT_ID: string; FLATFILE_NAMESPACE: string; - FLATFILE_SERVICES_NAMESPACE: string; } } diff --git a/lib/google-drive.ts b/lib/google-drive.ts new file mode 100644 index 0000000..ca0960d --- /dev/null +++ b/lib/google-drive.ts @@ -0,0 +1,40 @@ +import fs from "fs"; +import tmp from "tmp-promise"; + +const { google } = require("googleapis"); + +export const fetchFileFromDrive = async (): Promise => { + const service = google.drive({ + version: "v3", + auth: process.env.GOOGLE_DRIVE_API_KEY, + }); + + let file; + + try { + const response = await service.files.get({ + fileId: process.env.GOOGLE_DRIVE_FILE_ID, + alt: "media", + }); + + // Create a temporary file and write the data into it + const tmpFile = await tmp.file({ + prefix: "products-sample-data-", + postfix: ".csv", + }); + fs.writeFileSync(tmpFile.path, response.data); + + // Create a fs.ReadStream from the temporary file + file = fs.createReadStream(tmpFile.path); + + // When you're done with the file, close and unlink it + file.on("close", () => { + tmpFile.cleanup(); + }); + + return file; + } catch (err) { + console.log("fetchFileFromDrive error: ", err); + throw err; + } +}; diff --git a/lib/services/flatfile.ts b/lib/services/flatfile.ts index 38f38aa..70d97a8 100644 --- a/lib/services/flatfile.ts +++ b/lib/services/flatfile.ts @@ -1,21 +1,20 @@ import api from "@flatfile/api"; import { RecordDataWithLinks } from "@flatfile/api/api"; +import { ReadStream } from "fs"; export class FlatfileService { static createSpace = async ({ userId, - flatfileNamespace, spaceName, }: { userId: string; - flatfileNamespace: string; spaceName: string; }) => { const { data } = await api.spaces.create({ name: spaceName, environmentId: process.env.FLATFILE_ENVIRONMENT_ID, autoConfigure: true, - namespace: flatfileNamespace, + namespace: process.env.FLATFILE_NAMESPACE, metadata: { userId, }, @@ -99,4 +98,17 @@ export class FlatfileService { return records; } + + static async postFileToSpace({ + flatfileSpaceId, + file, + }: { + flatfileSpaceId: string; + file: ReadStream; + }) { + return await api.files.upload(file, { + spaceId: flatfileSpaceId, + environmentId: process.env.FLATFILE_ENVIRONMENT_ID, + }); + } } diff --git a/lib/services/space.ts b/lib/services/space.ts index b1cb366..ae21fdd 100644 --- a/lib/services/space.ts +++ b/lib/services/space.ts @@ -1,6 +1,5 @@ import { prismaClient } from "@/lib/prisma-client"; import { FlatfileService } from "./flatfile"; -import { WorkflowType } from "@/lib/workflow-type"; export class SpaceService { static async createSpace({ @@ -12,19 +11,9 @@ export class SpaceService { userId: string; spaceName: string; }) { - let flatfileNamespace; - - // TODO: Adjust for other workflows - if (workflowType === WorkflowType.FileFeed) { - flatfileNamespace = process.env.FLATFILE_SERVICES_NAMESPACE; - } else { - flatfileNamespace = process.env.FLATFILE_NAMESPACE; - } - const flatfileSpace = await FlatfileService.createSpace({ userId, spaceName, - flatfileNamespace, }); return await prismaClient.space.create({ diff --git a/package-lock.json b/package-lock.json index 185346e..0e8976d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "googleapis": "^134.0.0", "lucide-react": "^0.359.0", "luxon": "^3.4.4", "next": "14.1.4", @@ -42,6 +43,7 @@ "react-hook-form": "^7.51.1", "react-inlinesvg": "^4.1.3", "tailwind-merge": "^2.2.2", + "tmp-promise": "^3.0.3", "ts-invariant": "^0.10.3", "zod": "^3.22.4" }, @@ -2656,6 +2658,14 @@ "node": ">= 10.0.0" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2744,6 +2754,11 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -3282,6 +3297,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.711", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.711.tgz", @@ -3931,6 +3954,11 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4275,6 +4303,68 @@ "node": ">=8" } }, + "node_modules/gaxios": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.4.0.tgz", + "integrity": "sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -4452,6 +4542,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.7.0.tgz", + "integrity": "sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "134.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-134.0.0.tgz", + "integrity": "sha512-o8LhD1754W6MHWtpwAPeP1WUHgNxuMxCnLMDFlMKAA5kCMTNqX9/eaTXnkkAIv6YRfoKMQ6D1vyR6/biXuhE9g==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.1.0.tgz", + "integrity": "sha512-p3KHiWDBBWJEXk6SYauBEvxw5+UmRy7k2scxGtsNv9eHsTbpopJ3/7If4OrNnzJ9XMLg3IlyQXpVp8YPQsStiw==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -4474,6 +4620,18 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -5007,6 +5165,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -5179,6 +5348,14 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5235,6 +5412,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7403,6 +7599,22 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7698,6 +7910,11 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/use-callback-ref": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", diff --git a/package.json b/package.json index 68bb988..861984a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "googleapis": "^134.0.0", "lucide-react": "^0.359.0", "luxon": "^3.4.4", "next": "14.1.4", @@ -48,6 +49,7 @@ "react-hook-form": "^7.51.1", "react-inlinesvg": "^4.1.3", "tailwind-merge": "^2.2.2", + "tmp-promise": "^3.0.3", "ts-invariant": "^0.10.3", "zod": "^3.22.4" }, diff --git a/preflight.js b/preflight.js index 3872dc3..19375e0 100644 --- a/preflight.js +++ b/preflight.js @@ -2,7 +2,6 @@ VARS = [ "DATABASE_URL", "NEXTAUTH_SECRET", "FLATFILE_NAMESPACE", - "FLATFILE_SERVICES_NAMESPACE", "FLATFILE_API_KEY", "FLATFILE_ENVIRONMENT_ID", "NEXT_PUBLIC_FLATFILE_PUBLISHABLE_KEY", @@ -10,6 +9,9 @@ VARS = [ "NEXT_PUBLIC_APP_ID", "LISTENER_AUTH_TOKEN", "NEXT_PUBLIC_FLATFILE_NAMESPACE", + "GOOGLE_DRIVE_FILE_ID", + "NEXT_PUBLIC_GOOGLE_DRIVE_FILE_ID", + "GOOGLE_DRIVE_API_KEY", ]; function preflight() {