-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
File Backup on Quadratic Cloud (#425)
* File Backup on Quadratic API * add prisma command to Procfile * when backing up file record version and created / modified dates * add comment * send JSON response * lower file size limit * increase requests allowed to 30 per minute
- Loading branch information
1 parent
8e29669
commit 9bb2772
Showing
15 changed files
with
261 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
web: node dist/index.js | ||
web: node dist/index.js | ||
|
||
release: npx prisma migrate deploy |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
quadratic-api/prisma/migrations/20230413184442_init/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
-- CreateTable | ||
CREATE TABLE "QUser" ( | ||
"id" SERIAL NOT NULL, | ||
"auth0_user_id" TEXT, | ||
|
||
CONSTRAINT "QUser_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateTable | ||
CREATE TABLE "QFile" ( | ||
"id" SERIAL NOT NULL, | ||
"uuid" TEXT NOT NULL, | ||
"name" TEXT NOT NULL, | ||
"contents" JSONB NOT NULL, | ||
"created_date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"updated_date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"qUserId" INTEGER NOT NULL, | ||
|
||
CONSTRAINT "QFile_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "QUser_auth0_user_id_key" ON "QUser"("auth0_user_id"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "QFile_uuid_key" ON "QFile"("uuid"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "QFile" ADD CONSTRAINT "QFile_qUserId_fkey" FOREIGN KEY ("qUserId") REFERENCES "QUser"("id") ON DELETE RESTRICT ON UPDATE CASCADE; |
3 changes: 3 additions & 0 deletions
3
quadratic-api/prisma/migrations/20230413204718_add/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-- AlterTable | ||
ALTER TABLE "QFile" ADD COLUMN "times_updated" INTEGER NOT NULL DEFAULT 1, | ||
ADD COLUMN "version" TEXT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Please do not edit this file manually | ||
# It should be added in your version-control system (i.e. Git) | ||
provider = "postgresql" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// This is your Prisma schema file, | ||
// learn more about it in the docs: https://pris.ly/d/prisma-schema | ||
|
||
generator client { | ||
provider = "prisma-client-js" | ||
} | ||
|
||
datasource db { | ||
provider = "postgresql" | ||
url = env("DATABASE_URL") | ||
} | ||
|
||
// Models | ||
model QUser { | ||
id Int @id @default(autoincrement()) | ||
auth0_user_id String? @unique | ||
QFile QFile[] | ||
} | ||
|
||
model QFile { | ||
id Int @id @default(autoincrement()) | ||
uuid String @unique @default(uuid()) | ||
user_owner QUser @relation(fields: [qUserId], references: [id]) | ||
name String | ||
contents Json | ||
created_date DateTime @default(now()) | ||
updated_date DateTime @default(now()) | ||
qUserId Int | ||
// analytics fields | ||
times_updated Int @default(1) | ||
version String? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { PrismaClient, QUser } from '@prisma/client'; | ||
|
||
const prisma = new PrismaClient(); | ||
|
||
export const get_file = async (user: QUser, uuid: string) => { | ||
// Get the file from the database, only if it exists and the user owns it | ||
return await prisma.qFile.findFirst({ | ||
where: { | ||
qUserId: user.id, // important to prevent users from getting access to files they don't own | ||
uuid, | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Request as JWTRequest } from 'express-jwt'; | ||
import { PrismaClient } from '@prisma/client'; | ||
|
||
const prisma = new PrismaClient(); | ||
|
||
export const get_user = async (request: JWTRequest) => { | ||
return await prisma.qUser.upsert({ | ||
where: { | ||
auth0_user_id: request.auth?.sub, | ||
}, | ||
update: {}, | ||
create: { | ||
auth0_user_id: request.auth?.sub, | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export const get_file_metadata = (file_contents: any) => { | ||
try { | ||
return { | ||
name: file_contents.filename, | ||
version: file_contents.version, | ||
modified: file_contents.modified, | ||
created: file_contents.created, | ||
}; | ||
} catch (e) { | ||
console.error(e); | ||
return { | ||
name: undefined, | ||
version: undefined, | ||
modified: undefined, | ||
created: undefined, | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import express from 'express'; | ||
import { validateAccessToken } from '../middleware/auth'; | ||
import { Request as JWTRequest } from 'express-jwt'; | ||
import { z } from 'zod'; | ||
import rateLimit from 'express-rate-limit'; | ||
import { PrismaClient } from '@prisma/client'; | ||
import { get_user } from '../helpers/get_user'; | ||
import { get_file } from '../helpers/get_file'; | ||
import { get_file_metadata } from '../helpers/read_file'; | ||
|
||
const files_router = express.Router(); | ||
const prisma = new PrismaClient(); | ||
|
||
const ai_rate_limiter = rateLimit({ | ||
windowMs: 60 * 1000, // 1 minute | ||
max: 30, // Limit number of requests per windowMs | ||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers | ||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers | ||
keyGenerator: (request: JWTRequest, response) => { | ||
return request.auth?.sub || 'anonymous'; | ||
}, | ||
}); | ||
|
||
const FilesBackupRequestBody = z.object({ | ||
uuid: z.string(), | ||
fileContents: z.any(), | ||
}); | ||
|
||
// type FilesBackupRequestBodyType = z.infer<typeof FilesBackupRequestBody>; | ||
|
||
files_router.post('/backup', validateAccessToken, ai_rate_limiter, async (request: JWTRequest, response) => { | ||
const r_json = FilesBackupRequestBody.parse(request.body); | ||
|
||
const user = await get_user(request); | ||
const file = await get_file(user, r_json.uuid); | ||
|
||
const file_contents = JSON.parse(r_json.fileContents); | ||
const file_metadata = get_file_metadata(file_contents); | ||
|
||
if (file) { | ||
await prisma.qFile.update({ | ||
where: { | ||
id: file.id, | ||
}, | ||
data: { | ||
name: file_metadata.name, | ||
contents: file_contents, | ||
updated_date: new Date(file_metadata.modified), | ||
version: file_metadata.version, | ||
times_updated: { | ||
increment: 1, | ||
}, | ||
}, | ||
}); | ||
} else { | ||
await prisma.qFile.create({ | ||
data: { | ||
qUserId: user.id, | ||
uuid: r_json.uuid, | ||
name: file_metadata.name, | ||
contents: file_contents, | ||
created_date: new Date(file_metadata.created), | ||
updated_date: new Date(file_metadata.modified), | ||
version: file_metadata.version, | ||
}, | ||
}); | ||
} | ||
|
||
response.status(200).json({ message: 'File backup successful.' }); | ||
}); | ||
|
||
export default files_router; |
Oops, something went wrong.