massive refactor toDrop cascadeStudio and add CadQuery + OpenSCAD

resolves #400
This commit is contained in:
Kurt Hutten
2021-07-08 21:17:07 +10:00
parent 477a557eb8
commit 8e558d2342
158 changed files with 2335 additions and 2300 deletions

View 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!
}
`

View File

@@ -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 {

View File

@@ -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!
}
`

View File

@@ -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!
}
`

View 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!
}
`

View File

@@ -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]!
}

View File

@@ -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()

View File

@@ -1,9 +0,0 @@
/*
import { comments } from './comments'
*/
describe('comments', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})

View File

@@ -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(),
}

View File

@@ -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) => {

View File

@@ -1,9 +0,0 @@
/*
import { partReactions } from './partReactions'
*/
describe('partReactions', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})

View File

@@ -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 } }),
}

View File

@@ -1,9 +0,0 @@
/*
import { parts } from './parts'
*/
describe('parts', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})

View File

@@ -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(),
}

View 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 } }),
}

View File

@@ -1,9 +0,0 @@
/*
import { subjectAccessRequests } from './subjectAccessRequests'
*/
describe('subjectAccessRequests', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})

View File

@@ -1,9 +0,0 @@
/*
import { users } from './users'
*/
describe('users', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})

View File

@@ -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,
},
},