massive refactor toDrop cascadeStudio and add CadQuery + OpenSCAD
resolves #400
This commit is contained in:
0
app/api/db/dataMigrations/.keep
Normal file
0
app/api/db/dataMigrations/.keep
Normal file
@@ -0,0 +1,9 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "RW_DataMigration" (
|
||||
"version" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL,
|
||||
"finishedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("version")
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Comment` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Part` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `PartReaction` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Comment" DROP CONSTRAINT "Comment_partId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Comment" DROP CONSTRAINT "Comment_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Part" DROP CONSTRAINT "Part_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_partId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PartReaction" DROP CONSTRAINT "PartReaction_userId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Comment";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Part";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "PartReaction";
|
||||
@@ -0,0 +1,59 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Project" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" VARCHAR(25) NOT NULL,
|
||||
"description" TEXT,
|
||||
"code" TEXT,
|
||||
"mainImage" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"deleted" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProjectReaction" (
|
||||
"id" TEXT NOT NULL,
|
||||
"emote" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Comment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Project.title_userId_unique" ON "Project"("title", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ProjectReaction.emote_userId_projectId_unique" ON "ProjectReaction"("emote", "userId", "projectId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Project" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectReaction" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectReaction" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CadPackage" AS ENUM ('openscad', 'cadquery');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "cadPackage" "CadPackage" NOT NULL DEFAULT E'openscad';
|
||||
@@ -1,2 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -14,8 +14,7 @@ generator client {
|
||||
// ADMIN
|
||||
// }
|
||||
|
||||
// enum PartType {
|
||||
// CASCADESTUDIO
|
||||
// enum ProjectType {
|
||||
// JSCAD
|
||||
// }
|
||||
|
||||
@@ -33,15 +32,20 @@ model User {
|
||||
|
||||
image String? // url maybe id or file storage service? cloudinary?
|
||||
bio String? //mark down
|
||||
Part Part[]
|
||||
Reaction PartReaction[]
|
||||
Project Project[]
|
||||
Reaction ProjectReaction[]
|
||||
Comment Comment[]
|
||||
SubjectAccessRequest SubjectAccessRequest[]
|
||||
}
|
||||
|
||||
model Part {
|
||||
enum CadPackage {
|
||||
openscad
|
||||
cadquery
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
title String @db.VarChar(25)
|
||||
description String? // markdown string
|
||||
code String?
|
||||
mainImage String? // link to cloudinary
|
||||
@@ -50,23 +54,24 @@ model Part {
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
deleted Boolean @default(false)
|
||||
cadPackage CadPackage @default(openscad)
|
||||
|
||||
Comment Comment[]
|
||||
Reaction PartReaction[]
|
||||
Reaction ProjectReaction[]
|
||||
@@unique([title, userId])
|
||||
}
|
||||
|
||||
model PartReaction {
|
||||
model ProjectReaction {
|
||||
id String @id @default(uuid())
|
||||
emote String // an emoji
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
part Part @relation(fields: [partId], references: [id])
|
||||
partId String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
projectId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@unique([emote, userId, partId])
|
||||
@@unique([emote, userId, projectId])
|
||||
}
|
||||
|
||||
model Comment {
|
||||
@@ -74,8 +79,8 @@ model Comment {
|
||||
text String // the comment, should I allow mark down?
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
part Part @relation(fields: [partId], references: [id])
|
||||
partId String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
projectId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -91,3 +96,10 @@ model SubjectAccessRequest {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model RW_DataMigration {
|
||||
version String @id
|
||||
name String
|
||||
startedAt DateTime
|
||||
finishedAt DateTime
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ async function main() {
|
||||
})
|
||||
}
|
||||
|
||||
const parts = [
|
||||
const projects = [
|
||||
{
|
||||
title: 'demo-part1',
|
||||
title: 'demo-project1',
|
||||
description: '# can be markdown',
|
||||
mainImage: 'CadHub/kjdlgjnu0xmwksia7xox',
|
||||
user: {
|
||||
@@ -62,7 +62,7 @@ async function main() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'demo-part2',
|
||||
title: 'demo-project2',
|
||||
description: '## [hey](www.google.com)',
|
||||
user: {
|
||||
connect: {
|
||||
@@ -72,39 +72,43 @@ async function main() {
|
||||
},
|
||||
]
|
||||
|
||||
existing = await db.part.findMany({where: { title: parts[0].title}})
|
||||
existing = await db.project.findMany({where: { title: projects[0].title}})
|
||||
if(!existing.length) {
|
||||
await db.part.create({
|
||||
data: parts[0],
|
||||
await db.project.create({
|
||||
data: projects[0],
|
||||
})
|
||||
}
|
||||
existing = await db.part.findMany({where: { title: parts[1].title}})
|
||||
existing = await db.project.findMany({where: { title: projects[1].title}})
|
||||
if(!existing.length) {
|
||||
await db.part.create({
|
||||
data: parts[1],
|
||||
await db.project.create({
|
||||
data: projects[1],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const aPart = await db.part.findUnique({where: {
|
||||
const aProject = await db.project.findUnique({where: {
|
||||
title_userId: {
|
||||
title: parts[0].title,
|
||||
title: projects[0].title,
|
||||
userId: users[0].id,
|
||||
}
|
||||
}})
|
||||
await db.comment.create({
|
||||
data: {
|
||||
text: "nice part, I like it",
|
||||
user: {connect: { id: users[0].id}},
|
||||
part: {connect: { id: aPart.id}},
|
||||
text: "nice project, I like it",
|
||||
userId: users[0].id,
|
||||
projectId: aProject.id,
|
||||
// user: {connect: { id: users[0].id}},
|
||||
// project: {connect: { id: aProject.id}},
|
||||
}
|
||||
})
|
||||
await db.partReaction.create({
|
||||
await db.projectReaction.create({
|
||||
data: {
|
||||
emote: "❤️",
|
||||
user: {connect: { id: users[0].id}},
|
||||
part: {connect: { id: aPart.id}},
|
||||
userId: users[0].id,
|
||||
projectId: aProject.id,
|
||||
// user: {connect: { id: users[0].id}},
|
||||
// project: {connect: { id: aProject.id}},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"@redwoodjs/api": "^0.34.1",
|
||||
"@sentry/node": "^6.5.1",
|
||||
"cloudinary": "^1.23.0",
|
||||
"human-id": "^2.0.1",
|
||||
"nodemailer": "^6.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
39
app/api/src/graphql/ProjectReactions.sdl.js
Normal file
39
app/api/src/graphql/ProjectReactions.sdl.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export const schema = gql`
|
||||
type ProjectReaction {
|
||||
id: String!
|
||||
emote: String!
|
||||
user: User!
|
||||
userId: String!
|
||||
project: Project!
|
||||
projectId: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Query {
|
||||
projectReactions: [ProjectReaction!]!
|
||||
projectReaction(id: String!): ProjectReaction
|
||||
projectReactionsByProjectId(projectId: String!): [ProjectReaction!]!
|
||||
}
|
||||
|
||||
input ToggleProjectReactionInput {
|
||||
emote: String!
|
||||
userId: String!
|
||||
projectId: String!
|
||||
}
|
||||
|
||||
input UpdateProjectReactionInput {
|
||||
emote: String
|
||||
userId: String
|
||||
projectId: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
toggleProjectReaction(input: ToggleProjectReactionInput!): ProjectReaction!
|
||||
updateProjectReaction(
|
||||
id: String!
|
||||
input: UpdateProjectReactionInput!
|
||||
): ProjectReaction!
|
||||
deleteProjectReaction(id: String!): ProjectReaction!
|
||||
}
|
||||
`
|
||||
@@ -4,8 +4,8 @@ export const schema = gql`
|
||||
text: String!
|
||||
user: User!
|
||||
userId: String!
|
||||
part: Part!
|
||||
partId: String!
|
||||
project: Project!
|
||||
projectId: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
@@ -18,13 +18,13 @@ export const schema = gql`
|
||||
input CreateCommentInput {
|
||||
text: String!
|
||||
userId: String!
|
||||
partId: String!
|
||||
projectId: String!
|
||||
}
|
||||
|
||||
input UpdateCommentInput {
|
||||
text: String
|
||||
userId: String
|
||||
partId: String
|
||||
projectId: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
export const schema = gql`
|
||||
type PartReaction {
|
||||
id: String!
|
||||
emote: String!
|
||||
user: User!
|
||||
userId: String!
|
||||
part: Part!
|
||||
partId: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Query {
|
||||
partReactions: [PartReaction!]!
|
||||
partReaction(id: String!): PartReaction
|
||||
partReactionsByPartId(partId: String!): [PartReaction!]!
|
||||
}
|
||||
|
||||
input TogglePartReactionInput {
|
||||
emote: String!
|
||||
userId: String!
|
||||
partId: String!
|
||||
}
|
||||
|
||||
input UpdatePartReactionInput {
|
||||
emote: String
|
||||
userId: String
|
||||
partId: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
togglePartReaction(input: TogglePartReactionInput!): PartReaction!
|
||||
updatePartReaction(
|
||||
id: String!
|
||||
input: UpdatePartReactionInput!
|
||||
): PartReaction!
|
||||
deletePartReaction(id: String!): PartReaction!
|
||||
}
|
||||
`
|
||||
@@ -1,45 +0,0 @@
|
||||
export const schema = gql`
|
||||
type Part {
|
||||
id: String!
|
||||
title: String!
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
deleted: Boolean!
|
||||
user: User!
|
||||
userId: String!
|
||||
Comment: [Comment]!
|
||||
Reaction(userId: String): [PartReaction]!
|
||||
}
|
||||
|
||||
type Query {
|
||||
parts(userName: String): [Part!]!
|
||||
part(id: String!): Part
|
||||
partByUserAndTitle(userName: String!, partTitle: String!): Part
|
||||
}
|
||||
|
||||
input CreatePartInput {
|
||||
title: String!
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
userId: String!
|
||||
}
|
||||
|
||||
input UpdatePartInput {
|
||||
title: String
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
userId: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createPart(input: CreatePartInput!): Part!
|
||||
forkPart(input: CreatePartInput!): Part!
|
||||
updatePart(id: String!, input: UpdatePartInput!): Part!
|
||||
deletePart(id: String!): Part!
|
||||
}
|
||||
`
|
||||
52
app/api/src/graphql/projects.sdl.ts
Normal file
52
app/api/src/graphql/projects.sdl.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export const schema = gql`
|
||||
type Project {
|
||||
id: String!
|
||||
title: String!
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
user: User!
|
||||
userId: String!
|
||||
deleted: Boolean!
|
||||
cadPackage: CadPackage!
|
||||
Comment: [Comment]!
|
||||
Reaction(userId: String): [ProjectReaction]!
|
||||
}
|
||||
|
||||
enum CadPackage {
|
||||
openscad
|
||||
cadquery
|
||||
}
|
||||
|
||||
type Query {
|
||||
projects(userName: String): [Project!]!
|
||||
project(id: String!): Project
|
||||
projectByUserAndTitle(userName: String!, projectTitle: String!): Project
|
||||
}
|
||||
|
||||
input CreateProjectInput {
|
||||
title: String
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
userId: String!
|
||||
cadPackage: CadPackage!
|
||||
}
|
||||
|
||||
input UpdateProjectInput {
|
||||
title: String
|
||||
description: String
|
||||
code: String
|
||||
mainImage: String
|
||||
userId: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createProject(input: CreateProjectInput!): Project!
|
||||
forkProject(input: CreateProjectInput!): Project!
|
||||
updateProject(id: String!, input: UpdateProjectInput!): Project!
|
||||
deleteProject(id: String!): Project!
|
||||
}
|
||||
`
|
||||
@@ -8,9 +8,9 @@ export const schema = gql`
|
||||
updatedAt: DateTime!
|
||||
image: String
|
||||
bio: String
|
||||
Parts: [Part]!
|
||||
Part(partTitle: String): Part
|
||||
Reaction: [PartReaction]!
|
||||
Projects: [Project]!
|
||||
Project(projectTitle: String): Project
|
||||
Reaction: [ProjectReaction]!
|
||||
Comment: [Comment]!
|
||||
SubjectAccessRequest: [SubjectAccessRequest]!
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { AuthenticationError, ForbiddenError } from '@redwoodjs/api'
|
||||
import { db } from 'src/lib/db'
|
||||
|
||||
export const requireOwnership = async ({ userId, userName, partId } = {}) => {
|
||||
export const requireOwnership = async ({
|
||||
userId,
|
||||
userName,
|
||||
projectId,
|
||||
}: { userId?: string; userName?: string; projectId?: string } = {}) => {
|
||||
// IMPORTANT, don't forget to await this function, as it will only block
|
||||
// unwanted db actions if it has time to look up resources in the db.
|
||||
if (!context.currentUser) {
|
||||
throw new AuthenticationError("You don't have permission to do that.")
|
||||
}
|
||||
if (!userId && !userName && !partId) {
|
||||
if (!userId && !userName && !projectId) {
|
||||
throw new ForbiddenError("You don't have access to do that.")
|
||||
}
|
||||
|
||||
@@ -33,10 +37,10 @@ export const requireOwnership = async ({ userId, userName, partId } = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (partId) {
|
||||
const user = await db.part
|
||||
if (projectId) {
|
||||
const user = await db.project
|
||||
.findUnique({
|
||||
where: { id: partId },
|
||||
where: { id: projectId },
|
||||
})
|
||||
.user()
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
import { comments } from './comments'
|
||||
*/
|
||||
|
||||
describe('comments', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -33,6 +33,6 @@ export const deleteComment = ({ id }) => {
|
||||
export const Comment = {
|
||||
user: (_obj, { root }) =>
|
||||
db.comment.findUnique({ where: { id: root.id } }).user(),
|
||||
part: (_obj, { root }) =>
|
||||
db.comment.findUnique({ where: { id: root.id } }).part(),
|
||||
project: (_obj, { root }) =>
|
||||
db.comment.findUnique({ where: { id: root.id } }).project(),
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { v2 as cloudinary } from 'cloudinary'
|
||||
import humanId from 'human-id'
|
||||
|
||||
cloudinary.config({
|
||||
cloud_name: 'irevdev',
|
||||
api_key: process.env.CLOUDINARY_API_KEY,
|
||||
@@ -36,6 +38,26 @@ export const generateUniqueString = async (
|
||||
return generateUniqueString(newSeed, isUniqueCallback, count)
|
||||
}
|
||||
|
||||
export const generateUniqueStringWithoutSeed = async (
|
||||
isUniqueCallback: (seed: string) => Promise<any>,
|
||||
count = 0
|
||||
) => {
|
||||
const seed = humanId({
|
||||
separator: '-',
|
||||
capitalize: false,
|
||||
})
|
||||
const isUnique = !(await isUniqueCallback(seed))
|
||||
if (isUnique) {
|
||||
return seed
|
||||
}
|
||||
count += 1
|
||||
if (count > 100) {
|
||||
console.log('trouble finding unique')
|
||||
return `very-unique-${seed}`.slice(0, 10)
|
||||
}
|
||||
return generateUniqueStringWithoutSeed(isUniqueCallback, count)
|
||||
}
|
||||
|
||||
export const destroyImage = ({ publicId }) =>
|
||||
new Promise((resolve, reject) => {
|
||||
cloudinary.uploader.destroy(publicId, (error, result) => {
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
import { partReactions } from './partReactions'
|
||||
*/
|
||||
|
||||
describe('partReactions', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,117 +0,0 @@
|
||||
import { db } from 'src/lib/db'
|
||||
import {
|
||||
foreignKeyReplacement,
|
||||
enforceAlphaNumeric,
|
||||
generateUniqueString,
|
||||
destroyImage,
|
||||
} from 'src/services/helpers'
|
||||
import { requireAuth } from 'src/lib/auth'
|
||||
import { requireOwnership } from 'src/lib/owner'
|
||||
|
||||
export const parts = ({ userName }) => {
|
||||
if (!userName) {
|
||||
return db.part.findMany({ where: { deleted: false } })
|
||||
}
|
||||
return db.part.findMany({
|
||||
where: {
|
||||
deleted: false,
|
||||
user: {
|
||||
userName,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const part = ({ id }) => {
|
||||
return db.part.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
export const partByUserAndTitle = async ({ userName, partTitle }) => {
|
||||
const user = await db.user.findUnique({
|
||||
where: {
|
||||
userName,
|
||||
},
|
||||
})
|
||||
return db.part.findUnique({
|
||||
where: {
|
||||
title_userId: {
|
||||
title: partTitle,
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const createPart = async ({ input }) => {
|
||||
requireAuth()
|
||||
return db.part.create({
|
||||
data: foreignKeyReplacement(input),
|
||||
})
|
||||
}
|
||||
|
||||
export const forkPart = async ({ input }) => {
|
||||
// Only difference between create and fork part is that fork part will generate a unique title
|
||||
// (for the user) if there is a conflict
|
||||
const isUniqueCallback = async (seed) =>
|
||||
db.part.findUnique({
|
||||
where: {
|
||||
title_userId: {
|
||||
title: seed,
|
||||
userId: input.userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
const title = await generateUniqueString(input.title, isUniqueCallback)
|
||||
// TODO change the description to `forked from userName/partName ${rest of description}`
|
||||
return db.part.create({
|
||||
data: foreignKeyReplacement({ ...input, title }),
|
||||
})
|
||||
}
|
||||
|
||||
export const updatePart = async ({ id, input }) => {
|
||||
requireAuth()
|
||||
await requireOwnership({ partId: id })
|
||||
if (input.title) {
|
||||
input.title = enforceAlphaNumeric(input.title)
|
||||
}
|
||||
const originalPart = await db.part.findUnique({ where: { id } })
|
||||
const imageToDestroy =
|
||||
originalPart.mainImage !== input.mainImage &&
|
||||
input.mainImage &&
|
||||
originalPart.mainImage
|
||||
const update = await db.part.update({
|
||||
data: foreignKeyReplacement(input),
|
||||
where: { id },
|
||||
})
|
||||
if (imageToDestroy) {
|
||||
console.log(
|
||||
`image destroyed, publicId: ${imageToDestroy}, partId: ${id}, replacing image is ${input.mainImage}`
|
||||
)
|
||||
// destroy after the db has been updated
|
||||
destroyImage({ publicId: imageToDestroy })
|
||||
}
|
||||
return update
|
||||
}
|
||||
|
||||
export const deletePart = async ({ id }) => {
|
||||
requireAuth()
|
||||
await requireOwnership({ partId: id })
|
||||
return db.part.update({
|
||||
data: {
|
||||
deleted: true,
|
||||
},
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
export const Part = {
|
||||
user: (_obj, { root }) =>
|
||||
db.part.findUnique({ where: { id: root.id } }).user(),
|
||||
Comment: (_obj, { root }) =>
|
||||
db.part.findUnique({ where: { id: root.id } }).Comment(),
|
||||
Reaction: (_obj, { root }) =>
|
||||
db.part
|
||||
.findUnique({ where: { id: root.id } })
|
||||
.Reaction({ where: { userId: _obj.userId } }),
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
import { parts } from './parts'
|
||||
*/
|
||||
|
||||
describe('parts', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -5,24 +5,24 @@ import { requireOwnership } from 'src/lib/owner'
|
||||
import { db } from 'src/lib/db'
|
||||
import { foreignKeyReplacement } from 'src/services/helpers'
|
||||
|
||||
export const partReactions = () => {
|
||||
return db.partReaction.findMany()
|
||||
export const projectReactions = () => {
|
||||
return db.projectReaction.findMany()
|
||||
}
|
||||
|
||||
export const partReaction = ({ id }) => {
|
||||
return db.partReaction.findUnique({
|
||||
export const projectReaction = ({ id }) => {
|
||||
return db.projectReaction.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
export const partReactionsByPartId = ({ partId }) => {
|
||||
return db.partReaction.findMany({
|
||||
where: { partId: partId },
|
||||
export const projectReactionsByProjectId = ({ projectId }) => {
|
||||
return db.projectReaction.findMany({
|
||||
where: { projectId },
|
||||
})
|
||||
}
|
||||
|
||||
export const togglePartReaction = async ({ input }) => {
|
||||
// if write fails emote_userId_partId @@unique constraint, then delete it instead
|
||||
export const toggleProjectReaction = async ({ input }) => {
|
||||
// if write fails emote_userId_projectId @@unique constraint, then delete it instead
|
||||
requireAuth()
|
||||
await requireOwnership({ userId: input?.userId })
|
||||
const legalReactions = ['❤️', '👍', '😄', '🙌'] // TODO figure out a way of sharing code between FE and BE, so this is consistent with web/src/components/EmojiReaction/EmojiReaction.js
|
||||
@@ -36,33 +36,33 @@ export const togglePartReaction = async ({ input }) => {
|
||||
let dbPromise
|
||||
const inputClone = { ...input } // TODO foreignKeyReplacement mutates input, which I should fix but am lazy right now
|
||||
try {
|
||||
dbPromise = await db.partReaction.create({
|
||||
dbPromise = await db.projectReaction.create({
|
||||
data: foreignKeyReplacement(input),
|
||||
})
|
||||
} catch (e) {
|
||||
dbPromise = db.partReaction.delete({
|
||||
where: { emote_userId_partId: inputClone },
|
||||
dbPromise = db.projectReaction.delete({
|
||||
where: { emote_userId_projectId: inputClone },
|
||||
})
|
||||
}
|
||||
return dbPromise
|
||||
}
|
||||
|
||||
export const updatePartReaction = ({ id, input }) => {
|
||||
return db.partReaction.update({
|
||||
export const updateProjectReaction = ({ id, input }) => {
|
||||
return db.projectReaction.update({
|
||||
data: foreignKeyReplacement(input),
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
export const deletePartReaction = ({ id }) => {
|
||||
return db.partReaction.delete({
|
||||
export const deleteProjectReaction = ({ id }) => {
|
||||
return db.projectReaction.delete({
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
export const PartReaction = {
|
||||
export const ProjectReaction = {
|
||||
user: (_obj, { root }) =>
|
||||
db.partReaction.findUnique({ where: { id: root.id } }).user(),
|
||||
part: (_obj, { root }) =>
|
||||
db.partReaction.findUnique({ where: { id: root.id } }).part(),
|
||||
db.projectReaction.findUnique({ where: { id: root.id } }).user(),
|
||||
project: (_obj, { root }) =>
|
||||
db.projectReaction.findUnique({ where: { id: root.id } }).project(),
|
||||
}
|
||||
141
app/api/src/services/projects/projects.ts
Normal file
141
app/api/src/services/projects/projects.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import type { Prisma } from '@prisma/client'
|
||||
import type { ResolverArgs } from '@redwoodjs/api'
|
||||
|
||||
import { db } from 'src/lib/db'
|
||||
import {
|
||||
foreignKeyReplacement,
|
||||
enforceAlphaNumeric,
|
||||
generateUniqueString,
|
||||
generateUniqueStringWithoutSeed,
|
||||
destroyImage,
|
||||
} from 'src/services/helpers'
|
||||
import { requireAuth } from 'src/lib/auth'
|
||||
import { requireOwnership } from 'src/lib/owner'
|
||||
|
||||
export const projects = ({ userName }) => {
|
||||
if (!userName) {
|
||||
return db.project.findMany({ where: { deleted: false } })
|
||||
}
|
||||
return db.project.findMany({
|
||||
where: {
|
||||
deleted: false,
|
||||
user: {
|
||||
userName,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const project = ({ id }: Prisma.ProjectWhereUniqueInput) => {
|
||||
return db.project.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
export const projectByUserAndTitle = async ({ userName, projectTitle }) => {
|
||||
const user = await db.user.findUnique({
|
||||
where: {
|
||||
userName,
|
||||
},
|
||||
})
|
||||
return db.project.findUnique({
|
||||
where: {
|
||||
title_userId: {
|
||||
title: projectTitle,
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
const isUniqueProjectTitle = (userId: string) => async (seed: string) =>
|
||||
db.project.findUnique({
|
||||
where: {
|
||||
title_userId: {
|
||||
title: seed,
|
||||
userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
interface CreateProjectArgs {
|
||||
input: Prisma.ProjectCreateArgs['data']
|
||||
}
|
||||
|
||||
export const createProject = async ({ input }: CreateProjectArgs) => {
|
||||
requireAuth()
|
||||
console.log(input.userId)
|
||||
const isUniqueCallback = isUniqueProjectTitle(input.userId)
|
||||
let title = input.title
|
||||
if (!title) {
|
||||
title = await generateUniqueStringWithoutSeed(isUniqueCallback)
|
||||
}
|
||||
return db.project.create({
|
||||
data: foreignKeyReplacement({
|
||||
...input,
|
||||
title,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const forkProject = async ({ input }) => {
|
||||
// Only difference between create and fork project is that fork project will generate a unique title
|
||||
// (for the user) if there is a conflict
|
||||
const isUniqueCallback = isUniqueProjectTitle(input.userId)
|
||||
const title = await generateUniqueString(input.title, isUniqueCallback)
|
||||
// TODO change the description to `forked from userName/projectName ${rest of description}`
|
||||
return db.project.create({
|
||||
data: foreignKeyReplacement({ ...input, title }),
|
||||
})
|
||||
}
|
||||
|
||||
interface UpdateProjectArgs extends Prisma.ProjectWhereUniqueInput {
|
||||
input: Prisma.ProjectUpdateInput
|
||||
}
|
||||
|
||||
export const updateProject = async ({ id, input }: UpdateProjectArgs) => {
|
||||
requireAuth()
|
||||
await requireOwnership({ projectId: id })
|
||||
if (input.title) {
|
||||
input.title = enforceAlphaNumeric(input.title)
|
||||
}
|
||||
const originalProject = await db.project.findUnique({ where: { id } })
|
||||
const imageToDestroy =
|
||||
originalProject.mainImage !== input.mainImage &&
|
||||
input.mainImage &&
|
||||
originalProject.mainImage
|
||||
const update = await db.project.update({
|
||||
data: foreignKeyReplacement(input),
|
||||
where: { id },
|
||||
})
|
||||
if (imageToDestroy) {
|
||||
console.log(
|
||||
`image destroyed, publicId: ${imageToDestroy}, projectId: ${id}, replacing image is ${input.mainImage}`
|
||||
)
|
||||
// destroy after the db has been updated
|
||||
destroyImage({ publicId: imageToDestroy })
|
||||
}
|
||||
return update
|
||||
}
|
||||
|
||||
export const deleteProject = async ({ id }: Prisma.ProjectWhereUniqueInput) => {
|
||||
requireAuth()
|
||||
await requireOwnership({ projectId: id })
|
||||
return db.project.update({
|
||||
data: {
|
||||
deleted: true,
|
||||
},
|
||||
where: { id },
|
||||
})
|
||||
}
|
||||
|
||||
export const Project = {
|
||||
user: (_obj, { root }: ResolverArgs<ReturnType<typeof project>>) =>
|
||||
db.project.findUnique({ where: { id: root.id } }).user(),
|
||||
Comment: (_obj, { root }: ResolverArgs<ReturnType<typeof project>>) =>
|
||||
db.project
|
||||
.findUnique({ where: { id: root.id } })
|
||||
.Comment({ orderBy: { createdAt: 'desc' } }),
|
||||
Reaction: (_obj, { root }: ResolverArgs<ReturnType<typeof project>>) =>
|
||||
db.project
|
||||
.findUnique({ where: { id: root.id } })
|
||||
.Reaction({ where: { userId: _obj.userId } }),
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
import { subjectAccessRequests } from './subjectAccessRequests'
|
||||
*/
|
||||
|
||||
describe('subjectAccessRequests', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
import { users } from './users'
|
||||
*/
|
||||
|
||||
describe('users', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -51,9 +51,9 @@ export const updateUserByUserName = async ({ userName, input }) => {
|
||||
`You've tried to used a protected word as you userName, try something other than `
|
||||
)
|
||||
}
|
||||
const originalPart = await db.user.findUnique({ where: { userName } })
|
||||
const originalProject = await db.user.findUnique({ where: { userName } })
|
||||
const imageToDestroy =
|
||||
originalPart.image !== input.image && originalPart.image
|
||||
originalProject.image !== input.image && originalProject.image
|
||||
const update = await db.user.update({
|
||||
data: input,
|
||||
where: { userName },
|
||||
@@ -73,14 +73,14 @@ export const deleteUser = ({ id }) => {
|
||||
}
|
||||
|
||||
export const User = {
|
||||
Parts: (_obj, { root }) =>
|
||||
db.user.findUnique({ where: { id: root.id } }).Part(),
|
||||
Part: (_obj, { root }) =>
|
||||
_obj.partTitle &&
|
||||
db.part.findUnique({
|
||||
Projects: (_obj, { root }) =>
|
||||
db.user.findUnique({ where: { id: root.id } }).Project(),
|
||||
Project: (_obj, { root }) =>
|
||||
_obj.projectTitle &&
|
||||
db.project.findUnique({
|
||||
where: {
|
||||
title_userId: {
|
||||
title: _obj.partTitle,
|
||||
title: _obj.projectTitle,
|
||||
userId: root.id,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user