Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions packages/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ type Comment {
id: ID!
text: String
user: UserInterface!
published: Boolean!
}

"""
Expand All @@ -24,6 +25,20 @@ type CreateCommentPayload {
commentable: CommentableInterface!
}

type UpdateCommentPayload {
comment: Comment!
commentable: CommentableInterface!
}

type DeleteCommentPayload {
comment: Comment!
commentable: CommentableInterface!
}

type PublishCommentsPayload {
report: Report!
}

type Day implements CommentableInterface {
comments: [Comment!]!
createdAt: String!
Expand Down Expand Up @@ -129,6 +144,41 @@ type Mutation {
"""
createCommentOnReport(text: String!, id: ID!, traineeId: ID!): CreateCommentPayload!

"""
Updates a comment on a Day which is identified by the id argument.
"""
updateCommentOnDay(text: String!, id: ID!, traineeId: ID!, commentId: ID!): UpdateCommentPayload!

"""
Updates a comment on a Entry which is identified by the id argument.
"""
updateCommentOnEntry(text: String!, id: ID!, traineeId: ID!, commentId: ID!): UpdateCommentPayload!

"""
Updates a comment on a Report which is identified by the id argument.
"""
updateCommentOnReport(text: String!, id: ID!, traineeId: ID!, commentId: ID!): UpdateCommentPayload!

"""
Deletes a comment on a Day which is identified by the id argument.
"""
deleteCommentOnDay(id: ID!, traineeId: ID!, commentId: ID!): DeleteCommentPayload!

"""
Deletes a comment on a Entry which is identified by the id argument.
"""
deleteCommentOnEntry(id: ID!, traineeId: ID!, commentId: ID!): DeleteCommentPayload!

"""
Deletes a comment on a Report which is identified by the id argument.
"""
deleteCommentOnReport(id: ID!, traineeId: ID!, commentId: ID!): DeleteCommentPayload!

"""
Publishes all comments on a report which is identified by the id argument.
"""
publishAllComments(id: ID!, traineeId: ID!): PublishCommentsPayload!

"""
Creates a new entry which is assigned to the matching report based on the day Id
"""
Expand Down
117 changes: 117 additions & 0 deletions packages/api/src/graphql.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/backend/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const GermanTranslations: Translations = {
missingReport: 'Bericht konnte nicht gefunden werden',
missingDay: 'Tag konnte nicht gefunden werden',
missingEntry: 'Eintrag konnte nicht gefunden werden',
missingComment: 'Kommentar konnte nicht gefunden werden',
wrongReportStatus: 'Der Bericht hat den falschen Status',
wrongDayStatus: 'Der Tag hat den falschen Status',
userAlreadyExists: 'Ein Benutzer mit dieser Email existiert bereits',
Expand All @@ -26,6 +27,7 @@ export const GermanTranslations: Translations = {
missingPeriod: 'Die Ausbildungsperiode fehlt',
cantDeleteYourself: 'Ein Admin kann sich nicht selber für die Löschung markieren',
cantChangeOwnEmail: 'Ein Admin kann seine eigene E-Mail Adresse nicht ändern',
cantEditPublishedComment: 'Veröffentlichte Kommentare können nicht mehr editiert werden',
},
email: {
hello: 'Hallo',
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const EnglishTranslations: Translations = {
missingReport: "clouldn't find report",
missingDay: "clouldn't find day",
missingEntry: "couldn't find entry",
missingComment: "couldn't find comment",
wrongReportStatus: 'wrong report status',
wrongDayStatus: 'wrong Day status',
userAlreadyExists: 'User with this Email already exists',
Expand All @@ -26,6 +27,7 @@ export const EnglishTranslations: Translations = {
missingPeriod: 'Missing period',
cantDeleteYourself: "An Admin can't mark themselves for deletion",
cantChangeOwnEmail: "An Admin can't change their own e-mail address",
cantEditPublishedComment: 'Published Comments can not be edited',
},
email: {
hello: 'Hello',
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Translations = {
missingReport: string
missingDay: string
missingEntry: string
missingComment: string
wrongReportStatus: string
wrongDayStatus: string
userAlreadyExists: string
Expand All @@ -30,6 +31,7 @@ export type Translations = {
missingPeriod: string
cantDeleteYourself: string
cantChangeOwnEmail: string
cantEditPublishedComment: string
}
email: EmailTranslations
print: PrintTranslations
Expand Down
95 changes: 94 additions & 1 deletion packages/backend/src/resolvers/comment.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { AuthenticatedContext, GqlResolvers } from '@lara/api'

import { reportById } from '../repositories/report.repo'
import { userById } from '../repositories/user.repo'
import { commentableReports, createCommentOnCommentable } from '../services/comment.service'
import {
commentableReports,
createCommentOnCommentable,
deleteCommentOnCommentable,
publishCommentsOnReport,
updateCommentOnCommentable,
} from '../services/comment.service'
import { reportDayByDayId } from '../services/day.service'
import { reportDayEntryByEntryId } from '../services/entry.service'
import { t } from '../i18n'
Expand Down Expand Up @@ -71,5 +77,92 @@ export const commentResolver: GqlResolvers<AuthenticatedContext> = {
report,
})
},
updateCommentOnDay: async (_parent, { id, text, traineeId, commentId }, { currentUser }) => {
const reports = await commentableReports(traineeId)
const { report, day } = reportDayByDayId(id, reports)

return updateCommentOnCommentable({
commentable: day,
text,
currentUser,
report,
commentId,
})
},
updateCommentOnEntry: async (_parent, { id, text, traineeId, commentId }, { currentUser }) => {
const reports = await commentableReports(traineeId)
const { report, entry } = reportDayEntryByEntryId(id, reports)

return updateCommentOnCommentable({
commentable: entry,
text,
currentUser,
report,
commentId,
})
},
updateCommentOnReport: async (_parent, { id, text, traineeId, commentId }, { currentUser }) => {
const report = await reportById(id)

if (report?.traineeId !== traineeId) {
throw new GraphQLError(t('errors.missingReport', currentUser.language))
}

return updateCommentOnCommentable({
commentable: report,
text,
currentUser,
report,
commentId,
})
},
deleteCommentOnDay: async (_parent, { id, traineeId, commentId }, { currentUser }) => {
const reports = await commentableReports(traineeId)
const { report, day } = reportDayByDayId(id, reports)

return deleteCommentOnCommentable({
commentable: day,
currentUser,
report,
commentId,
})
},
deleteCommentOnEntry: async (_parent, { id, traineeId, commentId }, { currentUser }) => {
const reports = await commentableReports(traineeId)
const { report, entry } = reportDayEntryByEntryId(id, reports)

return deleteCommentOnCommentable({
commentable: entry,
currentUser,
report,
commentId,
})
},
deleteCommentOnReport: async (_parent, { id, traineeId, commentId }, { currentUser }) => {
const report = await reportById(id)

if (report?.traineeId !== traineeId) {
throw new GraphQLError(t('errors.missingReport', currentUser.language))
}

return deleteCommentOnCommentable({
commentable: report,
currentUser,
report,
commentId,
})
},
publishAllComments: async (_parent, { id, traineeId }, { currentUser }) => {
const report = await reportById(id)

if (report?.traineeId !== traineeId) {
throw new GraphQLError(t('errors.missingReport', currentUser.language))
}

return publishCommentsOnReport({
currentUser,
report,
})
},
},
}
14 changes: 14 additions & 0 deletions packages/backend/src/resolvers/report.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ export const reportTraineeResolver: GqlResolvers<TraineeContext> = {
: report
}

if (report && report.status === 'review')
return {
...report,
comments: report.comments.filter((com) => com.published),
days: report.days.map((day) => ({
...day,
entries: day.entries.map((entry) => ({
...entry,
comments: entry.comments.filter((com) => com.published),
})),
comments: day.comments.filter((com) => com.published),
})),
}

return report
},
},
Expand Down
128 changes: 128 additions & 0 deletions packages/backend/src/services/comment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const generateComment = (text: string, user: User): Comment => {
createdAt: new Date().toISOString(),
userId: user.id,
text,
published: false,
}
}

Expand Down Expand Up @@ -62,6 +63,133 @@ export const createCommentOnCommentable = async ({
}
}

type UpdateCommentOnCommentableOptions = {
commentable?: CommentableInterface
report?: Report
text: string
currentUser: User
commentId: string
}

/**
* Abstract method to update comments for entries, days and reports
* @param options Data for updating a comment
* @returns GQL update comment payload
*/
export const updateCommentOnCommentable = async ({
commentable,
text,
report,
currentUser,
commentId,
}: UpdateCommentOnCommentableOptions): Promise<GqlResolversTypes['UpdateCommentPayload']> => {
const t = createT(currentUser.language)

if (!report) {
throw new GraphQLError(t('errors.missingReport'))
}

if (!commentable) {
throw new GraphQLError(t('errors.missingCommentable'))
}

const comment = commentable.comments.find((com) => com.id === commentId)
if (!comment) {
throw new GraphQLError(t('errors.missingComment'))
}
if (comment.published) {
throw new GraphQLError(t('errors.cantEditPublishedComment'))
}
comment.text = text

await updateReport(report, { updateKeys: ['days', 'comments'] })

return {
comment,
commentable,
}
}

type DeleteCommentOnCommentableOptions = {
commentable?: CommentableInterface
report?: Report
currentUser: User
commentId: string
}

/**
* Abstract method to delete comments for entries, days and reports
* @param options Data for deleting a comment
* @returns GQL delete comment payload
*/
export const deleteCommentOnCommentable = async ({
commentable,
report,
currentUser,
commentId,
}: DeleteCommentOnCommentableOptions): Promise<GqlResolversTypes['DeleteCommentPayload']> => {
const t = createT(currentUser.language)

if (!report) {
throw new GraphQLError(t('errors.missingReport'))
}

if (!commentable) {
throw new GraphQLError(t('errors.missingCommentable'))
}

const comment = commentable.comments.find((com) => com.id === commentId)
if (!comment) {
throw new GraphQLError(t('errors.missingComment'))
}
if (comment.published) {
throw new GraphQLError(t('errors.cantEditPublishedComment'))
}
commentable.comments = commentable.comments.filter((com) => com.id !== comment.id)

await updateReport(report, { updateKeys: ['days', 'comments'] })

return {
comment,
commentable,
}
}

type PublishCommenstOnReportOptions = {
report?: Report
currentUser: User
}

/**
* Abstract method to publish all comments on a report
* @param options Data for publishing comments
* @returns GQL publish comments payload
*/
export const publishCommentsOnReport = async ({
report,
currentUser,
}: PublishCommenstOnReportOptions): Promise<GqlResolversTypes['PublishCommentsPayload']> => {
const t = createT(currentUser.language)

if (!report) {
throw new GraphQLError(t('errors.missingReport'))
}

report.comments.forEach((com) => (com.published = true))
report.days.forEach((day) => {
day.comments.forEach((com) => (com.published = true))
day.entries.forEach((entry) => {
entry.comments.forEach((com) => (com.published = true))
})
})

await updateReport(report, { updateKeys: ['days', 'comments'] })

return {
report,
}
}

export const commentableReports = async (traineeId: string): Promise<Report[]> => {
const trainee = await traineeById(traineeId)

Expand Down
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@rebass/grid": "^6.1.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-localization": "^2.0.6",
"styled-components": "^6.1.19",
"styled-modern-normalize": "^0.2.0"
},
Expand Down
Loading